内核同时监听多个socket,任何一个socket状态发生变化,都会通知给进程,这就是IO复用吧。
-
IO复用的典型应用场合:
-
当客户端处理多个描述符时使用
如果一个tcp服务器既要处理监听套接字,又要处理已连接套接字,需要IO复用
如果一个服务器既要处理tcp又要处理udp,需要IO复用
如果一个服务器要处理多个服务或者多个协议,需要IO复用IO复用并非只限于网络编程,啥意思??呵呵
五种IO模型:
-
阻塞式IO:IO操作无数据时,就把进程投入睡眠
-
非阻塞式IO:IO操作无数据时,返回一个EWOULDBLOCK错误,不把进程投入睡眠
-
IO复用(select、poll):可以监听多个描述符,当所有描述符都无数据时,阻塞于selece或poll系统调用,当监听描述符中的任何一个有数据可处理时,select或poll函数会返回。
IO复用 和 使用多线程每个线程分别处理某个阻塞式系统调用这种模型,比较类似
-
信号驱动式IO:
-
异步IO:
select函数:
- 我们调用select告知内核我们对哪些描述符感兴趣(读、写、异常)以及等待时间。描述符不局限于套接字,任何描述符都可以使用select进行测试。
#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);
struct timeval{
long tv_sec; //seconds
long tv_usec; //microseconds
}
/*
timeout参数有三种可能
1)永远等待,传入NULL即可
2)大于0,最多等待固定时长
3)0,不等待,立即返回
*/
/*
三个集合参数都是值-结果类型,分别表示我们对可读、可写、异常错误感兴趣的描述符集合。
它们通常是一个数组,其中每个整数中的每一位代表一个描述符。假设使用32位整数,那么该数组中的第一个元素对应于描述符0-31,第二个整数对应于描述符32-63,依此类推。下面是四个操作这几个数组的宏
*/
void FD_ZERO(fd_set *fdset); //初始化
void FD_SET(int fd,fd_set *fdset); //开启描述符bit位
void FD_CLR(int fd,fd_set *fdset); //关闭描述符bit位
int FD_ISSET(int fd,fd_set *fdset); //判断描述符是否被开启
FD_SETSIZE//常值,定义每个fdset可包含的最多描述符个数
//使用select时,需要处理返回的EINTR错误
-
描述符就绪条件:
-
满足下列四个条件中的任何一个时,一个套接字准备好读:
1)该套接字接收缓冲区中的字节数,大于等于套接字接收缓冲区低水位标记的当前大小。对这样的套接字进行读操作不回阻塞,会返回一个大于0的值。使用SO_RCVLOWAT设置低水位值,tcp和udp默认都是1。
2)该连接的读半部关闭(收到FIN),此时去读会立即返回0
3)该套接字是一个监听套接字(listening中),且已完成三次握手的连接数不为0,对这样的套接字的accept通常不会阻塞(有阻塞情况?15.6介绍) -
满足下列四个条件中的任何一个时,套接字可写:
1)套接字的发送缓冲区可用字节数大于等于套接字发送缓冲区低水位标志的当前大小,并且或者该套接字已连接,或者该套接字不需要连接(udp)。使用SO_SNDLOWAT设置该值,tcp和udp默认都是2048。
2)该套接字的写半部关闭。此时写会返回SIGPIPE信号。
3)使用非阻塞式connect的套接字已建立,或者connect以失败告终。
4)有一个套接字错误需要处理。可以通过指定SO_ERROR套接字选项调用getsockopt获取并清除。 -
如果一个套接字存在带外数据或者仍处于带外标记,那么它有异常条件要处理。
当某个套接字发生错误时,它将由select标记为即可读又可写。
pselect函数:
#include <sys/select.h>
#include <time.h>
int pselect(int maxfdp1, fd_set *readset,fd_set *writeset,fd_set *exceptset, const struct timespec *timeout,const sigset_t *sigmask);
struct timespec{
time_t tv_sec; //seconds
long tv_nsec; //nanoseconds
}
/*
pselect相对于select有两个变化
1)使用timespec结构
2)第六个参数可以允许程序先屏幕某些信号
*/
-
poll函数:
#include <poll.h>
int poll(struct pollfd *fdarray, unsigned long nods, int timeout);
struct polled {
int fd; //套接字
short events; //感兴趣的事件,作为输入
short revents; //发生的事件,作为结果返回
/*
events和revents中的标志为包括:
常值 可以作为输入 可以作为输出 说明
POLLIN 是 是 普通或优先级带数据可读
POLLRDNORM 是 是 普通数据可读
POLLRDBAND 是 是 优先级带数据可读
POLLPRI 是 是 高优先级数据可读
POLLOUT 是 是 普通数据可写
POLLWRNORM 是 是 普通数据可写
POLLWRBAND 是 是 优先级带数据可写
POLLERR 否 是 发生错误
POLLHUP 否 是 发生挂起
POLLNVAL 否 是 描述符不是一个打开的文件
*/
/*
timeout参数三种情况:
1)INFTIM或-1 ,永远等待
2)0,立即返回
3)大于0,等待固定毫秒数
*/
//poll没有像select那样的描述符个数限制的问题
-
shutdown:
#include <sys/socket.h>
int shutdown(int socked, int howto);
/*
shutdown不检查描述符引用计数,直接关闭连接。
shutdown可以关闭单向连接。
howto参数常值:
SHUT_RD 0
SHUT_WR 1
SHUT_RDWR 2
*/
-
select示例代码:
#server.c
#include "common.h"
void sig_chld(int signo){
pid_t pid;
int stat;
//pid = wait(&stat);
while( (pid = waitpid(-1,&stat, WNOHANG)) > 0)
printf("child %d terminated\n",pid);
return;
}
int main(int argc, char **argv) {
int sockfd, clientfd;
struct sockaddr_in serveraddr,clientaddr;
char buf[100];
bzero(&serveraddr,sizeof(serveraddr));
bzero(&clientaddr,sizeof(clientaddr));
serveraddr.sin_family = AF_INET;
inet_pton(AF_INET,"127.0.0.1",&serveraddr.sin_addr.s_addr);
serveraddr.sin_port = htons(45000);
sockfd = socket(AF_INET,SOCK_STREAM,0);
bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
listen(sockfd,10);
Signal(SIGCHLD, sig_chld);
int client[FD_SETSIZE];
int maxFd = sockfd;
int maxI = -1;
int clientNum = 0;
for(int i = 0; i < FD_SETSIZE; i++){
client[i] = -1;
}
fd_set allset,readset,writeset;
FD_ZERO(&allset);
FD_ZERO(&readset);
FD_ZERO(&writeset);
FD_SET(sockfd,&readset);
FD_SET(sockfd,&allset);
int i;
printf("FD_SETSIZE=%d\n",FD_SETSIZE);
while (1) {
int newMaxI;
maxFd = sockfd;
for(i = 0; i<maxI;i++){
if(client[i] != -1){
newMaxI = i;
maxFd = max(maxFd,client[i]);
}
}
maxI = min(maxI,newMaxI);
readset = allset;
int nReady = select(maxFd + 1,&readset,NULL,NULL,NULL);
printf("select over, %d ready\n",nReady);
if(FD_ISSET(sockfd,&readset)){
socklen_t clientlen = sizeof(clientaddr);
clientfd = accept(sockfd,(struct sockaddr*)&clientaddr,&clientlen);
if(clientfd > 0){
for(i = 0;i < FD_SETSIZE;i++){
if(client[i] < 0){
client[i] = clientfd;
break;
}
}
if(i == FD_SETSIZE)
{
puts("too more fd error");
return 1;
}
FD_SET(clientfd,&allset);
maxFd = max(maxFd,clientfd);
maxI = max(maxI,i);
clientNum++;
inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,buf,clientlen);
printf("new connect from:%s:%d, total %d clients\n",buf,ntohs(clientaddr.sin_port),clientNum);
if(--nReady <= 0)
continue;
}else{
err_sys("connection error:");
}
}
int activeSockNum = 0;
for(int i = 0; i <= maxI; i++){
clientfd = client[i];
if(clientfd<0) continue;
if(FD_ISSET(clientfd,&readset)){
activeSockNum++;
int readNum = read(clientfd,buf,sizeof(buf));
if(readNum < 0){
if(errno == EINTR){
puts("EINTR");
continue;
}
else{
shutdown(clientfd,2);
FD_CLR(clientfd,&allset);
client[i] = -1;
//clientNum--;
err_sys("read error");
}
}else if(readNum == 0){
client[i] = -1;
FD_CLR(clientfd,&allset);
printf("connection closed by client\n");
close(clientfd);
// shutdown(clientfd,SHUT_RDWR);
//clientNum--;
}else{
buf[readNum] = 0;
printf("read %d bytes from client\n",readNum);
write(clientfd,buf,strlen(buf));
printf("send %ld bytes to client\n",strlen(buf));
}
if(--nReady<=0)
break;
}
}
printf("%d sockets active in this loop\n",activeSockNum);
}
return 0;
}
#client.c
#include "common.h"
int main(int argc, char **argv){
int sockfd;
struct sockaddr_in serveraddr;
char buf[100];
bzero(&serveraddr,sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
inet_pton(AF_INET,"127.0.0.1",&serveraddr.sin_addr.s_addr);
serveraddr.sin_port = htons(45000);
sockfd = socket(AF_INET,SOCK_STREAM,0);
int rtn = connect(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
if(rtn != 0){
err_sys("connect error");
}
fd_set readset;
FD_ZERO(&readset);
FD_SET(sockfd,&readset);
int sockStdin = fileno(stdin);
FD_SET(fileno(stdin),&readset);
int stdineof = 0;
while (1) {
FD_SET(sockfd,&readset);
int maxfd = sockfd;
if(stdineof == 0) {
FD_SET(sockStdin,&readset);
maxfd = max(sockfd,fileno(stdin));
}
int nReady = select(maxfd + 1,&readset,NULL,NULL,NULL);
printf("nReady = %d\n",nReady);
if(nReady > 0){
if(FD_ISSET(sockStdin,&readset)){
puts("read from stdin");
int readNum = read(sockStdin,buf,sizeof(buf));
if(readNum == 0){
puts("eof, close socket");
shutdown(sockfd,1);
FD_CLR(sockStdin,&readset);
stdineof = 1;
continue;
}
write(sockfd,buf,readNum);
}
if(FD_ISSET(sockfd,&readset)){
puts("read from server");
int readNum = read(sockfd,buf,sizeof(buf));
if(readNum == 0){
if(stdineof == 1) {
puts("server close socket");
return 0;
}
else err_sys("client read error");
}
write(fileno(stdout),buf,readNum);
}
}
}
return 0;
}
#common.c
#include "common.h"
void err_sys(char *info){
fprintf(stderr, "%s:%s\n", info, strerror(errno));
exit(EXIT_FAILURE);
}
Sigfunc *Signal(int signo, Sigfunc *func){
struct sigaction act,oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (signo == SIGALRM){
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT;
#endif
}else {
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART;
#endif
}
if(sigaction(signo,&act,&oact) < 0){
puts("sigaction error");
return(SIG_ERR);
}
return(oact.sa_handler);
}
-
poll示例代码:
#server.c
#include "common.h"
#include <poll.h>
#include <limits.h>
void sig_chld(int signo){
pid_t pid;
int stat;
//pid = wait(&stat);
while( (pid = waitpid(-1,&stat, WNOHANG)) > 0)
printf("child %d terminated\n",pid);
return;
}
int main(int argc, char **argv) {
int sockfd, clientfd;
struct sockaddr_in serveraddr,clientaddr;
char buf[100];
bzero(&serveraddr,sizeof(serveraddr));
bzero(&clientaddr,sizeof(clientaddr));
serveraddr.sin_family = AF_INET;
inet_pton(AF_INET,"127.0.0.1",&serveraddr.sin_addr.s_addr);
serveraddr.sin_port = htons(45000);
sockfd = socket(AF_INET,SOCK_STREAM,0);
bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
listen(sockfd,10);
Signal(SIGCHLD, sig_chld);
printf("OPEN_MAX=%d\n",OPEN_MAX);
struct pollfd client[OPEN_MAX];
int i;
for(i = 0;i<OPEN_MAX;i++){
client[i].fd = -1;
}
int maxi = 0;
client[0].fd = sockfd;
client[0].events = POLLRDNORM;
while(1){
int newMaxi = 0;
for(int i = 0; i <= maxi; i++){
if(client[i].fd != -1){
newMaxi = i;
}
}
maxi = newMaxi;
printf("start poll, maxi = %d\n",maxi);
int nready = poll(client,maxi+1,-1);
printf("nready = %d\n",nready);
for(i = 1; i <= maxi; i++){
clientfd = client[i].fd;
if(clientfd < 0) continue;
if(client[i].revents & (POLLRDNORM | POLLERR)){
int n = read(clientfd,buf,sizeof(buf));
if(n < 0){
client[i].fd = -1;
close(clientfd);
err_sys("read errof");
}
if(n == 0){
puts("client close socket");
client[i].fd = -1;
close(clientfd);
continue;
}
buf[n] = 0;
printf("%s",buf);
write(clientfd,buf,n);
if(--nready <= 1)
break;
}
}
if(client[0].revents & POLLRDNORM){
puts("new client");
socklen_t len = sizeof(clientaddr);
clientfd = accept(sockfd,(struct sockaddr*)&clientaddr,&len);
if(clientfd < 0)
err_sys("connect error");
inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,buf,len);
printf("new connection from:%s:%d\n",buf,ntohs(clientaddr.sin_port));
for(i = 1;i<OPEN_MAX;i++){
if(client[i].fd == -1){
client[i].fd = clientfd;
client[i].events = POLLRDNORM;
break;
}
}
if(i == OPEN_MAX)
err_sys("too many sockets error");
maxi = max(i,maxi);
if(--nready <= 0)
continue;
}
}
return 0;
}