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 }