实验任务
-
实现一个CS模式的文件传输应用,要求
- 客户端可以向服务器上传文件
- 客户端可以获取服务器文件列表,并下载指定文件
-
效果示例 (源码)
实验步骤一、确定传输协议
-
作为一个双向文件传输的C/S应用,必须规定双方交流的规则,也就是协议的语法、语义、时序
-
协议格式:操作码(ASCII字符) + 信息
其中
- close connection \text{close connection} close connection, o p t = 0 opt = 0 opt=0,客户端发起,结束连接
- client → 文 件 server \text{client} \xrightarrow{文件} \text{server} client文件server, o p t = 1 opt = 1 opt=1,由客户端发送请求,服务器返回确认,之后进入文件传输
- client ← 文 件 列 表 server \text{client} \xleftarrow{文件列表} \text{server} client文件列表server, o p t = 2 opt = 2 opt=2,对于下载服务器文件,首先要请求文件列表,服务器返回文件数量及文件名列表,每个文件名长度为32字节
- client ← 文 件 server \text{client} \xleftarrow{文件} \text{server} client文件server, o p t = 4 opt = 4 opt=4,客户端指定文件,服务器返回文件长度,之后进入文件传输
- file transfer \text{file transfer} file transfer, o p t = 5 opt=5 opt=5,操作码后跟4字节的传输内容长度,从第五个字节开始是传输的内容主体
-
协议封装及提取实现示例:服务器封装文件列表,客户端提取
server.c
void sendFileList(SOCK conn) { int fileCount = 0; /* 记录文件数量 */ char sendBuf[BUFSIZE] = {0}; /* 发送缓冲区 */ sendBuf[0] = '2'; /* 首字节为'2',返回文件列表 */ /* 目录获取文件列表*/ DIR* dir; if((dir = opendir(".")) == NULL) { printf("open dir error(%d): %s\n"); exit(0); } struct dirent *ent; while((ent = readdir(dir)) != NULL) { if(ent->d_type == 8) { /* 将文件名拷贝至发送缓冲区中,每个文件名占32字节 */ strncpy(sendBuf + 5 + (fileCount++) * 32, ent->d_name, strlen(ent->d_name) + 1); } } /* 将文件数量填充至第2个字节开始的四个字节中 */ *(int *)(sendBuf + 1) = fileCount; /* 向服务器发送按协议格式封装的文件列表 */ if(send(conn, sendBuf, 5 + 32 * fileCount, 0) == -1) { printf("send error(%d): %s\n", errno, strerror(errno)); exit(0); } }
client.c
void sendFileListRequest(SOCK conn) { /* 发送获取服务器文件列表请求 */ char sendBuf[BUFSIZ] = {0}; /* 请求缓冲区 */ sendBuf[0] = '2'; /* opt=2 请求服务器文件列表 */ if(send(conn, sendBuf, 1, 0) == -1) { printf("send error(%d): %s\n", errno, strerror(errno)); exit(0); } /* 解析服务器响应 */ char recvBuf[BUFSIZE] = {0}; /* 接收缓冲区 */ int recvLen; if((recvLen = recv(conn, recvBuf, sizeof(recvBuf), 0)) == -1) { printf("recv error(%d): %s\n", errno, strerror(errno)); exit(0); } /* 根据协议,服务器返回的首字节应为 opt=2 */ if(recvBuf[0] != '2') { printf("unknown error\n"); exit(0); } /* 获取文件数量 */ int fileCount = *(int *)(recvBuf + 1); printf("%d\n", fileCount); int i; /* 循环输出文件名,每个文件名占32字节,可依次定位文件名地址 */ for(i = 0; i < fileCount; i++) { printf("%d. %s\n", i + 1, recvBuf + 5 + i * 32); } }
实验步骤二、服务器实现
-
主程序
int main() { /* 创建server socket并设置监听 略 */ printf("监听中......\n"); /* 客户端地址信息 */ int addrLen = sizeof(connAddr); // 这个客户端地址长度一定要初始化,不然无法记录地址信息 char addrBuf[32] = {0}; /* 等待客户端连接 */ while(1) { /* 接收客户端连接 */ if((conn = accept(server, (struct sockaddr*)&connAddr, &addrLen)) == -1) { printf("accept connection error(%d): %s\n", errno, strerror(errno)); continue; } /* 输出客户端地址信息 */ if(inet_ntop(AF_INET, &connAddr.sin_addr, addrBuf, sizeof(addrBuf)) == NULL) { printf("net address errro(%d), %s\n", errno, strerror(errno)); exit(0); } printf("接收到连接请求: %s!\n连接成功!\n", addrBuf); /* 处理客户端请求 */ responde(conn); } /* 释放socket资源 略 */ return 0; }
-
响应主程序
void responde(SOCK conn) { char recvBuf[BUFSIZE]; int recvLen; int closed = 0; /* 客户端断开连接标志 */ /* 循环处理客户端请求 */ while(1) { if(closed) break; /* 获取客户端请求 */ if((recvLen = recv(conn, recvBuf, sizeof(recvBuf), 0)) == -1) { printf("recv errro(%d): %s\n", errno, strerror(errno)); exit(0); } /* 解析客户端请求 */ switch (recvBuf[0]) { case '0': close(conn); closed = 1; printf("本次连接关闭!等待下个连接\n"); break; case '1': printf("接受文件: %s 文件大小: %d字节\n", recvBuf + 1, *(int *)(recvBuf + 33)); recvFile(conn, recvBuf + 1, *(int *)(recvBuf + 33)); break; case '2': printf("收到下载请求!\n"); sendFileList(conn); break; case '4': sendFile(conn, recvBuf + 1); break; default: break; } } }
-
功能实现:按照协议实现即可
实验步骤三、客户端实现
-
提示信息
char divider[] = { "=========================================\n" }; char options[] = { "欢迎来到文件系统,请选择需要的功能:\n" "send: 向服务器传送一个文件\n" "download: 从服务器下载一个文件\n" "quit: 退出程序\n" }; char inputTip[] = { "请输入操作指令: " };
-
主程序
int main(int argc, char *argv[]) { if(argc != 2) { printf("请输入服务器IP地址...\n"); exit(0); } /* 解析服务器地址 */ if(inet_pton(AF_INET, argv[1], &servAddr.sin_addr) == -1) { printf("net address error(%d): %s\n", errno, strerror(errno)); exit(0); } /* 创建client socket 略 */ /* 连接服务器 */ if((connect(client, (struct sockaddr*)&servAddr, sizeof(servAddr))) == -1) { printf("connect to server error(%d): %s\n", errno, strerror(errno)); exit(0); } /* 发起请求 */ request(client); /* 释放socket资源 略 */ return 0; }
-
请求主程序
void request(SOCK conn) { char option[32] = {0}; char fileName[32] = {0}; /* 循环发起请求 */ while(1) { /* 打印提示信息 */ printf("%s%s%s", divider, options, inputTip); scanf("%s", option); switch (option[0]) { case 's': printf("请输入要传送的文件名:"); scanf("%s", fileName); sendFile(conn, fileName); break; case 'd': printf("服务器端的文件列表:\n"); sendFileListRequest(conn); printf("请输入需要下载的文件名:"); scanf("%s", fileName); downloadFile(conn, fileName); break; case 'q': sendCloseRequest(conn); return ; default: break; } } }
-
功能实现:按照协议实现即可
总结
- 时间比较紧,客户端和服务器的不少代码是重复的,也比较乱,以后有时间再整理