1. TCP Server测试
在我https://www.cnblogs.com/Suzkfly/p/14049687.html这篇博客中提到,按照文种的范例程序测试,在Ubuntu中运行a.out,在windows下用网络调试助手不断的断开重连,在Ubuntu下另开一个终端,运行执行ps -au命令,可以看到每断开重连一次,系统中就会遗留一个进程,如下图:
2. 修改TCP Server代码
在调用recv函数时返回0,因此知道客户端断开连接了,此时需要进行的操作是让子进程退出,父进程重新开始连接,而由于父进程被scanf阻塞了,它不能及时调用到waitpid函数而得知子进程退出了,因此需要将标准输入设置为非阻塞,具体的设置方法参见:https://www.cnblogs.com/Suzkfly/p/14331287.html
修改后的代码如下:
1 /** 2 * filename: tcp_server.c 3 * author: Suzkfly 4 * date: 2021-01-25 5 * platform: Ubuntu 6 * 配合windows的网络调试工具使用: 7 * 1、先保证windows与Ubuntu在同一网段且互相能ping通; 8 * 2、在windows下打开网络调试助手,选择协议类型为TCP Client,远程主机地址为 9 * Ubuntu的IP地址,远程主机端口为Ubuntu例程中写的端口,接收设置和发送设 10 * 置都选择ASCLL。 11 * 3、运行Ubuntu下的TCP服务器程序; 12 * 4、网络调试助手上点击“连接”。 13 * 5、连接成功后在网络调试助手上发送数据,在Ubuntu下的终端上能看到, 14 * 在Ubuntu下的终端上输入字符串按回车发送,在windows上的网络调试助手上也 15 * 能看到。 16 */ 17 #include <stdio.h> 18 #include <sys/types.h> 19 #include <sys/socket.h> 20 #include <string.h> 21 #include <netinet/in.h> 22 #include <arpa/inet.h> 23 #include <sys/wait.h> 24 #include <unistd.h> 25 #include <fcntl.h> 26 27 //#define IP_ADDR "127.0.0.1" /* IP地址 */ 28 #define PORT 10000 /* 端口号 */ 29 30 int main(int argc, const char *argv[]) 31 { 32 int sock_fd = 0, confd = 0; 33 struct sockaddr_in serv_addr; /* 服务器IP(本机IP) */ 34 struct sockaddr_in client_addr; /* 客户端IP(连接者IP) */ 35 socklen_t addr_len = sizeof(struct sockaddr_in); 36 int ret = 0; /* 用于接收函数返回值 */ 37 int pid = 0; 38 char buf[128] = { 0 }; /* 用于存放数据的缓冲区 */ 39 int len = 0; /* 发送和接收数据的长度 */ 40 int ret_pid = 0; 41 int attr = 0; /* 文件描述符属性 */ 42 43 /* 创建TCP套接字 */ 44 sock_fd = socket(AF_INET, SOCK_STREAM, 0); 45 46 /* 将套接字与IP和端口绑定 */ 47 memset(&serv_addr, 0, sizeof(struct sockaddr_in)); 48 serv_addr.sin_family = AF_INET; 49 //serv_addr.sin_addr.s_addr = inet_addr(IP_ADDR); /* 绑定IP */ 50 serv_addr.sin_addr.s_addr = 0; /* 绑定0就是绑定自己 */ 51 serv_addr.sin_port = htons(PORT); /* 端口号 */ 52 ret = bind(sock_fd, (struct sockaddr*)&serv_addr, sizeof(struct sockaddr_in)); 53 if (ret == 0) { 54 printf("bind ok\n"); 55 } else { 56 printf("bind failed\n"); 57 close(sock_fd); 58 return 0; 59 } 60 61 /* 让套接字进入被动监听状态 */ 62 ret = listen(sock_fd, 10); 63 if (ret == 0) { 64 printf("listen ok\n"); 65 } else { 66 printf("listen failed\n"); 67 close(sock_fd); 68 return 0; 69 } 70 71 re_connect: 72 73 /* 接收客户端请求(阻塞) */ 74 memset(&client_addr, 0, sizeof(client_addr)); 75 printf("accept...\n"); 76 confd = accept(sock_fd, (struct sockaddr *)&client_addr, &addr_len); 77 printf("confd = %d\n", confd); 78 if (confd > 0) { 79 printf("accept ok\n"); 80 } else { 81 printf("accept failed\n"); 82 close(sock_fd); 83 return 0; 84 } 85 86 /* 打印客户端信息 */ 87 printf("addr_len = %d\n", addr_len); 88 printf("Client IP: %s\n", inet_ntoa(client_addr.sin_addr)); /* IP地址 */ 89 printf("Client Port:%d\n", ntohs(client_addr.sin_port)); /* 端口号 */ 90 91 memset(buf, 0, sizeof(buf)); 92 93 pid = fork(); 94 95 if (pid > 0) { /* 父进程,发送数据 */ 96 attr = fcntl(STDIN_FILENO, F_GETFL); 97 attr |= O_NONBLOCK; 98 fcntl(STDIN_FILENO, F_SETFL, attr); 99 while (1) { 100 ret_pid = waitpid(-1, NULL, WNOHANG); /* 用非阻塞的方式等待子进程退出 */ 101 if (ret_pid == pid) { 102 goto re_connect; /* 重新连接 */ 103 } 104 105 len = read(STDIN_FILENO, buf, sizeof(buf)); 106 if (len > 0) { 107 send(confd, buf, strlen(buf), 0); 108 memset(buf, 0, sizeof(buf)); 109 } 110 } 111 } else if (pid == 0) { /* 子进程,接收数据 */ 112 while (1) { 113 memset(buf, 0, sizeof(buf)); 114 len = recv(confd, buf, sizeof(buf), 0); 115 if (len == 0) { /* 如果recv返回0,则表示远端断开连接 */ 116 break; 117 } 118 printf("len = %d\n", len); 119 printf("data: %s\n", buf); 120 } 121 } 122 }
通过ps -au命令查看,当客户端连接时存在两个进程,当客户端断开连接时,存在1个进程,不会再出现每次断开重连都会多一个进程的现象。而且断开重连短时间内无法发送数据的问题也顺带解决了。
3、修改TCP Client代码
根据TCP Server相同的思路修改代码,不同之处是在TCP Client的代码中如果检测到服务器断开连接,那么程序直接退出,不重连。修改后代码如下:
1 /** 2 * filename: tcp_client.c 3 * author: Suzkfly 4 * date: 2021-01-22 5 * platform: Ubuntu 6 * 配合windows的网络调试工具使用: 7 * 1、先保证windows与Ubuntu在同一网段且互相能ping通; 8 * 2、在windows下打开网络调试助手,选择协议类型为TCP Server,本地主机地址选 9 * 择windows的IP地址(或者windows下能和Ubuntu ping通的地址),端口号和 10 * 本文件中传入的端口号一致,接收设置和发送设置都选择ASCLL。 11 * 3、点击“打开”按钮。 12 * 4、运行Ubuntu下的TCP客户端程序; 13 * 5、连接成功后在网络调试助手上发送数据,在Ubuntu下的终端上能看到, 14 * 在Ubuntu下的终端上输入字符串按回车发送,在windows上的网络调试助手上也 15 * 能看到。 16 */ 17 #include <stdio.h> 18 #include <sys/types.h> 19 #include <sys/socket.h> 20 #include <string.h> 21 #include <netinet/in.h> 22 #include <arpa/inet.h> 23 #include <wait.h> 24 #include <fcntl.h> 25 #include <unistd.h> 26 27 #define IP_ADDR "192.168.0.1" /* 服务器IP地址 */ 28 #define PORT 24576 /* 服务器端口号 */ 29 30 31 int main(int argc, const char *argv[]) 32 { 33 int sock_fd = 0; 34 int ret = 0; 35 struct sockaddr_in serv_addr; /* 服务器地址 */ 36 int pid = 0; 37 char buf[128] = { 0 }; 38 int len = 0; 39 int attr = 0; 40 int wait_ret = 0; 41 42 /* 创建TCP套接字 */ 43 sock_fd = socket(AF_INET, SOCK_STREAM, 0); 44 if (sock_fd < 0) { 45 printf("socket failed\n"); 46 return 0; 47 } 48 49 /* 与服务器建立连接 */ 50 memset(&serv_addr, 0, sizeof(struct sockaddr_in)); 51 serv_addr.sin_family = AF_INET; 52 serv_addr.sin_addr.s_addr = inet_addr(IP_ADDR); /* 服务器IP */ 53 serv_addr.sin_port = htons(PORT); /* 服务器端口号 */ 54 ret = connect(sock_fd, 55 (struct sockaddr *)&serv_addr, 56 sizeof(struct sockaddr_in)); 57 if (ret == 0) { 58 printf("connect ok\n"); 59 } else { 60 printf("connect failed\n"); 61 close(sock_fd); 62 return 0; 63 } 64 65 pid = fork(); 66 67 if (pid > 0) { 68 attr = fcntl(STDIN_FILENO, F_GETFL); 69 attr |= O_NONBLOCK; 70 fcntl(STDIN_FILENO, F_SETFL, attr); 71 72 while (1) { /* 父进程,发送数据 */ 73 wait_ret = waitpid(-1, NULL, WNOHANG); /* 用非阻塞的方式等待子进程退出 */ 74 if (wait_ret == pid) { 75 break; 76 } 77 78 len = read(STDIN_FILENO, buf, sizeof(buf)); 79 if (len > 0) { 80 send(sock_fd, buf, strlen(buf), 0); 81 memset(buf, 0, sizeof(buf)); 82 } 83 } 84 } else if (pid == 0) { /* 子进程,接收数据 */ 85 while (1) { 86 memset(buf, 0, sizeof(buf)); 87 len = recv(sock_fd, buf, sizeof(buf), 0); 88 89 if (len == 0) { /* 如果recv返回0,则表示服务器关闭 */ 90 break; 91 } 92 93 printf("len = %d\n", len); 94 printf("data: %s\n", buf); 95 } 96 } 97 }