I/O复用就是I/O进程不必阻塞于等待I/O,I/O就绪时内核将会通知该进程。I/O复用是通过select和poll函数支持的。
在谈I/O复用之前,先来看下unix下5种I/O模型。以recvfrom(recvfrom)为例。
阻塞式I/O:|-----------recvfrom等待数据报----------|--------接收-------|
数据报到来
非阻塞式I/O:|-----recvfrom不断询问内核数据报是否到来-----|--------接收-------|
数据报到来
I/O复用:|----------进程阻塞于select调用--------|-------接收--------|
某sockfd可读
信号驱动I/O:|--------------进程建立信号SIGIO处理程序,继续执行|-----接收-------|
信号中断
异步I/O:|---------------进程继续执行,等待内核通知完成----------|
内核通知完成
===============================================华丽的分割线=====================================
select函数
函数原型:select(int maxfdp, fd_set *rset, fd_set *wset, fd_set *eset, struct timeval *timeout);
select第一个参数意为描述符的个数,它的值为rset,wset,eset三个描述符集合中最大的fd+1。
rset,wset,eset分别为读fd集合,写fd集合,异常fd集合。
fd_set设置描述符很有技巧。int型变量有32位,它会将已就绪的描述符的那一位置为1。比如读集合rset中的描述符33,它就将该集合中的第二个元素值的第二位置为1(31+2)。(rset的第一个元素代表着描述符0~31)
select中用到的函数:
void FD_ZERO(fd_set *fdset)
void FD_SET(int fd, fd_set *fdset)
void FD_CLR(int fd, fd_set *fdset)
int FD_ISSET(int fd, fd_set *fdset)
皆可望文生义。
select最终是用在socket编程里,以下就直接奉上我理解到的伪代码和书中的源码:
(伪代码一词用的并不准确,但我只是想表达这种意思)
服务端伪代码:
int main(int argc, char **argv)
{
int client[FD_SETSIZE];
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bind(listenfd, (SA *)sockaddr, sizeof(sockaddr));
listen(listenfd, LISTENQ);
fd_set rset;
maxfd = listenfd + 1;
for(; ;) {
select(maxfd, &rset, NULL, NULL, NULL);
if(FD_ISSET(listenfd, &rset)) {
connfd = accept(listenfd, cliaddr, clilen);
for(i = 1; I < FD_SETSIZE; i++)
if(client[I] < 0) {
client[I] = connfd;
break;
}
maxi = I;
FD_SET(connfd, &rset);
}
for(I = 1;i < maxi; I++) {
if(client[i] < 0)
continue;
if(FD_ISISET(client[I], &rset))
do_read;
}
}
}
服务端源码:
#include "unp.h"
int main(int argc, char **argv)
{
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready, client[FD_SETSIZE];
ssize_t n;
fd_set rset,allset;
char buf[MAXLINE];
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (SA *)&servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
maxfd = listenfd;
maxi = -1;
for(i = 0; i < FD_SETSIZE; i++)
client[i] = -1;
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
for(; ;) {
rset = allset;
nready = Select(maxfd+1, &rset, NULL, NULL, NULL);
if(FD_ISSET(listenfd, &rset)) {
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *)&cliaddr, &clilen);
for(i = 0; i < FD_SETSIZE; i++)
if(client[i] < 0) {
client[i] = connfd;
break;
}
if(i == FD_SETSIZE)
err_quit("too many clients");
FD_SET(connfd, &allset);
if(connfd > maxfd)
maxfd = connfd;
if(i > maxi)
maxi = i;
if(--nready <= 0)
continue;
}
for(i = 0; i <= maxi; i++) {
if((sockfd = client[i]) < 0)
continue;
if(FD_ISSET(sockfd, &rset)) {
if((n = Read(sockfd, buf, MAXLINE)) == 0) {
Close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -1;
} else
Writen(sockfd, buf, n);
if(--nready <= 0)
break;
}
}
}
}
POll函数
struct pollfd {
int fd;
short events; //调用时的值
short revents; //调用返回的值
}
插入伪码和源码:
服务端伪码:
int main(int argc, char **argv)
{
int client[FD_SETSIZE];
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bind(listenfd, (SA *)sockaddr, sizeof(sockaddr));
listen(listenfd, LISTENQ);
struct pollfd client[OPENMAX];
client[0].fd = listenfd;
client[0].events = POLLRDNORM;
for(I = 1; I < OPENMAX; I++)
client[I].fd = -1;
maxi = 0;
for(; ;) {
if(client[0].revents & POLLRDNORM) { //new connn com
connfd = accept();
for(I = 1;i < OPENMAX; I++)
{
if(client[i] < 0) {
client[i].fd = connfd;
break;
}
}
maxi = i;
client[i].events = POLLRDNORM;
}
for(i = 1;i < maxi; i++)
{
if(client[i] < 0)
continue;
if(client[i].revents & (POLLRDNORM | POLLERR))
do_read;
}
}
}
服务器端源码:
#include "unp.h"
#include <limits.h>
int main(int argc, char **argv)
{
int i, maxi, listenfd, connfd, sockfd;
int nready;
ssize_t n;
char buf[MAXLINE];
socklen_t clilen;
struct pollfd client[OPEN_MAX];
struct sockaddr_in cliaddr, servaddr;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (SA *)&servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
client[0].fd = listenfd;
client[0].events = POLLDNORM;
for(i = 1; i < OPEN_MAX; i++)
client[i].fd = -1;
maxi = 0;
for(; ;) {
nready = Poll(client, maxi + 1, INFTIM);
if(client[0].revents & POLLRDNORM) {
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *)&cliaddr, &clilen);
for(i = 0; i < OPEN_MAX; i++)
if(client[i].fd < 0) {
client[i].fd = connfd;
break;
}
if(i == OPEN_MAX)
err_quit("too many clients");
client[i].events = POLLRDNORM;
if(i > maxi)
maxi = i;
if(--nready <= 0)
continue;
}
for(i = 0; i <= maxi; i++) {
if((sockfd = client[i].fd) < 0)
continue;
if(client[i].revents & (POLLRDNORM | POLLERR)) {
if((n = Read(sockfd, buf, MAXLINE)) == 0) {
if(errno == ECONNRESET) {
Close(sockfd);
client[i].fd = -1;
} else
err_sys("read error");
} else if(n == 0) {
Close(sockfd);
client[i].fd = -1;
} else
Writen(sockfd, buf, n);
if(--nready <= 0)
break;
}
}
}
}