- 客户端下载服务器所在目录的文件
- TCP 粘包问题- -面试
- 解决方案
- recv
- send
- 要求
- 代码实现
- 服务器-----server.c
- 客户端----client.c
- 执行结果
- 注意
- 6. 非原创
TCP 粘包问题- -面试解决方案
TCP的底层有一个 Nagel 算法,这个算法会将一定短的时间内的
发往同一个接收端的多个小的数据包
组装成一个整体 发送给对方
但是对方没法确定每条消息的边界,
所以就导致了 接收方收到消息后无法正确的解析
- 只要保证每次发送的数据包一样大,接收方也按照这个大小去接收 - 就不会出现粘包问题 - 每次收发的数据不要以字符数组的方式发送, - 把要发送的数据封装成一个结构体发送给对方,对方也要使用同样的结构体去接 - 循环发送文件内容recv
功能: 在套接字中接收一条消息 如果对方的socket已经关闭了,recv会返回0send
功能:
向套接字中发送一条消息
如果对方的socket已经关闭了,第一次send会返回0
第二次send会报错 SIGPIPE
要求
- 客户端和服务器不要运行在同一路径 - 客户端先给服务器发送要下载的文件名 - 服务器收到文件名后,判断当前路径下是否有该文件 (open(只读)--ENOENT) - 如果文件不存在,则发送 不存在的提示 给客户端 - 如果文件存在,也要先告诉客户端文件存在,然后 - 循环读取文件内容,发送给客户端 - 客户端新建一个文件,接收文件内容,并保存到文件中。代码实现 服务器-----server.c
#include客户端----client.c#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ERRLOG(errmsg) do { printf("%s--%s(%d):", __FILE__, __func__, __LINE__); perror(errmsg); exit(-1); } while (0) //用结构体传输--防止TCP 粘包问题 typedef struct __MSG { int num; char buff[128]; } msg_t; int main(int argc, const char *argv[]) { if (3 != argc) { printf("Usage : %s n", argv[0]); exit(-1); } // 1.创建套接字 int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (-1 == sockfd) { ERRLOG("socket error"); } // 2.填充服务器网络信息结构体 struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; //端口号 填 8888 9999 6789 ...都可以 server_addr.sin_port = htons(atoi(argv[2])); // ip地址 要么是当前Ubuntu主机的IP地址 或者 //如果本地测试的化 使用 127.0.0.1 也可以 server_addr.sin_addr.s_addr = inet_addr(argv[1]); //结构体长度 socklen_t server_addr_len = sizeof(server_addr); // 3.将套接字和网络信息结构体绑定 if (-1 == bind(sockfd, (struct sockaddr *)&server_addr, server_addr_len)) { ERRLOG("bind error"); } //将套接字设置成被动监听状态 if (-1 == listen(sockfd, 10)) { ERRLOG("listen error"); } //用来保存客户端信息的结构体 struct sockaddr_in client_addr; memset(&client_addr, 0, sizeof(client_addr)); //清空 socklen_t client_addr_len = sizeof(client_addr); int accept_fd; char filename[128] = {0}; char buff[128] = {0}; int ret = 0; int fd; msg_t msg; //用结构体传输--防止TCP 粘包问题 memset(&msg, 0, sizeof(msg_t)); //清空 ACC: //重新等待客户端连接 //阻塞等待客户端连接--一旦有客户端连接就会解除阻塞 printf("等待客户端n"); accept_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addr_len); if (-1 == accept_fd) ERRLOG("accept error"); printf("客户端 (%s:%d) 连接成功n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); RECV_filename: //重新接收 //接收客户端发来的数据 if (0 > recv(accept_fd, &msg, sizeof(msg_t), 0)) ERRLOG("recv error"); strcpy(filename, msg.buff); printf("要下载的文件是:[%s]n", filename); //只读 if (-1 == (fd = open(filename, O_RDONLY))) //-1错误 { if (errno == ENOENT) { //文件不存在 //发送文件不存在的消息给客户端 memset(&msg, 0, sizeof(msg_t)); //清空 strcpy(msg.buff, "文件不存在"); if (0 > send(accept_fd, &msg, sizeof(msg_t), 0)) ERRLOG("send error"); goto RECV_filename; //重新接收 } ERRLOG("open error"); //其他错误 } //文件存在也要发送 文件存在的信息给客户端 memset(&msg, 0, sizeof(msg_t)); strcpy(msg.buff, "文件存在"); if (0 > send(accept_fd, &msg, sizeof(msg_t), 0)) ERRLOG("send error"); //循环发送文件内容 while ((ret = read(fd, buff, 128)) > 0) { memset(&msg, 0, sizeof(msg_t)); printf("ret= %dn", ret); msg.num = ret; strcpy(msg.buff, buff); if (0 > send(accept_fd, &msg, sizeof(msg_t), 0)) ERRLOG("send error"); } //发送文件结束的标志 memset(&msg, 0, sizeof(msg_t)); strcpy(msg.buff, "结束"); msg.num = 0; //结束的标志 if (0 > send(accept_fd, &msg, sizeof(msg_t), 0)) ERRLOG("send error"); close(fd); close(accept_fd); goto ACC; //重新等待客户端连接 close(sockfd); return 0; }
#include执行结果 注意#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //用结构体传输--防止TCP 粘包问题 typedef struct __MSG { int num; char buff[128]; } msg_t; #define ERRLOG(errmsg) do { printf("%s--%s(%d):", __FILE__, __func__, __LINE__); perror(errmsg); exit(-1); } while (0) int main(int argc, const char *argv[]) { if (3 != argc) { printf("Usage : %s n", argv[0]); exit(-1); } // 1.创建套接字 int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (-1 == sockfd) { ERRLOG("socket error"); } // 2.填充服务器网络信息结构体 struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; //端口号 填 8888 9999 6789 ...都可以 server_addr.sin_port = htons(atoi(argv[2])); // ip地址 要么是当前Ubuntu主机的IP地址 或者 //如果本地测试的化 使用 127.0.0.1 也可以 server_addr.sin_addr.s_addr = inet_addr(argv[1]); //结构体长度 socklen_t server_addr_len = sizeof(server_addr); //与服务器建立连接 if (-1 == connect(sockfd, (struct sockaddr *)&server_addr, server_addr_len)) { ERRLOG("connect error"); } char filename[128] = {0}; char buff[128] = {0}; msg_t msg; //用结构体传输--防止TCP 粘包问题 memset(&msg, 0, sizeof(msg_t)); //清空 INPUT_filename: printf("下载文件名: "); scanf("%s", filename); strcpy(msg.buff, filename); //发--将文件名发给服务器 if (0 > send(sockfd, &msg, sizeof(msg_t), 0)) ERRLOG("send error"); //收--接收文件是否存在的信息 if (0 > recv(sockfd, &msg, sizeof(msg_t), 0)) ERRLOG("recv error"); //如果文件不存在 重新输入文件名 if (0 == strcmp(msg.buff, "文件不存在")) { printf("文件不存在n"); goto INPUT_filename; //重新发送 } //文件存在 则循环接收文件内容 并写到文件里 if (strcmp(msg.buff, "文件存在") == 0) { int dfd; if ((dfd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0664)) == -1) ERRLOG("open src error"); while (0 != recv(sockfd, &msg, sizeof(msg_t), 0)) { printf("msg.num = %dn", msg.num); if (msg.num == 0) break; if (-1 == write(dfd, msg.buff, msg.num)) ERRLOG("write error"); } close(dfd); } printf("文件[%s]下载完成n", filename); close(sockfd); return 0; }
- 使用scanf输入的字符串 结尾没有 'n
scanf("%s", filename);
- 如果使用 fgets() 需要手动清理换行符
fgets(filename, N, stdin);
filename[strlen(filename)-1] = ' ';//将结尾的 n 换成
6. 非原创


