做网络服务的时候并发服务端程序的编写必不可少。前端客户端应用程序是否稳定一部分取决于客户端自身,而更多的取决于服务器是否相应时间够迅速,够稳定.
常见的linux并发服务器模型;
-
- 多进程并发服务器
- 多线程并发服务器
- select多路I/O转接服务器
- poll多路I/O转接服务器
- epool多路I/O转接服务器.
本次主要讨论poll多路I/转接并发服务器模型:
前几章介绍完了多进程并发服务器, 多线程并发服务器, selete多路I/O转接服务器, poll多路I/O转接服务器, 本章开始介绍epoll(linux特有)多路I/O转接模型.
由于多进程和多线程模型在实现中相对简单, 但由于其开销和CPU高度中比较大, 所以一般不用多线程和多进程来实现服务模型. select由于其跨平台, 但其最高上限默认为1024, 修改突破1024的话需要重新编译linux内核, poll虽然解决了select1024的限制, 但由于poll本质实现上也是轮循机制, 所以对于客户端的增加也会使效率降低. 本章来详细介绍epoll多路I/O转发服务器模型.
epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
目前epell是linux大规模并发网络程序中的热门首选模型。
epoll除了提供select/poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。
主要用到的API:
int epoll_create(int size);
简介: 创建epoll句柄
参数: 监听数量
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* evvent);
简单: 控制某个epoll监听的文件描述符上的事件, 注册, 修改, 删除
epfd: 为epoll_create的句柄
op: 动作, 用3个宏来表示
EPOLL_CTL_ADD 注册新的fd到epfd
EPOLL_CTL_MOD 修改已经注册的fd的监听事件
EPOLL_CTL_DEL 从epfd删除一个fd
event: 告诉内核需要监听的事件
struct epoll_event{
__uint32_t events; /*Epoll events*/
epoll_data_t data; /"use data variable"/
}
typedef union epoll_data{
void* ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data_t;
EPOLLIN 监听读事件, EPOLLOUT 监听写事件 EPOLLERR 监听异常事件
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
函数说明: 等待所监控文件描述符上有事件的产生.
epfd: 为epoll_create的句柄
events: 用来存内核得到事件的集合
maxevents: 告之内核这个events有多大, 这个maxevents的值不能大于创建epoll_create()的size
timeout: 等待时间
-1: 阻塞等待
0: 立即返回, 非阻塞
>0: 指定毫秒
返回值: 成功返回有多少文件描述符就绪, 时间到时返回0, 出错返回-1
代码如下:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <string.h> #include <sys/epoll.h> #include <ctype.h> #define SERV_PORT 9096 //服务端口 #define CLIENT_MAX 1024 //客户端最大连接 int main(int argc, char* argv[]){ int listenfd, connfd, efd; struct sockaddr_in serv_addr, clie_addr; socklen_t clie_addr_len; char buf[BUFSIZ]; int n, i, j, nready, opt; struct epoll_event op[CLIENT_MAX+1], ep; //创建监听套接字 listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //设置端口复用 opt = 1; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); //绑定地址族 bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(SERV_PORT); serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); //设置连接同时最高上限 listen(listenfd, SOMAXCONN); //创建epoll efd = epoll_create(CLIENT_MAX+1); //添加请求连接监听套接字 ep.events = EPOLLIN; ep.data.fd = listenfd; epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &ep); for(;;){ //监听事件 nready = epoll_wait(efd, op, CLIENT_MAX + 1, -1); for(i = 0; i < nready; i++){ if(!(op[i].events & EPOLLIN)) continue; //当有连接请求 if(op[i].data.fd == listenfd){ clie_addr_len = sizeof(clie_addr); //获取连接 connfd = accept(listenfd, (struct sockaddr*)&clie_addr, &clie_addr_len); printf("%s:%d connect successfully!\n", inet_ntoa(clie_addr.sin_addr), ntohs(clie_addr.sin_port)); //监听读事件 ep.events = EPOLLIN; ep.data.fd = connfd; //添加至监听 epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &ep); }else{ //客房端消息 bzero(buf, sizeof(buf)); n = read(op[i].data.fd, buf, sizeof(buf)); //客户端关闭连接 if(0 == n){ //从监听中移除 epoll_ctl(efd, EPOLL_CTL_DEL, op[i].data.fd, NULL); //关闭连接 close(op[i].data.fd); }else{ //转为大写 for(j = 0; j < n; j++){ buf[j] = toupper(buf[j]); } //回写给客户端 write(op[i].data.fd, buf, n); } } } } close(listenfd); close(efd); return 0; }