我们首先开启套接口的信号驱动I/O功能,并通过sigaction系统调用安装一个信号处理函数。该系统调用立即发回,我们的进程继续工作,也就是说它没有被阻塞。当数据报准备好时,内核就为该进程产生一个SIGIO信号。我们随后既可以在信号处理函数中调用recvfrom读取数据报,并通知主循环数据已经准备好待处理,也可以立即通知主循环,让它读取数据报。
无论如何处理SIGIO信号,这种模型的优势在于等待数据报到达期间,进程不被阻塞。主循环可以继续执行,只要不时等待来自信号处理函数的通知:既可以是数据已经准备好被处理,也可以是数据报已准备好被读取。
异步I/O(asynchronous I/O)有POSIX规范定义。后来演变成当前POSIX规范的各种早期标准定义的实时函数中存在的差异已经取得一致。一般地说,这些函数的工作机制是:告知内核启动某个操作,并让内核在整个操作(包括将数据从内核拷贝到我们自己的缓冲区)完成后通知我们。这种模型与前与前面介绍的信号驱动模型的主要区别在于:信号驱动I/O是由内核通知我们何时可以启动一个I/O操作,而异步I/O模型是由内核通知我们I/O操作何时完成。
同步I/O与异步I/O对比
POSIX把这两个术语定义如下:
·同步I/O操作(synchronous I/O operation)导致请求进程阻塞,直到I/O操作完成。
·异步I/O(asynchronous I/O operation)不导致请求进程阻塞。
根据上述定义,我们前4种模型----阻塞I/O模型、非阻塞I/O模型、I/O复用模型和信号去驱动I/O模型都是同步I/O模型,因为其中真正的I/O操作(recvfrom)将阻塞进程。只有异步I/O模型与POSIX定义的异步I/O相匹配。
- #include <sys/select.h>
- #include <sys/time.h>
- int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,
- const struct timeval *timeout);
从最后一个参数timeout 开始介绍,它告知内核等待所指定描述符中任何一个就绪可花多长时间。其timeval结构用于指定这段时间的秒数和微妙数。
- struct timeval
- {
- long tv_sec; //seconds
- long tv_usec; //mircoseconds
- }
- void FD_ZERO(fd_set *fdset); //从fdset中清除所有的文件描述符
- void FD_SET(int fd, fd_set *fdset); //将fd加入到fdset
- void FD_CLR(int fd, fd_set *fdset); //将fd从fdset里面清除
- int FD_ISSET(int fd, fd_set *fdset); //判断fd是否在fdset集合中
- fd_set rset;
- FD_ZERO(&rset);
- FD_SET(1, &rset);
- FD_SET(4 &rset);
- FD_SET(5, &rset);
对于可读文件描述符集以下四种情况会导致置位:
1、socket接收缓冲区中的数据量大于或等于当前缓冲区的低水位线.此时对于read操作不会被阻塞并且返回一个正值(读取的字节数).低水位线可以通过SO_RCVLOWAT选项设定,对于Tcp和Udp来说其默认值为1.
2、socket连接的读端被关闭,如shutdown(socket, SHUT_RD)或者close(socket).对应底层此时会接到一个FIN包,read不会被阻塞但会返回0.代表读到socket末端.
3、socket是一个监听socket并且有新连接等待.此时accept操作不会被阻塞.
4、发生socket错误.此时read操作会返回SOCKET_ERROR(-1).可以通过errno来获取具体错误信息.
对于可写文件描述符集以下四种情况会导致置位:
1、socket发送缓冲区中的可用缓冲大小大于或等于发送缓冲区中的低水位线并且满足以下条件之一
(1)、socket已连接
(2)、socket本身不要求连接,典型如Udp
低水位线可以通过SO_SNDLOWAT选项设置.对于Tcp和Udp来说一般为2048.
2、socket连接的写端被关闭,如shutdown(socket, SHUT_WR)或者close(socket).在一个已经被关闭写端的句柄上写数据会得到SIGPIPE的信号(errno).
3、一个非阻塞的connect操作连接成功 或者 connect操作失败.
4、发生socket错误.此时write操作会返回SOCKET_ERROR(-1).可以通过errno来获取具体错误信息.
对于异常文件描述符集只有一种情况(针对带外数据):
当收到带外数据(out-of-band)时或者socket的带外数据标志未被清除.
下面看个具体例子:
server
- #include <stdio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <sys/select.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #define PORT 8888
- #define MAXSIZE 128
- int main()
- {
- int i,nbyte;
- int listenfd, confd, maxfd;
- char buffer[MAXSIZE];
- fd_set global_rdfs, current_rdfs;
- struct sockaddr_in addr,clientaddr;
- int addrlen = sizeof(struct sockaddr_in);
- int caddrlen = sizeof(struct sockaddr_in);
- if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
- {
- perror("socket error");
- exit(-1);
- }
- else
- {
- printf("socket successfully!\n");
- printf("listenfd : %d\n",listenfd);
- }
- memset(&addr, 0 ,addrlen);
- addr.sin_family = AF_INET;
- addr.sin_port = htons(PORT);
- addr.sin_addr.s_addr = htonl(INADDR_ANY);
- if(bind(listenfd,(struct sockaddr *)&addr,addrlen) == -1)
- {
- perror("bind error");
- exit(-1);
- }
- else
- {
- printf("bind successfully!\n");
- printf("listen port:%d\n",PORT);
- }
- if(listen(listenfd,5) == -1)
- {
- perror("listen error");
- exit(-1);
- }
- else
- {
- printf("listening...\n");
- }
- maxfd = listenfd;
- FD_ZERO(&global_rdfs);
- FD_SET(listenfd,&global_rdfs);
- while(1)
- {
- current_rdfs = global_rdfs;
- if(select(maxfd + 1,¤t_rdfs, NULL, NULL,0) < 0)
- {
- perror("select error");
- exit(-1);
- }
- for(i = 0; i <= listenfd + 1; i++)
- {
- if(FD_ISSET(i, ¤t_rdfs))
- {
- if(i == listenfd)
- {
- if((confd = accept(listenfd,(struct sockaddr *)&clientaddr,&caddrlen)) == -1)
- {
- perror("accept error");
- exit(-1);
- }
- else
- {
- printf("Connect from [IP:%s PORT:%d]\n",
- inet_ntoa(clientaddr.sin_addr),clientaddr.sin_port);
- FD_SET(confd,&global_rdfs);
- maxfd = (maxfd > confd ? maxfd : confd);
- }
- }
- else
- {
- if((nbyte = recv(i, buffer, sizeof(buffer),0)) < 0)
- {
- perror("recv error");
- exit(-1);
- }
- else if(nbyte == 0)
- {
- close(i);
- FD_CLR(i,&global_rdfs);
- }
- else
- {
- printf("recv:%s\n",buffer);
- send(i, buffer, sizeof(buffer),0);
- }
- }
- }
- }
- }
- return 0;
- }
执行结果如下:
- fs@ubuntu:~$ cd qiang/select/
- fs@ubuntu:~/qiang/select$ ./select2
- socket successfully!
- listenfd : 3
- bind successfully!
- listen port:8888
- listening...
- Connect from [IP:192.168.3.51 PORT:1992]
- recv:hello
- Connect from [IP:192.168.3.53 PORT:2248]