socket编程的同步、异步与阻塞、非阻塞示例详解
分类: 架构设计与优化
简介
图 1. 基本 Linux I/O 模型的简单矩阵
每个 I/O 模型都有自己的使用模式,它们对于特定的应用程序都有自己的优点。
本节将简要对其一一进行介绍。
一、同步阻塞模式
在这个模式中,用户空间的应用程序执行一个系统调用,并阻塞,直到系统调用完成为止(数据传输完成或发生错误)。
显然,代码中的connect, send, recv都是同步阻塞工作模式,
在结果没有返回时,程序什么也不做,
二、同步非阻塞模式
同步阻塞 I/O 的一种效率稍低的变种是同步非阻塞 I/O。
在这种模型中,系统调用是以非阻塞的形式打开的。
这意味着 I/O 操作不会立即完成, 操作可能会返回一个错误代码,
说明这个命令不能立即满足(EAGAIN 或 EWOULDBLOCK),
非阻塞的实现是 I/O 命令可能并不会立即满足,需要应用程序调用许多次来等待操作完成。
这可能效率不高,
因为在很多情况下,当内核执行这个命令时,应用程序必须要进行忙碌等待,直到数据可用为止,
或者试图执行其他工作。
因为数据在内核中变为可用到用户调用 read 返回数据之间存在一定的间隔,这会导致整体数据吞吐量的降低。
异步阻塞模式,异步非阻塞模式以及server端程序见本文的第二部分。
http://blog.chinaunix.net/uid-26000296-id-3755268.html
图 1. 基本 Linux I/O 模型的简单矩阵
![基本 Linux I/O 模型的简单矩阵](http://www.ibm.com/developerworks/cn/linux/l-async/figure1.gif)
每个 I/O 模型都有自己的使用模式,它们对于特定的应用程序都有自己的优点。
本节将简要对其一一进行介绍。
一、同步阻塞模式
在这个模式中,用户空间的应用程序执行一个系统调用,并阻塞,直到系统调用完成为止(数据传输完成或发生错误)。
/* * \brief * 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]\n", argv[0]); return 1; } *snd_buf = '\0'; 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\n", snd_buf); /* 同步阻塞模式 */ if ((recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, 0)) == -1) { perror("recv:"); exit(1); } rcv_buf[recvbytes] = '\0'; printf("recv:%s\n", rcv_buf); close(sockfd); return 0; }
显然,代码中的connect, send, recv都是同步阻塞工作模式,
在结果没有返回时,程序什么也不做,
二、同步非阻塞模式
同步阻塞 I/O 的一种效率稍低的变种是同步非阻塞 I/O。
在这种模型中,系统调用是以非阻塞的形式打开的。
这意味着 I/O 操作不会立即完成, 操作可能会返回一个错误代码,
说明这个命令不能立即满足(EAGAIN 或 EWOULDBLOCK),
非阻塞的实现是 I/O 命令可能并不会立即满足,需要应用程序调用许多次来等待操作完成。
这可能效率不高,
因为在很多情况下,当内核执行这个命令时,应用程序必须要进行忙碌等待,直到数据可用为止,
或者试图执行其他工作。
因为数据在内核中变为可用到用户调用 read 返回数据之间存在一定的间隔,这会导致整体数据吞吐量的降低。
/* * \brief * 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]\n", argv[0]); return 1; } *snd_buf = '\0'; 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\n"); } printf("send:%s\n", snd_buf); /* 同步非阻塞模式 */ while ((recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, MSG_DONTWAIT)) == -1) { sleep(1); printf("sleep\n"); } rcv_buf[recvbytes] = '\0'; printf("recv:%s\n", rcv_buf); close(sockfd); return 0; }
异步阻塞模式,异步非阻塞模式以及server端程序见本文的第二部分。
http://blog.chinaunix.net/uid-26000296-id-3755268.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 三、异步阻塞模式 2 另外一个阻塞解决方案是带有阻塞通知的非阻塞 I/O。 3 在这种模型中,配置的是非阻塞 I/O,然后使用阻塞 select 系统调用来确定一个 I/O 描述符何时有操作。 4 使 select 调用非常有趣的是它可以用来为多个描述符提供通知,而不仅仅为一个描述符提供通知。 5 对于每个提示符来说,我们可以请求这个描述符可以写数据、有读数据可用以及是否发生错误的通知 6 7 下面的C语言实现的例子,它从网络上接受数据写入一个文件中: 8 /* 9 * \brief 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]\n", argv[0]); 59 return 1; 60 } 61 62 63 *snd_buf = '\0'; 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\n", 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] = '\0'; 127 printf("recv:%s\n", 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 \n",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!\n", 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 * \brief 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]\n", argv[0]); 264 return 1; 265 } 266 267 268 *snd_buf = '\0'; 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\n", 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] = '\0'; 333 printf("recv:%s\n", 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 \n",cir_count); 347 } 348 349 end: 350 close(fp); 351 close(sockfd); 352 353 return 0; 354 } 355 356 五、server端程序: 357 /* 358 * \brief 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...\n"); 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\n",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]='\0'; 450 printf("recv:%s\n", rcv_buf); 451 452 453 *snd_buf='\0'; 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\n", snd_buf); 464 465 close(client_fd); 466 //exit(1); 467 } 468 469 //close(client_fd); 470 } 471 472 return 0; 473 }