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
原文地址:https://www.cnblogs.com/lihonglin2016/p/4433176.html