五十五、linux 编程——TCP 连接和关闭过程及服务器的并发处理

时间:2021-04-30 23:49:27

55.1 TCP 连接和关闭过程

55.1.1 介绍

  五十五、linux 编程——TCP 连接和关闭过程及服务器的并发处理

  建立连接的过程就是三次握手的过程:客户端发送 SYN 报文给服务器,服务器回复 SYN+ACK 报文,客户机再发送 ACK 报文。

  关闭连接的过程:客户机先发送 FIN 报文,服务器回复 ACK 报文,服务器再发送 FIN 报文,客户机再发送响应报文 ACK。

55.1.2  自定义协议编程例子 

  msg.h

 1 #ifndef __MSG_H__
 2 #define __MSG_H__
 3 
 4 #include <sys/types.h>
 5 
 6 typedef struct {
 7     /** 协议头部: 不传输任何数据,只包含发送端的一些信息 */
 8     char head[10];      ///< 协议头部
 9     char checknum;      ///< 校验码
10 
11     /**协议体部 */
12     char buff[512];     ///< 数据
13 }Msg;
14 
15 /** 发送一个基于自定义协议的 message,发送的数据存放在 buff 中 */
16 extern int write_msg(int sockfd, char *buff, ssize_t len);
17 
18 /** 读取一个基于自定义协议的 message, 读取的数据存放在 buff 中 */
19 extern int read_msg(int sockfd, char *buff, ssize_t len);
20 
21 #endif

  msg.c

 1 #include "msg.h"
 2 #include <unistd.h>
 3 #include <string.h>
 4 #include <memory.h>
 5 #include <sys/types.h>
 6 
 7 
 8 /** 计算校验码 */
 9 static unsigned char msg_check(Msg *message)
10 {
11     unsigned char s = 0;
12     int i;
13     for(i = 0; i < sizeof(message->head); i++){
14         s += message->head[i];
15     }
16 
17     for(i = 0; i < sizeof(message->buff); i++){
18         s += message->buff[i];
19     }
20 
21     return s;
22 }
23 
24 
25 /** 发送一个基于自定义协议的 message,发送的数据存放在 buff 中 */
26 int write_msg(int sockfd, char *buff, ssize_t len)
27 {
28     Msg message;
29     memset(&message, 0, sizeof(message));
30     strcpy(message.head, "hello");
31     memcpy(message.buff, buff, len);
32     message.checknum = msg_check(&message);
33 
34     if(write(sockfd, &message, sizeof(message)) != sizeof(message)){
35         return -1;
36     }
37 
38     return 0;
39 }
40 
41 /** 读取一个基于自定义协议的 message, 读取的数据存放在 buff 中 */
42 int read_msg(int sockfd, char *buff, ssize_t len)
43 {
44     Msg message;
45     memset(&message, 0, sizeof(message));
46 
47     ssize_t size;
48     if((size = read(sockfd, &message, sizeof(message))) < 0){
49         return -1;
50     }
51     else if(size == 0){
52         return 0;
53     }
54 
55     /** 进行校验码验证,判断接收到的 message 是否完整 */
56     unsigned char s = msg_check(&message);
57     if((s == (unsigned char)message.checknum) && (!strcmp("hello", message.head))){
58         memcpy(buff, message.buff, len);
59         return sizeof(message);
60     }
61     return -1;
62 
63 }

  编译成 .o 文件:gcc -o obj/msg.o -Iinclude -c src/msg.c

55.2 服务器的并发过程

55.2.1 介绍

  一个服务器处理多个客户端的请求,就称为服务器的并发。

  • 服务器端并发性处理
    • 多进程模型
    • 多线程模型
    • I/O多路转换(select)

  五十五、linux 编程——TCP 连接和关闭过程及服务器的并发处理

55.2.2  基于自定义协议的多进程模型编程

(1)服务器代码

  echo_tcp_server.c

  1 #include <netdb.h>
  2 #include <netinet/in.h>
  3 #include <sys/socket.h>
  4 #include <sys/wait.h>
  5 #include <unistd.h>
  6 #include <string.h>
  7 #include <stdio.h>
  8 #include <stdlib.h>
  9 #include <memory.h>
 10 #include <signal.h>
 11 #include <time.h>
 12 #include <arpa/inet.h>
 13 #include <errno.h>
 14 #include "msg.h"
 15 
 16 
 17 int sockfd;
 18 
 19 void sig_handler(int signo)
 20 {
 21     if(signo == SIGINT){
 22         printf("server close\n");
 23         /** 步骤6: 关闭 socket */
 24         close(sockfd);
 25         exit(1);
 26     }
 27 
 28     if(signo == SIGINT){
 29         printf("child process deaded...\n");
 30         wait(0);
 31     }
 32 }
 33 
 34 /** 输出连接上来的客户端相关信息 */
 35 void out_addr(struct sockaddr_in *clientaddr)
 36 {
 37     /** 将端口从网络字节序转换成主机字节序 */
 38     int port = ntohs(clientaddr->sin_port);
 39     char ip[16];
 40     memset(ip, 0, sizeof(ip));
 41     /** 将 ip 地址从网络字节序转换成点分十进制 */
 42     inet_ntop(AF_INET, &clientaddr->sin_addr.s_addr, ip, sizeof(ip));
 43     printf("client: %s(%d) connected\n", ip, port);
 44 }
 45 
 46 void do_service(int fd)
 47 {
 48     /** 和客户端进行读写操作(双向通信) */
 49     char buff[512];
 50     while(1){
 51         memset(buff, 0, sizeof(buff));
 52         printf("start read and write....\n");
 53         ssize_t size;
 54         if((size = read_msg(fd, buff, sizeof(buff))) < 0){
 55             perror("protocal error");
 56             break;
 57         }
 58         else if(size == 0){
 59             break;
 60         }
 61         else {
 62             printf("%s\n", buff);
 63             if(write_msg(fd, buff, sizeof(buff)) < 0){
 64                 if(errno == EPIPE){
 65                     break;
 66                 }
 67                 perror("protocal error");
 68             }
 69         }
 70     }
 71 }
 72 
 73 int main(int argc, char *argv[])
 74 {
 75     if(argc < 2){
 76         printf("usage: %s #port\n", argv[0]);
 77         exit(1);
 78     }
 79 
 80     if(signal(SIGINT, sig_handler) == SIG_ERR){
 81         perror("signal sigint error");
 82         exit(1);
 83     }
 84 
 85     if(signal(SIGCHLD, sig_handler) == SIG_ERR){
 86         perror("signal sigchld error");
 87         exit(1);
 88     }
 89 
 90     /** 步骤1: 创建 socket(套接字) 
 91      *  注: socket 创建在内核中,是一个结构体.
 92      *  AF_INET: IPV4
 93      *  SOCK_STREAM: tcp 协议
 94      *  AF_INET6: IPV6
 95      */
 96     sockfd = socket(AF_INET, SOCK_STREAM, 0);
 97     if(sockfd < 0){
 98         perror("socket error");
 99         exit(1);
100     }
101 
102     /** 
103      * 步骤2: 调用 bind 函数将 socket 和地址(包括 ip、port)进行绑定
104      */
105     struct sockaddr_in  serveraddr;
106     memset(&serveraddr, 0, sizeof(struct sockaddr_in));
107     /** 往地址中填入 ip、port、internet 地址族类型 */
108     serveraddr.sin_family = AF_INET;    ///< IPV4
109     serveraddr.sin_port = htons(atoi(argv[1])); ///< 填入端口
110     serveraddr.sin_addr.s_addr = INADDR_ANY; ///< 填入 IP 地址
111     if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in))){
112         perror("bind error");
113         exit(1);
114     }
115 
116     /**
117      *  步骤3: 调用 listen 函数启动监听(指定 port 监听)
118      *         通知系统去接受来自客户端的连接请求
119      *         (将接受到的客户端连接请求放置到对应的队列中)
120      *  第二个参数: 指定队列的长度
121      */
122     if(listen(sockfd, 10) < 0){
123         perror("listen error");
124         exit(1);
125     }
126 
127     /**
128      *  步骤4: 调用 accept 函数从队列中获得一个客户端的请求连接, 并返回新的
129      *         socket 描述符
130      *  注意:  若没有客户端连接,调用此函数后会足则, 直到获得一个客户端的连接
131      */
132     struct sockaddr_in clientaddr;
133     socklen_t clientaddr_len = sizeof(clientaddr);
134     while(1){
135         int fd = accept(sockfd, (struct sockaddr *)&clientaddr, &clientaddr_len);
136         if(fd < 0){
137             perror("accept error");
138             continue;
139         }
140 
141         /**
142          *  步骤5: 启动子进程去调用 IO 函数(read/write)和连接的客户端进行双向的通信
143          */
144         pid_t pid = fork();
145         if(pid < 0){
146             continue;
147         }
148         else if(pid == 0){
149             /** 子进程 */
150             out_addr(&clientaddr);
151             do_service(fd);
152             /** 步骤6: 关闭 socket */
153             close(fd);
154             break;
155         }
156         else{
157             /** 父进程 */
158             /** 步骤6: 关闭 socket */
159             close(fd);
160         }
161     }
162 
163     return 0;
164 }

  gcc -o bin/echo_tcp_server -Iinclude obj/msg.o src/echo_tcp_server.c 

(2)客户端代码

  echo_tcp_client.c

 1 #include <sys/types.h>
 2 #include <stdlib.h>
 3 #include <stdio.h>
 4 #include <memory.h>
 5 #include <unistd.h>
 6 #include <sys/socket.h>
 7 #include <netdb.h>
 8 #include <signal.h>
 9 #include <string.h>
10 #include <time.h>
11 #include <arpa/inet.h>
12 #include "msg.h"
13 
14 
15 int main(int argc, char *argv[])
16 {
17     if(argc < 3){
18         printf("usage: %s ip port\n", argv[0]);
19         exit(1);
20     }
21 
22     /** 步骤1: 创建 socket */
23     int sockfd = socket(AF_INET, SOCK_STREAM, 0);
24     if(sockfd < 0){
25         perror("socket error");
26         exit(1);
27     }
28 
29     /** 往 serveraddr 中填入 ip、port 和地址族类型(ipv4) */
30     struct sockaddr_in serveraddr;
31     memset(&serveraddr, 0, sizeof(struct sockaddr_in));
32     serveraddr.sin_family = AF_INET;
33     serveraddr.sin_port = htons(atoi(argv[2]));
34     /** 将 ip 地址转换成网络字节序后填入 serveraddr 中  */
35     inet_pton(AF_INET, argv[1], &serveraddr.sin_addr.s_addr);
36 
37     /**
38      *  步骤2: 客户端调用 connect 函数连接到服务器端
39      */
40     if(connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in)) < 0){
41         perror("connect error");
42         exit(1);
43     }
44 
45     /** 步骤3: 调用 IO 函数(read/write)和服务器端进行双向通信 */
46     char buff[512];
47     ssize_t size;
48     char *prompt = "==>";
49     while(1){
50         memset(buff, 0, sizeof(buff));
51         write(STDOUT_FILENO, prompt, 3);
52         size = read(STDIN_FILENO, buff, sizeof(buff));
53         if(size < 0) continue;
54         buff[size - 1] = '\0';
55 
56         if(write_msg(sockfd, buff, sizeof(buff)) < 0){
57             perror("write msg error");
58             continue;
59         }
60         else {
61             if(read_msg(sockfd, buff, sizeof(buff)) < 0){
62                 perror("read msg error");
63                 continue;
64             }
65             else {
66                 printf("%s\n", buff);
67             }
68         }
69     }
70 
71     /** 步骤4: 关闭 socket */
72     close(sockfd);
73 
74     return 0;
75 }

  gcc -o bin/echo_tcp_client -Iinclude obj/msg.o src/echo_tcp_client.c

(3)测试

  开启两个终端进行测试,一个运行服务器,一个运行客户端:

  五十五、linux 编程——TCP 连接和关闭过程及服务器的并发处理

  五十五、linux 编程——TCP 连接和关闭过程及服务器的并发处理

  五十五、linux 编程——TCP 连接和关闭过程及服务器的并发处理

55.2.3  基于自定义协议的多线程模型编程

  五十五、linux 编程——TCP 连接和关闭过程及服务器的并发处理

  echo_tcp_server_th.c

  1 #include <netdb.h>
  2 #include <netinet/in.h>
  3 #include <sys/socket.h>
  4 #include <sys/wait.h>
  5 #include <unistd.h>
  6 #include <string.h>
  7 #include <stdio.h>
  8 #include <stdlib.h>
  9 #include <memory.h>
 10 #include <signal.h>
 11 #include <time.h>
 12 #include <arpa/inet.h>
 13 #include <errno.h>
 14 #include "msg.h"
 15 #include <pthread.h>
 16 
 17 
 18 int sockfd;
 19 
 20 void sig_handler(int signo)
 21 {
 22     if(signo == SIGINT){
 23         printf("server close\n");
 24         /** 步骤6: 关闭 socket */
 25         close(sockfd);
 26         exit(1);
 27     }
 28 }
 29 
 30 void do_service(int fd)
 31 {
 32     /** 和客户端进行读写操作(双向通信) */
 33     char buff[512];
 34     while(1){
 35         memset(buff, 0, sizeof(buff));
 36         ssize_t size;
 37         if((size = read_msg(fd, buff, sizeof(buff))) < 0){
 38             perror("protocal error");
 39             break;
 40         }
 41         else if(size == 0){
 42             break;
 43         }
 44         else {
 45             printf("%s\n", buff);
 46             if(write_msg(fd, buff, sizeof(buff)) < 0){
 47                 if(errno == EPIPE){
 48                     break;
 49                 }
 50                 perror("protocal error");
 51             }
 52         }
 53     }
 54 }
 55 
 56 
 57 void out_fd(int fd)
 58 {
 59     struct sockaddr_in   addr;
 60     socklen_t len = sizeof(addr);
 61     /** 从 fd 中获得连接的客户端相关信息并放置到 sockaddr_in 当中 */
 62     if(getpeername(fd, (struct sockaddr *)&addr, &len) < 0){
 63         perror("getpeername error");
 64         return;
 65     }
 66 
 67     char ip[16];
 68     memset(ip, 0, sizeof(ip));
 69     int port = ntohs(addr.sin_port);
 70     inet_ntop(AF_INET, &addr.sin_addr.s_addr, ip, sizeof(ip));
 71     printf("%16s(%5d) closed!\n", ip, port);
 72 }
 73 
 74 void *th_fn(void *arg)
 75 {
 76     int fd = (int)arg;
 77 
 78     do_service(fd);
 79     out_fd(fd);
 80     close(fd);
 81     return (void *)0;
 82 }
 83 
 84 int main(int argc, char *argv[])
 85 {
 86     if(argc < 2){
 87         printf("usage: %s #port\n", argv[0]);
 88         exit(1);
 89     }
 90 
 91     if(signal(SIGINT, sig_handler) == SIG_ERR){
 92         perror("signal sigint error");
 93         exit(1);
 94     }
 95 
 96 
 97     /** 步骤1: 创建 socket(套接字) 
 98      *  注: socket 创建在内核中,是一个结构体.
 99      *  AF_INET: IPV4
100      *  SOCK_STREAM: tcp 协议
101      *  AF_INET6: IPV6
102      */
103     sockfd = socket(AF_INET, SOCK_STREAM, 0);
104     if(sockfd < 0){
105         perror("socket error");
106         exit(1);
107     }
108 
109     /** 
110      * 步骤2: 调用 bind 函数将 socket 和地址(包括 ip、port)进行绑定
111      */
112     struct sockaddr_in  serveraddr;
113     memset(&serveraddr, 0, sizeof(struct sockaddr_in));
114     /** 往地址中填入 ip、port、internet 地址族类型 */
115     serveraddr.sin_family = AF_INET;    ///< IPV4
116     serveraddr.sin_port = htons(atoi(argv[1])); ///< 填入端口
117     serveraddr.sin_addr.s_addr = INADDR_ANY; ///< 填入 IP 地址
118     if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in))){
119         perror("bind error");
120         exit(1);
121     }
122 
123     /**
124      *  步骤3: 调用 listen 函数启动监听(指定 port 监听)
125      *         通知系统去接受来自客户端的连接请求
126      *         (将接受到的客户端连接请求放置到对应的队列中)
127      *  第二个参数: 指定队列的长度
128      */
129     if(listen(sockfd, 10) < 0){
130         perror("listen error");
131         exit(1);
132     }
133 
134     /**
135      *  步骤4: 调用 accept 函数从队列中获得一个客户端的请求连接, 并返回新的
136      *         socket 描述符
137      *  注意:  若没有客户端连接,调用此函数后会足则, 直到获得一个客户端的连接
138      */
139 
140     /** 设置线程的分离属性 */
141     pthread_attr_t attr;
142     pthread_attr_init(&attr);
143     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
144 
145     while(1){
146         /** 主控线程负责调用 accept 去获得客户端的连接 */
147         int fd = accept(sockfd, NULL, NULL);
148         if(fd < 0){
149             perror("accept error");
150             continue;
151         }
152 
153         /**
154          *  步骤5: 启动子线程去调用 IO 函数(read/write)和连接的客户端进行双向的通信
155          */
156         pthread_t th;
157         int err;
158         /** 以分离状态启动子线程 */
159         if((err = pthread_create(&th, &attr, th_fn, (void *)fd)) != 0){
160             perror("pthread create error");
161         }
162 
163         pthread_attr_destroy(&attr);
164 
165     }
166 
167     return 0;
168 }

  客户端程序用上面的客户端程序即可。