c语言中epoll函数,【linux下c语言服务器开发系列4】IO复用[select and epoll函数的使用]...

前言

服务器的重要作用是对多种外部事件或者请求作出反应。对于多个外部请求,可以通过多进程进行处理,即每来一个连接,我就fork一个子进程,用这个子进程专门针对这个请求进行处理,包括读写操作,与其他进程通讯,有数据库打交道等。这种方式是多个进程并行进行处理。但是,并行的程序,由于需要fork子进程,进行进程管理、进程切换等,需要耗费较多的资源,是否有串行的方式解决这个问题呢?有,那就是IO复用。IO复用是把所有需要进行处理的IO的读写注册成事件,如果哪个IO可以读写了,就触发相应的事件,通过复用函数返回结果,然后主进程(这里只有一个进程)根据返回的结果进行判断,分析是哪个IO事件,然后再做具体的处理。通过这种方式,我们可以把多个外部连接请求注册在一起去监听,当某个请求有内容到达时,触发这个文件描述符的可读事件,服务器端的主进程判断,发现是这个客户有内容要发送,且内容已经准备好了,于是调用read()函数,读取内容,把读到的内容交给核心业务处理函数,进行进一步处理(要求这里的处理,一般是非阻塞的处理),处理结束后,继续监听所有注册的文件描述符。然后循环上面那个过程。

1.IO复用函数

IO复用函数主要有select,poll和epoll三个。我看了select和epoll两个。现在可能epoll用的更多一些。具体的函数的使用方法,可以看man手册(我一般在ubuntu下开发,在window上写博客,现在不太方便调用man)。当然,也可以直接参考我写的小例子。这样更清晰明了。

2.select函数使用方法(待补)

3.epoll函数的使用方法(待补)

4.例子

4.1阻塞版

通过对select和epoll的学习,我写了下面的小例子。例子的功能是 echoback服务器,既需要监听客户端的连接,连接建立后,对客户端发送的数据直接写回去。该服务器还要接受本端标准输入,并把标准输入的内容写到标准输出。通过例子1我们发现,这个需求是比较难做到的,因为不IO复用的话,需要一直阻塞在accept或read函数,要么就一直阻塞在scanf函数。例子见http://git.oschina.net/mengqingxi89/codelittle/blob/master/codes/echoserver/block_io.cpp。使用方法,编译后,起一个terminal运行./block_io 127.0.0.1 12345,服务器就启动了,在起一个terminal,用telnet 127.0.0.1 12345 作为客户端,就可以连接到服务器了。我们发现,客户端连接后,在服务器的terminal就不能输入了,这是因为阻塞造成的,下面我们用IO复用的方法来解决这个问题,包括两个版本,select版和epoll版的。

4.2select版

select版是把listenfd和conn套接字都进行监听,把标准输入套接字0也进行监听,哪个套接字有事件,则处理哪个。从而实现了复用。

具体的代码见

核心部分是

while(run_flag)

{

FD_SET(conn,&read_fds);

FD_SET(conn,&exception_fds);

FD_SET(0,&read_fds); //标准输入

FD_SET(0,&exception_fds);

//int ret=select(maxfd(allsockets,2)+1,&read_fds,&write_fds,&exception_fds,);

//int ret=select(conn+1,&read_fds,&write_fds,&exception_fds,&timeout);

int ret=select(conn+1,&read_fds,&write_fds,&exception_fds,NULL);

if(ret<0)

{

printf("selection error\n");

break;

}

if(FD_ISSET(conn,&read_fds))

{

dowork(users[user_number]);

}

else if (FD_ISSET(conn,&exception_fds))

{

printf("conn excetpion\n");

break;

}

if(FD_ISSET(0,&read_fds))

{

printf("yes\n");

//getline(&ioinput,BUF_SIZE,stdin);

//gets(ioinput);

scanf("%s",ioinput);

printf("%s\n",ioinput);

}

4.3 epoll版本

epoll版本和select版本差不多,只不过epoll的效率可能更高一些,因为他只在内核维护一个事件注册表,而且返回时返回的ready事件也极大的方便了后续的处理。

关键部分是

int addfd(int epollfd, int fd)

{

epoll_event event;

event.data.fd=fd;

event.events=EPOLLIN|EPOLLET;

epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);

setnonblock(fd);

}

*************************************************

epoll_event events [ MAX_EVENT_NUMBER];

int epollfd=epoll_create(5);

assert(epollfd!=-1);

//注册listenfd上的可读事件

addfd(epollfd,listenfd);

addfd(epollfd,0);

**************************************************

while(run_flag)

{

int number=epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);

if(number<0)

{

printf("selection error\n");

break;

}

for(int i=0;i

{

int sockfd=events[i].data.fd;

if(sockfd==listenfd)

{

int acfd=accept(listenfd,NULL,NULL);

if(acfd>=0)

{

conn=acfd;

user_number++;

/*users[user_number] 表示一个conn,唯一标识这个用户链接*/

users[user_number].conn=acfd;

addfd(epollfd,acfd);

}

else

{

printf("accept error\n");

break;

}

}

else if (sockfd==0)

{

scanf("%s",ioinput);

printf("%s\n",ioinput);

}

else

dowork(users[user_number]);

}

}

5总结

有了前面几篇日志的基础,我们就可以做点更大的事了。做一个功能健全的echoback服务器,采用epollIO复用,采用多进程处理每个客户端的请求,在此过程中,对重要信号进行处理。下一个博客就是完成上面的任务。敬请期待。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值