zoukankan      html  css  js  c++  java
  • 六十、linux 编程—— I/O 多路复用 select

    60.1 介绍

      

      

      

      

      

    60.2 例子

      

      echo_tcp_server_select.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 <fcntl.h>
     12 #include <time.h>
     13 #include <arpa/inet.h>
     14 #include <errno.h>
     15 #include <pthread.h>
     16 #include "vector_fd.h"
     17 
     18 vector_fd *vfd;
     19 int sockfd;
     20 
     21 void sig_handler(int signo)
     22 {
     23     if(signo == SIGINT){
     24         printf("server close
    ");
     25         /** 步骤6: 关闭 socket */
     26         close(sockfd);
     27         /** 销毁动态数组 */
     28         destroy_vector_fd(vfd);
     29         exit(1);
     30     }
     31 }
     32 
     33 /**
     34  * fd 对应于某个连接的客户端,和某一个连接的客户端进行双向通信(非阻塞方式)
     35  */
     36 void do_service(int fd)
     37 {
     38     char buff[512];
     39     memset(buff, 0, sizeof(buff));
     40 
     41     /**
     42      *  因为采用非阻塞方式,若读不到数据直接返回,
     43      *  直接服务于下一个客户端,
     44      *  因此不需要判断 size < 0 的情况 */
     45     ssize_t size = read(fd, buff, sizeof(buff));
     46 
     47     if(size == 0){
     48         /** 客户端已经关闭连接 */
     49         printf("client closed
    ");
     50         /** 从动态数组中删除对应的 fd */
     51         remove_fd(vfd, fd);
     52         /** 关闭对应客户端的 socket */
     53         close(fd);
     54     }
     55     else if(size > 0){
     56         printf("%s
    ", buff);
     57         if(write(fd, buff, size) < 0){
     58             if(errno == EPIPE){
     59                 /** 客户端关闭连接 */
     60                 perror("write error");
     61                 remove_fd(vfd, fd);
     62                 close(fd);
     63             }
     64             perror("protocal error");
     65         }
     66     }
     67 }
     68 
     69 void out_addr(struct sockaddr_in *clientaddr)
     70 {
     71     char ip[16];
     72     memset(ip, 0, sizeof(ip));
     73     int port = ntohs(clientaddr->sin_port);
     74     inet_ntop(AF_INET, &clientaddr->sin_addr.s_addr, ip, sizeof(ip));
     75     printf("%s(%d) connected!
    ", ip, port);
     76 }
     77 
     78 /** 遍历出动态数组中所有的描述符并加入到描述符集 set
     79  * 中,同时此函数返回动态数组中最大的那个描述符 */
     80 int add_set(fd_set *set)
     81 {
     82     FD_ZERO(set);   ///< 清空描述符集
     83     int max_fd = vfd->fd[0];
     84     int i = 0;
     85     for(; i < vfd->counter; i++){
     86         int fd = get_fd(vfd, i);
     87         if(fd > max_fd) max_fd = fd;
     88         FD_SET(fd, set);    ///< 将 fd 加入到描述符集中
     89     }
     90 
     91     return max_fd;
     92 }
     93 
     94 void *th_fn(void *arg)
     95 {
     96     /** 设置超时时间 2s */
     97     struct timeval t;
     98     t.tv_sec = 2;
     99     t.tv_usec = 0;
    100 
    101     int n = 0;
    102     int maxfd;
    103     fd_set set; ///< 描述符集
    104     maxfd = add_set(&set);
    105 
    106     /**
    107      * 调用 select 函数会阻塞,委托内核去检查传入的描述符是否准备好,
    108      * 若有则返回准备好的描述符;超时则返回 0
    109      * 第一个参数为描述符集中的描述符的范围(最大描述符 + 1)
    110      */
    111     while((n = select(maxfd + 1, &set, NULL, NULL, &t)) >= 0){
    112         /** 检测哪些描述符准备好, 并和这些准备好的描述符对应的客户端进行数据的双向通信 */
    113         if(n > 0){
    114             int i = 0;
    115             for(; i < vfd->counter; i++){
    116                 int fd = get_fd(vfd, i);
    117                 if(FD_ISSET(fd, &set)){
    118                     do_service(fd);
    119                 }
    120             }
    121         }
    122         /** 重新设置时间和清空描述符集 */
    123         t.tv_sec = 2;
    124         t.tv_usec = 0;
    125         /** 重新遍历动态数组中最新的描述符放置到描述符集中 */
    126         maxfd = add_set(&set);
    127     }
    128 
    129     return (void *)0;
    130 }
    131 
    132 int main(int argc, char *argv[])
    133 {
    134     if(argc < 2){
    135         printf("usage: %s #port
    ", argv[0]);
    136         exit(1);
    137     }
    138 
    139     if(signal(SIGINT, sig_handler) == SIG_ERR){
    140         perror("signal sigint error");
    141         exit(1);
    142     }
    143 
    144 
    145     /** 步骤1: 创建 socket(套接字)
    146      *  注: socket 创建在内核中,是一个结构体.
    147      *  AF_INET: IPV4
    148      *  SOCK_STREAM: tcp 协议
    149      *  AF_INET6: IPV6
    150      */
    151     sockfd = socket(AF_INET, SOCK_STREAM, 0);
    152     if(sockfd < 0){
    153         perror("socket error");
    154         exit(1);
    155     }
    156 
    157     /**
    158      * 步骤2: 调用 bind 函数将 socket 和地址(包括 ip、port)进行绑定
    159      */
    160     struct sockaddr_in  serveraddr;
    161     memset(&serveraddr, 0, sizeof(struct sockaddr_in));
    162     /** 往地址中填入 ip、port、internet 地址族类型 */
    163     serveraddr.sin_family = AF_INET;    ///< IPV4
    164     serveraddr.sin_port = htons(atoi(argv[1])); ///< 填入端口
    165     serveraddr.sin_addr.s_addr = INADDR_ANY; ///< 填入 IP 地址
    166     if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in))){
    167         perror("bind error");
    168         exit(1);
    169     }
    170 
    171     /**
    172      *  步骤3: 调用 listen 函数启动监听(指定 port 监听)
    173      *         通知系统去接受来自客户端的连接请求
    174      *         (将接受到的客户端连接请求放置到对应的队列中)
    175      *  第二个参数: 指定队列的长度
    176      */
    177     if(listen(sockfd, 10) < 0){
    178         perror("listen error");
    179         exit(1);
    180     }
    181 
    182     /** 创建放置套接字描述符 fd 的动态数组 */
    183     vfd = create_vector_fd();
    184 
    185     /** 设置线程的分离属性 */
    186     pthread_t th;
    187     pthread_attr_t attr;
    188     pthread_attr_init(&attr);
    189     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    190     int err;
    191     if((err = pthread_create(&th, &attr, th_fn, (void *)0)) != 0){
    192         perror("pthread create error");
    193         exit(1);
    194     }
    195     pthread_attr_destroy(&attr);
    196 
    197     /**
    198      * 1)主控线程获得客户端的链接,将新的 socket 描述符放置到动态数组中
    199      * 2) (a)启动的子线程调用 select 函数委托内核去检查传入到 select
    200      *       中的描述符是否准备好.
    201      *    (b)利用 FD_ISSET 来找出准备好的那些描述符,
    202      *       并和对应的客户端进行双向通信(非阻塞)
    203      */
    204     struct sockaddr_in clientaddr;
    205     socklen_t len = sizeof(clientaddr);
    206 
    207     while(1){
    208         /**
    209          *  步骤4: 调用 accept 函数从队列中获得一个客户端的请求连接, 并返回新的
    210          *         socket 描述符
    211          *  注意:  若没有客户端连接,调用此函数后会阻塞, 直到获得一个客户端的连接
    212          */
    213         /** 主控线程负责调用 accept 去获得客户端的连接 */
    214         int fd = accept(sockfd, (struct sockaddr *)&clientaddr, &len);
    215         if(fd < 0){
    216             perror("accept error");
    217             continue;
    218         }
    219 
    220         out_addr(&clientaddr);
    221 
    222         /** 将返回的新的 socket 描述符加入到动态数组中 */
    223         add_fd(vfd, fd);
    224     }
    225 
    226     return 0;
    227 }

      编译运行测试:

      

  • 相关阅读:
    《C++标准程序库》 第6章 STL Container
    《C++语言99个常见编程错误》
    单例模式
    《C++标准程序库》 第7章 Iterator Adapters
    Shell颜色封装(C++)
    《改善C++程序的150个建议》
    OpenCV之图片的创建、保存和复制
    XMLDOM对象方法:对象事件
    三国中最精辟的十句话
    中国十大名茶及鉴别方法
  • 原文地址:https://www.cnblogs.com/kele-dad/p/10526857.html
Copyright © 2011-2022 走看看