Unix的五种I/O模型
- 阻塞式I/O
- 非阻塞式I/O
- I/O复用(select poll)
- 信号驱动式I/O(SIGIO)
- 异步I/O(POSIX的aio系列函数)
阻塞与非阻塞I/O
最流行的I/O模型是阻塞式I/O,一般默认情况下所有套接字都是阻塞的,但是进程可以把一个套接字设置成非阻塞式I/O,以通知内核——当所请求的I/O操作必须把当前进程投入睡眠时才能完成时,不要把当前进程投入睡眠,而是返回一个错误。
I/O复用
当我们使用select或者poll将进程阻塞在这两个系统调用之上而不是阻塞在真正的I/O调用上时,成为I/O复用。I/O复用的好处是,可以让一个进程同时对多个套接字描述符的状态进行监控。而传统的阻塞式一个进程监控一个套接字的状态。
select函数
select函数可以使一个进程同时等待多个事件中的任何一个发生,并且只在有一个或多个事件发生或经历一段制定的时间后才唤醒它。
注意:select函数监听的set集合在每次返回后非激活状态的套接字位在set集合中被清空,因此每次重新select时需要更新fd_set
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfd1,
fd_set *readset, // 读描述符监控
fd_set *writeset, // 写描述符监控
fd_set *exceptset, // 异常条件描述符监控
const struct timeval *timeout // 超时等待设置
);
//返回:若有就绪套接字则返回其数目,超时返回0,失败返回-1
对于exceptset目前支持2中条件:
- 某个套接字的带外数据到达
- 某个已置为分组模式的伪终端存在可从其主机读取的控制状态信息(不作讨论)
fd_set及其操作
通过FD_SET、FD_CLR、FD_ISSET、FD_ZERO等宏,可以使fd_set结构的每一位与要监控的描述符绑定、清除绑定、判定绑定、清空结构体。在Ubuntu16.4上使用sizeof测试,得到其长度为128字节,也就是1024位,为其同时支持的最多的描述符个数。
void FD_SERO(fd_set *fdset); // 清空fdset中的所有位
void FD_SET(int fd,fd_set *fdset); // 将描述符fd设置为监听状态
void FD_CLR(int fd,fd_set *fdset); //将fd从fdset中清除
void fd_ISSET(int fd,fd_set *fdset);//判断fd是否为集合fdset中的监听套接字
描述符就绪条件
读套接字就绪条件:
写套接字就绪条件:
异常条件就绪:
struct timeval结构体
struct timeval{
long tv_sec; // seconds
long tv_usec; // micro seconds
}
用于指定等待超时的秒数和微秒数。这个参数分为三种情况:
- 永远等待,直到有一个描述符准备就绪才返回。此时该参数设置为NULL
- 等待固定时间:在有一个描述符准备就绪时提前返回,否则超时返回0
- 不等待:将定时器的值都设置为0
shutdown函数
终止网络连接的方式通常是调用close函数。不过close函数有2个限制,可以通过使用shutdown来避免。
- close把描述符的引用计数减一,仅在该数变为0的时候才关闭套接字。使用shutdown可以不管引用计数,直接激发TCP的正常连接终止序列。
- close终止读和谐两个方向的数据传送。既然TCP连接是全涮工搞的,有时候我们需要告知对端我们已经完成了数据的发送,及时对端仍然有数据要发送给我们,此时如果调用close将不能收到对端的数据,而使用shutdown则可以选择关闭读、写或者读写全部关闭。
int shutdown(int sockfd,int howto);
//返回:成功返回0,失败返回-1
howto的可选项:
- SHUT_RD:关闭连接的读——套接字中不再读取数据,而且套接字接受缓冲区中的数据也会被丢弃。进程不能再对这个套接字调用任何读取函数。
- SHUT_WR:关闭连接的写——对于TCP套接字,这成为半关闭。当前留在套接字发送缓冲区中的数据将被发送掉,然后TCP的正常连接终止序列。无论这个套接字描述符的引用计数是否为0,shutdown都会激发TCP终止序列,以后进程不能再对这个套接字调用任何写函数。
- SHUT_RDWR:关闭读、写——相当于分别调取了shutdown两次并传递参数SHUT_RD和SHUT_WR
使用select和shutdown的网络服务器/客户端例子
// serv_select.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#define FD_SET_SIZE 1024
#define LISTEN_PORT 8003
#define MAX_BUF_LEN 1024
int main(){
int i,imaxfd,imax,listenfd,connectfd,nready,nread,iaddrlen;
struct sockaddr_in listen_addr,client_addr;
char buf[MAX_BUF_LEN];
int all_clientfd[FD_SET_SIZE];
for(i = 0;i < FD_SET_SIZE;++i)
all_clientfd[i] = -1;
listenfd = socket(AF_INET,SOCK_STREAM,0);
listen_addr.sin_family = AF_INET;
listen_addr.sin_port = htons(LISTEN_PORT);
listen_addr.sin_addr.s_addr = htonl(INADDR_ANY);
int ret = bind(listenfd,(struct sockaddr *)&listen_addr,sizeof(listen_addr));
ret = listen(listenfd,LISTENQ);
fd_set all_set,read_set;
FD_ZERO(&all_set);
FD_SET(listenfd,&all_set);
imax = -1;
imaxfd = listenfd;
while(1){
read_set = all_set;
nready = select(imaxfd + 1,&read_set,NULL,NULL,NULL);
if(nready < 0){
perror("select");
exit(1);
}
if(FD_ISSET(listenfd,&read_set)){
iaddrlen = sizeof(listen_addr);
connectfd = accept(listenfd,(struct sockaddr*)&listen_addr,&iaddrlen);
for(i = 0;i < FD_SET_SIZE;++i){
if(all_clientfd[i] == -1){
all_clientfd[i] = connectfd;
break;
}
}
if(i == FD_SET_SIZE){
printf("out of max listen fd count!\n");
continue;
}
if(i > imax){
imax = i;
}
FD_SET(connectfd,&all_set);
if(connectfd > imaxfd) imaxfd = connectfd;
if(--nready <= 0) continue;
}
for(i = 0;i < imax;++i){
if(all_client[i] == -1) continue;
if(FD_ISSET(all_client[i],&read_set)){
if(nread = read(all_client[i],buf,MAX_BUF_LEN) <= 0){
close(all_client[i]);
FD_CLR(all_client[i],&all_set);
all_client[i] = -1;
}
else {
write(all_client[i],buf,nread);
}
if(--nready <= 0) break;
}
}
}
}
// client_select.c