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


    55.1 TCP 连接和关闭过程

    55.1.1 介绍

      

      建立连接的过程就是三次握手的过程:客户端发送 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)

      

    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
    ");
     23         /** 步骤6: 关闭 socket */
     24         close(sockfd);
     25         exit(1);
     26     }
     27 
     28     if(signo == SIGINT){
     29         printf("child process deaded...
    ");
     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
    ", 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....
    ");
     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
    ", 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
    ", 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
    ", 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] = '';
    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
    ", 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)测试

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

      

      

      

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

      

      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
    ");
     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
    ", 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!
    ", 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
    ", 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 }

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

  • 相关阅读:
    列举⼀下 HTTP 中关于 "资源缓存" 的头部指令 (Head) 有哪些 ? 并简要介绍⼀下设置的规则 ?
    从输入URL到页面渲染完成
    git删除远程仓库分支
    @font-face的format属性
    【React】的行内样式不支持rgb
    前端面试题
    常见元素居中的五种方法
    数组存储表格数据
    java.util.Arrays类
    for-each循环
  • 原文地址:https://www.cnblogs.com/kele-dad/p/10421407.html
Copyright © 2020-2023  润新知