Linux的Socket IO模型趣解

2019-10-13 17:07:06于丽

四、信号驱动 I/O 模型

老陈接收到新的信件后,一般的程序是: 

打开信封—-掏出信纸 —-阅读信件—-回复信件 ……为了进一步减轻用户负担,小区物业又开发了一种新的技术:住户只要告诉小区物业对信件的操作步骤,小区物业信箱将按照这些步骤去处理信件,不再需要用户亲自拆信 /阅读/回复了!这就是信号驱动I/O模型。 

我们也可以用信号,让内核在描述字就绪时发送SIGIO信号通知我们。 

首先开启套接口的信号驱动 I/O功能,并通过sigaction系统调用安装一个信号处理函数。该系统调用将立即返回,我们的进程继续工作,也就是说没被阻塞。当数据报准备好读取时,内核就为该进程产生一个SIGIO信号,我们随后既可以在信号处理函数中调用recvfrom读取数据报,并通知主循环数据已准备好待处理,也可以立即通知主循环,让它读取数据报。 


无论如何处理SIGIO信号,这种模型的优势在于等待数据报到达期间,进程不被阻塞,主循环可以继续执行,只要不时地等待来自信号处理函数的通知:既可以是数据已准备好被处理,也可以是数据报已准备好被读取。 

五、异步非阻塞模式 

linux下的asynchronous IO其实用得很少。 

与前面的信号驱动模型的主要区别在于:信号驱动 I/O是由内核通知我们何时可以启动一个 I/O操作,而异步 I/O模型是由内核通知我们 I/O操作何时完成 。 

先看一下它的流程:

 

这就是异步非阻塞模式 

以read系统调用为例 

steps: 

a. 调用read;
b. read请求会立即返回,说明请求已经成功发起了。
c. 在后台完成读操作这段时间内,应用程序可以执行其他处理操作。
d. 当 read 的响应到达时,就会产生一个信号或执行一个基于线程的回调函数来完成这次 I/O 处理过程。

 /*
 * brief
 * tcp client
 */

#include 
#include 
#include 
#include 
#include 
#include 
#include

#include 
#include 
#include 
#define SERVPORT 8080
#define MAXDATASIZE 100
#define TFILE "data_from_socket.txt"

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;

 /* */
 fd_set readset, writeset;
 int check_timeval = 1;
 struct timeval timeout={check_timeval,0}; //阻塞式select, 等待1秒,1秒轮询
 int maxfd;
 int fp;
 int cir_count = 0;
 int ret;

 if (argc < 3)
 {
 printf("Usage:%s [ip address] [any string]n", argv[0]);
 return 1;
 }

 *snd_buf = '';
 strcat(snd_buf, argv[2]);

 if ((fp = open(TFILE,O_WRONLY)) < 0) //不是用fopen
 {
 perror("fopen:");
 exit(1);
 }

 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:%sn", snd_buf);

 while (1)
 {
 FD_ZERO(&readset);   //每次循环都要清空集合,否则不能检测描述符变化
 FD_SET(sockfd, &readset);  //添加描述符  
 FD_ZERO(&writeset);
 FD_SET(fp,  &writeset);

 maxfd = sockfd > fp ? (sockfd+1) : (fp+1); //描述符最大值加1

 ret = select(maxfd, &readset, NULL, NULL, &timeout); // 非阻塞模式
 switch( ret)
 {
  case -1:
  exit(-1);
  break;
  case 0:
  break;
  default:
  if (FD_ISSET(sockfd, &readset)) //测试sock是否可读,即是否网络上有数据
  {
   recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, MSG_DONTWAIT);
   rcv_buf[recvbytes] = '';
   printf("recv:%sn", rcv_buf);

   if (FD_ISSET(fp, &writeset))
   {
   write(fp, rcv_buf, strlen(rcv_buf)); // 不是用fwrite
   }
   goto end;
  }
 }
 timeout.tv_sec = check_timeval; // 必须重新设置,因为超时时间到后会将其置零

 cir_count++;
 printf("CNT : %d n",cir_count);
 }

end:
 close(fp);
 close(sockfd);

 return 0;
}