主要内容:
Linux下的TCP文件共享系统实验记录、设计过程、具体代码。
相关信息
设计性实验2
内容: 设计并实现一个基于tcp的文件共享系统,均采用命令行界面。
要求如下:
Linux服务端:
客户端:
应用层协议:
服务端
对外提供两个接口,一个是请求共享文件夹的文件夹内的所有文件的路径信息,每个文件包括文件名、大小和修改日期三个属性;另一个是请求下载共享文件夹内的任意文件或文件目录。
客户端
接收用户输入,发送对应服务端两个接口的两种请求。
cpp#include <stdio.h>
#include <dirent.h>
#include <libgen.h>
#include <stdlib.h>
#include <vector>
#include <string>
#include <iostream>
#include <cstring>
#include <filesystem>
#include <stack>
#include <fstream>
#include <queue>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <thread>
#include <mutex>
# define CMD_HELLO 1
# define CMD_QUERY 2
# define CMD_FILEINVAILD 4
# define CMD_FILEFOUND 5
using namespace std;
mutex mtx;
// 创建指定长度的-
string spaces(int num) {
string result;
for (int i = 0; i < num; i++) {
result += "----";
}
return result;
}
// RootDictStructure类 它的实例抽象意义上代表一个文件夹和它里面的所有内容
class RootDictStructure {
private:
vector<RootDictStructure> childFolders; // 存储子文件夹的实例
vector<string> files; // 存储文件夹下所有文件的文件名
string path; // 指向这个文件夹的绝对路径
public:
string folderName; // 该文件夹的文件夹名
RootDictStructure(string rootPath);
string isExist(string targetName);
string toTreeString();
string toString();
RootDictStructure() { // 无参的默认构造函数
folderName = "";
};
};
// RootDictStructure类的构造方法 传入一个文件夹的绝对路径 返回该文件夹所对应的RootDictStructure类的实例
// 该方法会对子文件夹递归地创建RootDictStructure类的实例
RootDictStructure::RootDictStructure(string rootPath) {
path = rootPath;
folderName = filesystem::path(path).stem().string();
try {
for (const auto& entry : std::filesystem::directory_iterator(rootPath)) {
if (entry.is_directory()) {
childFolders.push_back(RootDictStructure(entry.path()));
} else if (entry.is_regular_file()) {
files.push_back(entry.path().filename());
}
}
} catch (const std::filesystem::filesystem_error& e) {
std::cout << "Error accessing folder: " << e.what() << std::endl;
}
}
// 对于给定的一个文件/文件夹的名 判断它是否存在于这个文件夹中
// 如果存在 则返回该文件/文件夹的绝对路径
// 如果不存在 则返回空字符串
string RootDictStructure::isExist(string targetName) {
if (folderName == targetName) {
return path;
}
for (int i = 0; i < files.size(); i++) {
if (files[i] == targetName) {
return path + "/" + files[i];
}
}
// 队列 做层序用的
std::queue<RootDictStructure> queue;
for (int s = 0; s < childFolders.size(); s++) {
queue.push(childFolders[s]);
}
while (queue.size() != 0) {
RootDictStructure element = queue.front();
queue.pop();
if (element.folderName == targetName) {
return element.path;
}
for (int i = 0; i < element.files.size(); i++) {
if (element.files[i] == targetName) {
return element.path + "/" + element.files[i];
}
}
for (int i = 0; i < element.childFolders.size(); i++) {
queue.push(element.childFolders[i]);
}
}
return "";
}
// 将文件夹内的所有内容抽象为文件夹树并且输出 该方法不在网络传输中使用 仅作为检查信息
string RootDictStructure::toTreeString() {
string result = folderName + " (Folder)" + "\n";
for (int i = 0; i < files.size(); i++) {
result = result + "----" + files[i] + " (File)" + "\n";
}
// 栈 做前序用的 栈里的每个元素都对应一个文件夹结构和它在树中的深度
std::stack<std::pair<RootDictStructure, int>> stack;
for (int s = childFolders.size() - 1; s >= 0; s--) {
stack.push({childFolders[s], 1});
}
while (stack.size() != 0) {
std::pair<RootDictStructure, int> element = stack.top();
stack.pop();
result = result + spaces(element.second) + element.first.folderName + " (Folder)" + "\n";
for (int i = 0; i < element.first.files.size(); i++) {
result = result + spaces(element.second + 1);
result += element.first.files[i];
result += " (File)";
result += "\n";
}
for (int i = element.first.childFolders.size() - 1; i >= 0; i--) {
stack.push({element.first.childFolders[i], element.second + 1}); // 深度 + 1
}
}
return result;
}
// 将文件夹内的所有文件结构转换为可发送的字符串
string RootDictStructure::toString() {
/*
格式:
最开始的是主文件夹名
之后每一个{}内部表示一个文件夹和它内部的所有子文件 最开始是文件夹名 []内表示该文件夹在文件树上的层数 之后是文件名 ()内表示该文件的占用子节数
*/
string result = "";
// 栈 做前序用的 栈里的每个元素都对应一个文件夹结构和它在树中的深度
std::stack<std::pair<RootDictStructure, int>> stack;
stack.push({*this, 0});
struct stat statbuf; // 读取文件信息所需要的结构体
while (stack.size() != 0) {
std::pair<RootDictStructure, int> element = stack.top();
stack.pop();
result = result + "{" + element.first.folderName + "[" + std::to_string(element.second) + "]";
for (int i = 0; i < element.first.files.size(); i++) {
result += element.first.files[i];
stat((element.first.path + "/" + element.first.files[i]).c_str(), &statbuf);
// 这个statbuf.st_size是off_t类型 是偏移位类型 linux里可以等效是long类型
result += "(" + std::to_string(statbuf.st_size) + ")"; // 这个操作会将其转换为字符串类型 使用unicode编码 还有上面的element.second也是 整型转字符串 使用unicode编码
}
result += "}";
for (int i = element.first.childFolders.size() - 1; i >= 0; i--) {
stack.push({element.first.childFolders[i], element.second + 1}); // 深度 + 1
}
}
return result;
}
// 按十六进制打印给定的字符串 仅作检查信息使用
void printHex(const std::string& str) {
std::stringstream ss;
for (int i = 0; i < str.length(); i++) {
unsigned char c = static_cast<unsigned char>(str[i]);
ss << std::setw(2) << std::setfill('0') << std::hex << static_cast<int>(c);
if ((i + 1) % 2 == 0) {
ss << " ";
}
if ((i + 1) % 24 == 0) {
ss << std::endl;
}
}
std::cout << ss.str() << std::endl;
}
RootDictStructure root;
// 线程的启动函数 处理每一个连接
void clientHandler(int socketfd, string clientInfo) {
mtx.lock();
cout << "Received Hello from " << clientInfo << ", Thread ID is: " << this_thread::get_id() << endl;
mtx.unlock();
char buffer[2048];
memset(buffer, 0, sizeof(buffer));
int n = recv(socketfd, buffer, sizeof(buffer), 0);
if (n < 0) {
perror("recv error!");
return;
}
if (buffer[0] != CMD_HELLO) {
mtx.lock();
cout << "Hello from " << clientInfo << " is invaild: " << buffer << endl;
mtx.unlock();
close(socketfd);
return;
}
string reply = root.toString();
n = send(socketfd, reply.c_str(), strlen(reply.c_str()), 0);
if (n < 0) {
perror("send error");
return;
}
while (true) {
sleep(1);
memset(buffer, 0, sizeof(buffer));
n = recv(socketfd, buffer, sizeof(buffer), 0);
if (n < 0) {
perror("recv error!");
return;
}
if (n == 0) {
mtx.lock();
cout << "Exit from " << clientInfo << endl;
mtx.unlock();
close(socketfd);
return;
}
if (buffer[0] == CMD_QUERY) {
string queryFileName = string(buffer + 1);
mtx.lock();
cout << "Query File: " + queryFileName + " from " << clientInfo << endl;
mtx.unlock();
string queryFilePath = root.isExist(queryFileName);
memset(buffer, 0, sizeof(buffer));
if (queryFilePath == "") {
mtx.lock();
cout << "Query File: " + queryFileName + " from " << clientInfo << " is invaild: " << buffer << endl;
mtx.unlock();
buffer[0] = CMD_FILEINVAILD;
n = send(socketfd, buffer, 2048, 0);
if (n < 0) {
perror("send error");
return;
}
} else {
mtx.lock();
cout << "Query File: " + queryFileName + " from " << clientInfo << " is found!" << endl;
mtx.unlock();
ifstream file(queryFilePath);
if (!file.is_open()) {
mtx.lock();
cout << "Query File: " + queryFileName + " from " << clientInfo << " can not open!" << endl;
mtx.unlock();
}
file.seekg(0, std::ios::end);
std::streampos fileSize = file.tellg() + 1;
file.seekg(0, std::ios::beg);
char* fileBuffer = new char[fileSize];
fileBuffer[0] = CMD_FILEFOUND;
file.read(fileBuffer + 1, fileSize - 1);
file.close();
n = send(socketfd, fileBuffer, fileSize, 0);
if (n < 0) {
perror("send error");
return;
}
delete[] fileBuffer;
}
} else {
mtx.lock();
cout << "Query from " << clientInfo << " is invaild: " << buffer << ", try to receive again!" << endl;
mtx.unlock();
}
}
close(socketfd);
}
int main(int argc, char const *argv[])
{
if (argc != 4) {
cout << "Usage: ./server ip port dictionary" << endl;
return 0;
}
root = RootDictStructure(argv[3]);
cout << "File Structure is Loaded:" << endl;
cout << root.toTreeString() << endl;
int port = atoi(argv[2]);
string serverIP = argv[1];
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1) {
perror("Create Socket Failed: \n");
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(serverIP.c_str());
int ret = bind(server_socket, (struct sockaddr*)&addr, sizeof(addr));
if (0 > ret) {
perror("Bind Failed: \n");
close(server_socket);
return -1;
}
cout << "Start Serving" << endl;
listen(server_socket, 10);
int clientfd;
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
char client_ip[INET_ADDRSTRLEN];
int client_port = 0;
while (true) {
clientfd = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
if (clientfd < 0) {
perror("accept error: ");
continue;
}
inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, INET_ADDRSTRLEN);
client_port = ntohs(client_addr.sin_port);
thread t(clientHandler, clientfd, string(client_ip) + ":" + to_string(client_port));
t.detach();
}
return 0;
}
cpp#include <iostream>
#include <string>
#include <vector>
#include <bits/stdc++.h>
#include <unordered_map>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fstream>
# define CMD_HELLO 1
# define CMD_QUERY 2
# define CMD_FILEINVAILD 4
# define CMD_FILEFOUND 5
using namespace std;
string spaces(int num) {
string result;
for (int i = 0; i < num; i++) {
result += "----";
}
return result;
}
// parseStringToFolder 传入一段表示文件夹结构的字符串 将其进行解析并且输出
void parseString(string inputString) {
int rightPointer = 0, leftPointer = 0;
while (true) {
if (inputString[rightPointer] != '[') {
rightPointer += 1;
} else {
break;
}
}
string folderName = inputString.substr(0, rightPointer); // 找传入的文件夹的名字
leftPointer = rightPointer + 1;
while (inputString[rightPointer] != ']') {
rightPointer += 1;
}
int depth = stoi(inputString.substr(leftPointer, rightPointer - leftPointer)); // 找传入的文件夹的对应节点的深度
cout << spaces(depth) + folderName + " (Folder)" << endl;
// 开始解析该文件夹下的文件信息
leftPointer = rightPointer + 1;
while (rightPointer < inputString.size()) {
if (inputString[rightPointer] != '(') {
rightPointer += 1;
continue;
} else {
string fileName = inputString.substr(leftPointer, rightPointer - leftPointer);
leftPointer = rightPointer + 1;
while (inputString[rightPointer] != ')') {
rightPointer += 1;
}
cout << spaces(depth + 1) + fileName + " (File)" + " Size: " + inputString.substr(leftPointer, rightPointer - leftPointer) + " bytes" << endl;
leftPointer = rightPointer + 1;
}
}
}
// startParseString 开始将传入的表示全部文件夹结构字符串进行解析 并按照文件夹结构的方式进行输出
void startParseString(string inputString) {
cout << "Start Parse String To Folder Tree" << endl;
int rightPointer = 0, leftPointer = 0;
// 先找根节点
while (true) {
if (inputString[rightPointer] != '}') {
rightPointer += 1;
} else {
break;
}
}
while (true) {
if (inputString[leftPointer] != '{') {
rightPointer += 1;
} else {
break;
}
}
parseString(inputString.substr(leftPointer + 1, rightPointer - leftPointer - 1));
// 再找剩下的子节点
rightPointer += 1;
while (rightPointer < inputString.size()) {
leftPointer = rightPointer;
while (inputString[rightPointer] != '}') {
rightPointer += 1;
}
parseString(inputString.substr(leftPointer + 1, rightPointer - leftPointer - 1));
rightPointer += 1;
}
cout << "Parse Folder Tree Finished!" << endl;
}
int main(int argc, char const *argv[])
{
if (argc != 3) {
cout << "Usage: ./client ip port" << endl;
return 0;
}
int socketfd = socket(AF_INET, SOCK_STREAM, 0);
if (socketfd == -1) {
perror("create socket failed: \n");
return -1;
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[2]));
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
if (connect(socketfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("connect server failed: \n");
return -1;
}
char buffer[4096];
memset(buffer, 0, sizeof(buffer));
buffer[0] = CMD_HELLO;
ssize_t bytes_sent = send(socketfd, buffer, sizeof(buffer), 0);
if (bytes_sent == -1) {
perror("sending hello failed: \n");
close(socketfd);
return -1;
}
buffer[0] = 0;
ssize_t bytes_recv = recv(socketfd, buffer, sizeof(buffer), 0);
if (bytes_recv == -1) {
perror("receiving file structure failed: \n");
close(socketfd);
return -1;
}
cout << "Connect to Server Successful! Server File Structure: " << endl;
startParseString(buffer);
memset(buffer, 0, sizeof(buffer));
string fileName = "";
while (true) {
cout << "Please Enter File Name that will Sync to There, You can Exit with Enter 'exit': " << endl;
cin >> fileName;
if (fileName == "exit") {
break;
}
buffer[0] = CMD_QUERY;
strcpy(buffer + 1, fileName.c_str());
bytes_sent = send(socketfd, buffer, sizeof(buffer), 0);
if (bytes_sent == -1) {
perror("sending query failed: \n");
close(socketfd);
return -1;
}
memset(buffer, 0, sizeof(buffer));
ssize_t bytes_recv = recv(socketfd, buffer, sizeof(buffer), 0);
if (bytes_recv == -1) {
perror("receiving data failed: \n");
close(socketfd);
return -1;
}
if (buffer[0] == CMD_FILEINVAILD) {
cout << "File is Invaild!, Please Enter File Name Again!" << endl;
continue;
} else if (buffer[0] == CMD_FILEFOUND) {
cout << "File is Found!" << endl;
ofstream file(fileName);
file.write(buffer + 1, bytes_recv - 1);
if (bytes_recv == 4096) {
while (true) {
memset(buffer, 0, sizeof(buffer));
ssize_t bytes_recv = recv(socketfd, buffer, sizeof(buffer), 0);
if (bytes_recv == -1) {
perror("receiving data failed: \n");
close(socketfd);
return -1;
} else {
file.write(buffer, bytes_recv);
if (bytes_recv != 4096) {
break;
}
}
}
}
file.flush();
file.close();
cout << "Receive File Successfully!" << endl;
}
}
close(socketfd);
return 1;
}
本文作者:御坂19327号
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!