• socket编程的同步、异步与阻塞、非阻塞示例详解


    分类: 架构设计与优化

    简介
    图 1. 基本 Linux I/O 模型的简单矩阵
    基本 Linux I/O 模型的简单矩阵 
    每个 I/O 模型都有自己的使用模式,它们对于特定的应用程序都有自己的优点。
    本节将简要对其一一进行介绍。

    一、同步阻塞模式
    在这个模式中,用户空间的应用程序执行一个系统调用,并阻塞,直到系统调用完成为止(数据传输完成或发生错误)。
    /*
     * rief
     * tcp client
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/socket.h>
    #include <netdb.h>
    #include <string.h>
    #define SERVPORT 8080
    #define MAXDATASIZE 100
    
    int main(int argc, char *argv[])
    {
      int sockfd, recvbytes;
      char rcv_buf[MAXDATASIZE]; /*./client 127.0.0.1 hello */
      char snd_buf[MAXDATASIZE];
      struct hostent *host;             /* struct hostent
                                         * {
                                         * char *h_name; // general hostname
                                         * char **h_aliases; // hostname's alias
                                         * int h_addrtype; // AF_INET
                                         * int h_length; 
                                         * char **h_addr_list;
                                         * };
                                         */
      struct sockaddr_in server_addr;
    
      if (argc < 3)
      {
        printf("Usage:%s [ip address] [any string]
    ", argv[0]);
        return 1;
      }
    
      *snd_buf = '';
      strcat(snd_buf, argv[2]);
    
      if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
      {
        perror("socket:");
        exit(1);
      }
    
      server_addr.sin_family = AF_INET;
      server_addr.sin_port = htons(SERVPORT);
      inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
      memset(&(server_addr.sin_zero), 0, 8);
    
      /* create the connection by socket 
       * means that connect "sockfd" to "server_addr"
       * 同步阻塞模式 
       */
      if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
      {
        perror("connect");
        exit(1);
      }
    
      /* 同步阻塞模式  */
      if (send(sockfd, snd_buf, sizeof(snd_buf), 0) == -1)
      {
        perror("send:");
        exit(1);
      }
      printf("send:%s
    ", snd_buf);
    
       /* 同步阻塞模式  */
      if ((recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, 0)) == -1)
      {
        perror("recv:");
        exit(1);
      }
    
      rcv_buf[recvbytes] = '';
      printf("recv:%s
    ", rcv_buf);
    
      close(sockfd);
      return 0;
    }


    显然,代码中的connect, send, recv都是同步阻塞工作模式,
    在结果没有返回时,程序什么也不做,

    二、同步非阻塞模式
    同步阻塞 I/O 的一种效率稍低的变种是同步非阻塞 I/O。
    在这种模型中,系统调用是以非阻塞的形式打开的。
    这意味着 I/O 操作不会立即完成, 操作可能会返回一个错误代码,
    说明这个命令不能立即满足(EAGAIN 或 EWOULDBLOCK),
    非阻塞的实现是 I/O 命令可能并不会立即满足,需要应用程序调用许多次来等待操作完成。
    这可能效率不高,
    因为在很多情况下,当内核执行这个命令时,应用程序必须要进行忙碌等待,直到数据可用为止,
    或者试图执行其他工作。
    因为数据在内核中变为可用到用户调用 read 返回数据之间存在一定的间隔,这会导致整体数据吞吐量的降低。
    /*
     * rief
     * tcp client
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <errno.h>
    #include <netdb.h>
    #include <string.h>
    #include <unistd.h>
    #include <fcntl.h>
    
    #define SERVPORT 8080
    #define MAXDATASIZE 100
    
    
    int main(int argc, char *argv[])
    {
      int sockfd, recvbytes;
      char rcv_buf[MAXDATASIZE]; /*./client 127.0.0.1 hello */
      char snd_buf[MAXDATASIZE];
      struct hostent *host;             /* struct hostent
                                         * {
                                         * char *h_name; // general hostname
                                         * char **h_aliases; // hostname's alias
                                         * int h_addrtype; // AF_INET
                                         * int h_length; 
                                         * char **h_addr_list;
                                         * };
                                         */
      struct sockaddr_in server_addr;
      int flags;
      int addr_len;
    
      if (argc < 3)
      {
        printf("Usage:%s [ip address] [any string]
    ", argv[0]);
        return 1;
      }
    
      *snd_buf = '';
      strcat(snd_buf, argv[2]);
    
      if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
      {
        perror("socket:");
        exit(1);
      }
    
      server_addr.sin_family = AF_INET;
      server_addr.sin_port = htons(SERVPORT);
      inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
      memset(&(server_addr.sin_zero), 0, 8);
      addr_len = sizeof(struct sockaddr_in);
    
      /* Setting socket to nonblock */
      flags = fcntl(sockfd, F_GETFL, 0);
      fcntl(sockfd, flags|O_NONBLOCK);
    
      /* create the connection by socket 
       * means that connect "sockfd" to "server_addr"
       * 同步阻塞模式  
      */
      if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
      {
        perror("connect");
        exit(1);
      }
    
      /* 同步非阻塞模式 */
      while (send(sockfd, snd_buf, sizeof(snd_buf), MSG_DONTWAIT) == -1)
      {
        sleep(1);
        printf("sleep
    ");
      }
      printf("send:%s
    ", snd_buf);
    
    
      /* 同步非阻塞模式 */
      while ((recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, MSG_DONTWAIT)) == -1)
      {
        sleep(1);
        printf("sleep
    ");
      }
    
      rcv_buf[recvbytes] = '';
      printf("recv:%s
    ", rcv_buf);
    
      close(sockfd);
      return 0;
    }

    异步阻塞模式,异步非阻塞模式以及server端程序见本文的第二部分。
    http://blog.chinaunix.net/uid-26000296-id-3755268.html
     
      1 三、异步阻塞模式
      2 另外一个阻塞解决方案是带有阻塞通知的非阻塞 I/O。
      3 在这种模型中,配置的是非阻塞 I/O,然后使用阻塞 select 系统调用来确定一个 I/O 描述符何时有操作。
      4 使 select 调用非常有趣的是它可以用来为多个描述符提供通知,而不仅仅为一个描述符提供通知。
      5 对于每个提示符来说,我们可以请求这个描述符可以写数据、有读数据可用以及是否发生错误的通知
      6 
      7 下面的C语言实现的例子,它从网络上接受数据写入一个文件中:
      8 /*
      9  * rief
     10  * tcp client
     11  */
     12 
     13 #include <stdio.h>
     14 #include <stdlib.h>
     15 #include <sys/socket.h>
     16 #include <sys/select.h>
     17 #include <sys/time.h>
     18 #include <netdb.h>
     19 #include <string.h>
     20 
     21 #include <sys/types.h>
     22 #include <sys/stat.h>
     23 #include <fcntl.h>
     24 #define SERVPORT 8080
     25 #define MAXDATASIZE 100
     26 #define TFILE "data_from_socket.txt"
     27 
     28 
     29 int main(int argc, char *argv[])
     30 {
     31   int sockfd, recvbytes;
     32   char rcv_buf[MAXDATASIZE]; /*./client 127.0.0.1 hello */
     33   char snd_buf[MAXDATASIZE];
     34   struct hostent *host;             /* struct hostent
     35                                      * {
     36                                      * char *h_name; // general hostname
     37                                      * char **h_aliases; // hostname's alias
     38                                      * int h_addrtype; // AF_INET
     39                                      * int h_length; 
     40                                      * char **h_addr_list;
     41                                      * };
     42                                      */
     43   struct sockaddr_in server_addr;
     44 
     45 
     46   /* */
     47   fd_set readset, writeset;
     48   int check_timeval = 1;
     49   struct timeval timeout={check_timeval,0}; //阻塞式select, 等待1秒,1秒轮询
     50   int maxfd;
     51   int fp;
     52   int cir_count = 0;
     53   int ret;
     54 
     55 
     56   if (argc < 3)
     57   {
     58     printf("Usage:%s [ip address] [any string]
    ", argv[0]);
     59     return 1;
     60   }
     61 
     62 
     63   *snd_buf = '';
     64   strcat(snd_buf, argv[2]);
     65 
     66 
     67   if ((fp = open(TFILE,O_WRONLY)) < 0)    //不是用fopen
     68   {
     69     perror("fopen:");
     70     exit(1);
     71   }
     72 
     73 
     74   if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
     75   {
     76     perror("socket:");
     77     exit(1);
     78   }
     79 
     80 
     81   server_addr.sin_family = AF_INET;
     82   server_addr.sin_port = htons(SERVPORT);
     83   inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
     84   memset(&(server_addr.sin_zero), 0, 8);
     85 
     86 
     87   /* create the connection by socket 
     88    * means that connect "sockfd" to "server_addr"
     89    */
     90   if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
     91   {
     92     perror("connect");
     93     exit(1);
     94   }
     95 
     96 
     97   /**/
     98   if (send(sockfd, snd_buf, sizeof(snd_buf), 0) == -1)
     99   {
    100     perror("send:");
    101     exit(1);
    102   }
    103   printf("send:%s
    ", snd_buf);
    104 
    105   while (1)
    106   {
    107     FD_ZERO(&readset);            //每次循环都要清空集合,否则不能检测描述符变化
    108     FD_SET(sockfd, &readset);     //添加描述符       
    109     FD_ZERO(&writeset);
    110     FD_SET(fp,     &writeset);
    111 
    112     maxfd = sockfd > fp ? (sockfd+1) : (fp+1);    //描述符最大值加1
    113 
    114     ret = select(maxfd, &readset, NULL, NULL, NULL);   // 阻塞模式
    115     switch( ret)
    116     {
    117       case -1:
    118         exit(-1);
    119         break;
    120       case 0:
    121         break;
    122       default:
    123         if (FD_ISSET(sockfd, &readset))  //测试sock是否可读,即是否网络上有数据
    124         {
    125           recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, MSG_DONTWAIT);
    126           rcv_buf[recvbytes] = '';
    127           printf("recv:%s
    ", rcv_buf);
    128 
    129           if (FD_ISSET(fp, &writeset))
    130           {
    131             write(fp, rcv_buf, strlen(rcv_buf));   // 不是用fwrite
    132           }
    133           goto end;
    134         }
    135     }
    136     cir_count++;
    137     printf("CNT : %d 
    ",cir_count);
    138   }
    139 
    140 end:
    141   close(fp);
    142   close(sockfd);
    143 
    144 
    145   return 0;
    146 }
    147 
    148 perl实现:
    149 #! /usr/bin/perl
    150 ###############################################################################
    151 # File
    152 #  tcp_client.pl
    153 # Descript
    154 #  send message to server
    155 ###############################################################################
    156 use IO::Socket;
    157 use IO::Select;
    158 
    159 
    160 #hash to install IP Port
    161 %srv_info =(
    162 #"srv_ip"  => "61.184.93.197",
    163       "srv_ip"  => "192.168.1.73",
    164       "srv_port"=> "8080",
    165       );
    166 
    167 
    168 my $srv_addr = $srv_info{"srv_ip"};
    169 my $srv_port = $srv_info{"srv_port"};
    170 
    171 
    172 my $sock = IO::Socket::INET->new(
    173       PeerAddr => "$srv_addr",
    174       PeerPort => "$srv_port",
    175       Type     => SOCK_STREAM,
    176       Blocking => 1,
    177 #     Timeout  => 5,
    178       Proto    => "tcp")
    179 or die "Can not create socket connect. $@";
    180 
    181 
    182 $sock->send("Hello server!
    ", 0) or warn "send failed: $!, $@";
    183 $sock->autoflush(1);
    184 
    185 
    186 my $sel = IO::Select->new($sock);
    187 while(my @ready = $sel->can_read)
    188 {
    189   foreach my $fh(@ready)
    190   {
    191     if($fh == $sock)
    192     {
    193       while()
    194       {
    195         print $_;
    196       }
    197       $sel->remove($fh);
    198       close $fh;
    199     }
    200   }
    201 }
    202 $sock->close();
    203 
    204 四、异步非阻塞模式
    205 最后,异步非阻塞 I/O 模型是一种处理与 I/O 重叠(并行)进行的模型。
    206 以read系统调用为例
    207 steps:
    208 a. 调用read;
    209 b. read请求会立即返回,说明请求已经成功发起了。
    210 c. 在后台完成读操作这段时间内,应用程序可以执行其他处理操作。
    211 d. 当 read 的响应到达时,就会产生一个信号或执行一个基于线程的回调函数来完成这次 I/O 处理过程。
    212 
    213 /*
    214  * rief
    215  * tcp client
    216  */
    217 
    218 #include <stdio.h>
    219 #include <stdlib.h>
    220 #include <sys/socket.h>
    221 #include <sys/select.h>
    222 #include <sys/time.h>
    223 #include <netdb.h>
    224 #include <string.h>
    225 
    226 #include <sys/types.h>
    227 #include <sys/stat.h>
    228 #include <fcntl.h>
    229 #define SERVPORT 8080
    230 #define MAXDATASIZE 100
    231 #define TFILE "data_from_socket.txt"
    232 
    233 
    234 int main(int argc, char *argv[])
    235 {
    236   int sockfd, recvbytes;
    237   char rcv_buf[MAXDATASIZE]; /*./client 127.0.0.1 hello */
    238   char snd_buf[MAXDATASIZE];
    239   struct hostent *host;             /* struct hostent
    240                                      * {
    241                                      * char *h_name; // general hostname
    242                                      * char **h_aliases; // hostname's alias
    243                                      * int h_addrtype; // AF_INET
    244                                      * int h_length; 
    245                                      * char **h_addr_list;
    246                                      * };
    247                                      */
    248   struct sockaddr_in server_addr;
    249 
    250 
    251   /* */
    252   fd_set readset, writeset;
    253   int check_timeval = 1;
    254   struct timeval timeout={check_timeval,0}; //阻塞式select, 等待1秒,1秒轮询
    255   int maxfd;
    256   int fp;
    257   int cir_count = 0;
    258   int ret;
    259 
    260 
    261   if (argc < 3)
    262   {
    263     printf("Usage:%s [ip address] [any string]
    ", argv[0]);
    264     return 1;
    265   }
    266 
    267 
    268   *snd_buf = '';
    269   strcat(snd_buf, argv[2]);
    270 
    271 
    272   if ((fp = open(TFILE,O_WRONLY)) < 0)    //不是用fopen
    273   {
    274     perror("fopen:");
    275     exit(1);
    276   }
    277 
    278 
    279   if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    280   {
    281     perror("socket:");
    282     exit(1);
    283   }
    284 
    285 
    286   server_addr.sin_family = AF_INET;
    287   server_addr.sin_port = htons(SERVPORT);
    288   inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
    289   memset(&(server_addr.sin_zero), 0, 8);
    290 
    291 
    292   /* create the connection by socket 
    293    * means that connect "sockfd" to "server_addr"
    294    */
    295   if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
    296   {
    297     perror("connect");
    298     exit(1);
    299   }
    300 
    301 
    302   /**/
    303   if (send(sockfd, snd_buf, sizeof(snd_buf), 0) == -1)
    304   {
    305     perror("send:");
    306     exit(1);
    307   }
    308   printf("send:%s
    ", snd_buf);
    309 
    310 
    311   while (1)
    312   {
    313     FD_ZERO(&readset);            //每次循环都要清空集合,否则不能检测描述符变化
    314     FD_SET(sockfd, &readset);     //添加描述符       
    315     FD_ZERO(&writeset);
    316     FD_SET(fp,     &writeset);
    317 
    318     maxfd = sockfd > fp ? (sockfd+1) : (fp+1);    //描述符最大值加1
    319 
    320     ret = select(maxfd, &readset, NULL, NULL, &timeout);   // 非阻塞模式
    321     switch( ret)
    322     {
    323       case -1:
    324         exit(-1);
    325         break;
    326       case 0:
    327         break;
    328       default:
    329         if (FD_ISSET(sockfd, &readset))  //测试sock是否可读,即是否网络上有数据
    330         {
    331           recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, MSG_DONTWAIT);
    332           rcv_buf[recvbytes] = '';
    333           printf("recv:%s
    ", rcv_buf);
    334 
    335 
    336           if (FD_ISSET(fp, &writeset))
    337           {
    338             write(fp, rcv_buf, strlen(rcv_buf));   // 不是用fwrite
    339           }
    340           goto end;
    341         }
    342     }
    343     timeout.tv_sec = check_timeval;    // 必须重新设置,因为超时时间到后会将其置零
    344 
    345     cir_count++;
    346     printf("CNT : %d 
    ",cir_count);
    347   }
    348 
    349 end:
    350   close(fp);
    351   close(sockfd);
    352 
    353   return 0;
    354 }
    355 
    356 五、server端程序:
    357 /*
    358  * rief
    359  * tcp server
    360  */
    361 #include <stdio.h>
    362 #include <sys/socket.h>
    363 #include <sys/types.h>
    364 #include <netinet/in.h>
    365 #include <arpa/inet.h>
    366 #include <string.h>
    367 #include <stdlib.h>
    368 #define SERVPORT 8080
    369 #define BACKLOG 10 // max numbef of client connection
    370 #define MAXDATASIZE 100
    371 
    372 
    373 int main(char argc, char *argv[])
    374 {
    375   int sockfd, client_fd, addr_size, recvbytes;
    376   char rcv_buf[MAXDATASIZE], snd_buf[MAXDATASIZE];
    377   char* val;
    378   struct sockaddr_in server_addr;
    379   struct sockaddr_in client_addr;
    380   int bReuseaddr = 1;
    381 
    382 
    383   char IPdotdec[20];
    384 
    385 
    386   /* create a new socket and regiter it to os .
    387    * SOCK_STREAM means that supply tcp service, 
    388    * and must connect() before data transfort.
    389    */
    390   if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    391   {
    392     perror("socket:");
    393     exit(1);
    394   }
    395 
    396   /* setting server's socket */
    397   server_addr.sin_family = AF_INET;         // IPv4 network protocol
    398   server_addr.sin_port = htons(SERVPORT);
    399   server_addr.sin_addr.s_addr = INADDR_ANY; // auto IP detect
    400   memset(&(server_addr.sin_zero),0, 8);
    401 
    402   setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&bReuseaddr, sizeof(int));
    403   if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr))== -1)
    404   {
    405     perror("bind:");
    406     exit(1);
    407   }
    408 
    409   /* 
    410    * watting for connection , 
    411    * and server permit to recive the requestion from sockfd 
    412    */
    413   if (listen(sockfd, BACKLOG) == -1) // BACKLOG assign thd max number of connection
    414   {
    415     perror("listen:");
    416     exit(1);                                                                 
    417   }                                                                          
    418                                                                              
    419   while(1)                                                                   
    420   {                                                                          
    421     addr_size = sizeof(struct sockaddr_in);                                  
    422                                                                              
    423     /*                                                                       
    424      * accept the sockfd's connection,                                       
    425      * return an new socket and assign far host to client_addr               
    426      */                                                                      
    427     printf("watting for connect...
    ");                                      
    428     if ((client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &addr_size)) == -1)   
    429     {                                                                        
    430       /* Nonblocking mode */                                                 
    431       perror("accept:");                                                     
    432       continue;                                                              
    433     }                                                                        
    434                                                                              
    435     /* network-digital to ip address */                                      
    436     inet_ntop(AF_INET, (void*)&client_addr, IPdotdec, 16);                   
    437     printf("connetion from:%d : %s
    ",client_addr.sin_addr, IPdotdec);       
    438                                                                              
    439     //if (!fork())                                                           
    440     {                                                                        
    441       /* child process handle with the client connection */                  
    442                                                                              
    443       /* recive the client's data by client_fd */                            
    444       if ((recvbytes = recv(client_fd, rcv_buf, MAXDATASIZE, 0)) == -1)      
    445       {                                                                      
    446         perror("recv:");                                                     
    447         exit(1);                                                             
    448       }                                                                      
    449       rcv_buf[recvbytes]='';                                               
    450       printf("recv:%s
    ", rcv_buf);                                          
    451                                                                              
    452                                                                              
    453       *snd_buf='';                                                         
    454       strcat(snd_buf, "welcome");                                            
    455                                                                              
    456       sleep(3);                                                              
    457       /* send the message to far-hosts by client_fd */                       
    458       if (send(client_fd, snd_buf, strlen(snd_buf), 0) == -1)                
    459       {                                                                      
    460         perror("send:");                                                     
    461         exit(1);                                                             
    462       }                                                                      
    463       printf("send:%s
    ", snd_buf);                                          
    464                                                                              
    465       close(client_fd);                                                      
    466       //exit(1);                                                             
    467     }                                                                        
    468                                                                              
    469     //close(client_fd);                                                      
    470   }
    471 
    472   return 0;                                                                  
    473 }       
    View Code
  • 相关阅读:
    Linux内核设计第三周学习总结 跟踪分析Linux内核的启动过程
    Linux内核设计第二周学习总结 完成一个简单的时间片轮转多道程序内核代码
    Linux内核设计第一周学习总结 计算机如何工作
    信息安全系统设计基础期末总结
    信息安全系统设计基础第十四周学习总结
    信息安全系统设计基础第十三周学习总结
    20135310陈巧然 20135305姚歌 实验四:外设驱动程序设计
    linux内核设计与实现一书阅读整理 之第一二章整合
    20135239 益西拉姆 linux内核分析 使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用
    20135239 益西拉姆 linux内核分析 跟踪分析Linux内核的启动过程
  • 原文地址:https://www.cnblogs.com/lihonglin2016/p/4433176.html
Copyright © 2020-2023  润新知