红联Linux门户
Linux帮助

Linux的I/O多路转接模型和select()

发布时间:2014-12-12 21:50:44来源:linux网站作者:ropai

Linux的I/O多路转接模型:

在这种模型下,如果请求的I/O操作阻塞,它不是真正阻塞I/O,而是让其中一个函数等待,在这期间,I/O还能进行其他操作。select()和poll()就属于这种模型。


select()函数说明
 

头文件

#include<sys/types.h>

#include<sys/time.h>

#include<unistd.h>

原型 int select(int numfds,fd_set *readfds,fd_set *writefds,fd_set *exeptfds,struct timeval *timeout
参数

numfds:需要检查的号码最高的文件描述符+1

readfds:由select()监视的读文件描述符集合

writefds:由select()监视的写文件描述符集合

exeptfds:由select()监视的异常处理文件描述符集合

timeout:

     --- NULL:永远等待,直到捕捉到信号或文件描述符已经准备好为止

     ---具体值:struct timeval的指针,若timeout时间还没准备好,则立即返回

     ---0 :从不等待,测试所有指定的描述符并立即返回    

返回值

成功:准备好的文件描述符

-1:出错

对文件描述符操作的四个宏:
 

Linux select I/O多路转接模型学习笔记

最近在学习web server编程,准备先实现一个简单的server。
为了防止阻塞,我准备使用简单得select 模型实现。
先简单介绍一下select函数
#include<sys/select.h>
int select(int maxfdp1,fd_set *readset,fd_set * writeset,fd_set *exceptset,const struct timeval *timeout);
函数说明: select()用来等待文件描述词状态的改变。参数n代表最大的文件描述词加1,参数readfds、writefds 和exceptfds 称为描述词组,是用来回传该描述词的读,写或例外的状况。返回值:错误:-1,超时:0, 执行成功则返回文件描述词状态已改变的个数。底下的宏提供了处理这三种描述词组的方式:
FD_CLR(inr fd,fd_set* set);      //用来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set *set); //用来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_set*set);      //用来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set);    //用来清除描述词组set的全部位

参数 timeout为结构timeval,用来设置select()的等待时间,其结构定义如下
struct timeval
{
time_t tv_sec;
time_t tv_usec;
};
有三种情况,1永远等待,超时参数设置为NULL,2等待一定时间,时间自己设定。3不等待,超时设置为0。


下面贴出一段简单得代码,功能是实现一个简单得http server,可以吐出hello world
代码说明:
通过while循环处理监听状态
检查read fd set的状态,就绪,判读如果是listen socket ,说明可以accept,否则read。
检查write fd set的状态,就绪,write。

#include<iostream>
#include<map>
#include<stdlib.h>
#include<signal.h>
#include<errno.h>
#include<unistd.h>
#include<string.h>
#include<sys/resource.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>

#define PORT 80
#define BUFSIZE 8192
#define LISTENQ 64  //

using namespace std;

struct client_t{
int fd;
int state;
unsigned int pois;
string ip;
string read_buf;
string write_buf;

//http request
string method_req;
string url;
string http_v;
string host;
client_t(){
fd=0;
state=0;//读写状态,0-读,1-写
pois=0;
ip.clear();
read_buf.reserve(BUFSIZE);
write_buf.reserve(BUFSIZE);
method_req.clear();
url.clear();
http_v.clear();
host.clear();
}
};

//initialize server socket
int init_serversock(){
int fd;
struct sockaddr_in serveraddr;
if((fd=socket(AF_INET,SOCK_STREAM,0))<0){
cout<<strerror(errno)<<endl;
return -1;
}
serveraddr.sin_family=AF_INET;
serveraddr.sin_port=htons(PORT);
serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
bzero(&(serveraddr.sin_zero),8);
//bind
if(bind(fd,(struct sockaddr *)&serveraddr,sizeof(sockaddr))<0){
cout<<strerror(errno)<<endl;
return -1;
}
//listen
if(listen(fd,LISTENQ)){
cout<<strerror(errno)<<endl;
return -1;
}

return fd;
}

string int2string(int i){
char buf[16];
sprintf(buf,"%d",i);
return string(buf);
}

int main(int argc,char *argv[]){
cout<<getpid()<<endl;
int msock;
int fd,fdr;
map<int,client_t *> fds;

//initialize server socket
msock=init_serversock();
if(msock<0){
exit(0);
}

fd_set rfds,wfds,arfds,awfds;
FD_ZERO(&rfds);
FD_ZERO(&wfds);

FD_SET(msock,&rfds);

//忽略一个信号
signal(SIGPIPE,SIG_IGN); //否则write出错会段错误

//限制一下系统能用的最大描述符
struct rlimit limit;
if(getrlimit(RLIMIT_NOFILE,&limit)<0){
cout<<strerror(errno)<<endl;
exit(0);
}else{
if(limit.rlim_cur>FD_SETSIZE){
limit.rlim_cur=FD_SETSIZE;
limit.rlim_max=FD_SETSIZE;
}
if(setrlimit(RLIMIT_NOFILE,&limit)<0){
cout<<strerror(errno);
exit(0);
}
}

while(1){
memcpy(&arfds,&rfds,sizeof(rfds));
memcpy(&awfds,&wfds,sizeof(wfds));

int ret=select(FD_SETSIZE+1,&arfds,&awfds,NULL,NULL);
if(ret<0){
cout<<strerror(errno)<<endl;
continue;
}

for(fd=0,fdr=0;fd<FD_SETSIZE && fdr<ret;fd++){
if(FD_ISSET(fd,&arfds)){
fdr++;
if(fd==msock){
//now we can accept a request
struct sockaddr_in addr_in;
size_t addrlen=sizeof(struct sockaddr_in);
int rsock=accept(msock,(struct sockaddr *)&addr_in,&addrlen);
//accpet error
if(rsock<0){
cout<<strerror(errno)<<endl;
continue;
}

client_t *ptr=new client_t;
ptr->ip=string(inet_ntoa(addr_in.sin_addr));
fds[rsock]=ptr;
FD_SET(rsock,&rfds);
cout<<"accept a connection from:"<<ptr->ip<<endl;
}else{
if(fds[fd]->state==0){
char buf[BUFSIZE];
int n=read(fd,buf,BUFSIZE);
if(n<=0){
cout<<"fd:"<<fd<<"断线"<<endl;
FD_CLR(fd,&rfds);
delete fds[fd];
close(fd);
fds[fd]=NULL;
}else{
fds[fd]->read_buf.append(buf,n);
cout<<"读,fd=["<<fd<<"],size=["<<n<<"]"<<endl;
if(fds[fd]->read_buf.find("\r\n\r\n")!=string::npos){
string content="<html>\n<head>\n<title>test</title>\n</head>\n<body>\nhello world\n</body>\n</html>\n";
fds[fd]->write_buf="HTTP/1.1 200 Ok\r\n";
fds[fd]->write_buf+="Content-Type:text/html\r\n";
//fds[fd]->write_buf+="Content-Length:";
fds[fd]->write_buf+="Content-Length:"+int2string(content.length())+"\r\n";
fds[fd]->write_buf+="\r\n"+content;
fds[fd]->state=1;
FD_SET(fd,&wfds);
}
}
}
}
}
if(FD_ISSET(fd,&awfds)){
++fdr;
int n=write(fd,fds[fd]->write_buf.data(),fds[fd]->write_buf.length());
FD_CLR(fd,&rfds);
FD_CLR(fd,&wfds);
delete fds[fd];
close(fd);
fds[fd]=NULL;

if(n<0){
cout<<"写出错"<<endl;
}else{
cout<<"正常关闭"<<endl;
}
}
}
}

close(msock);

return 0;
}