select、poll、epoll用法
我们先从著名的C10K问题开始探讨,由于早期在网络还不普及的时候,互联网的用户并不是很多,一台服务器同时在线100个用户估计在当时已经算是大型应用了。但是随着互联网的发展,用户群体迅速的扩大,每一个用户都必须与服务器保持TCP连接才能进行实时的数据交互。Facebook这样的网站同一时间的并发TCP连接可能会过亿。这时候问题就来了。
解决这种问题的思路主要有两个:一个是对于每一个连接处理分配一个独立的进程或线程;另一种就是用同一进程或线程来同时处理若干个连接。
第一种解决方案,即来一个TCP链接,就需要分配一个进程/线程。而进程又是操作系统最昂贵的资源,一台机器无法创建很多进程,比如C10K问题就要创建1万个进程,操作系统是无法承受的。
第二中解决方案,即每个进程/线程同时处理多个连接(IO多路复用),我们讨论的select,poll,epoll都是基于这种思路的。
- 这种思路最简单的方法是循环挨个处理各个请求,每个连接对应一个socket,当所有的socket的有数据的时候,这种方法是可行的,但当应用读取某个socket的文件数据不ready的时候,整个应用就会阻塞在这里等待该文件句柄,即使别的文件句柄ready,也无法往下处理。
- select要解决的就是上面的阻塞问题,思路很简单,如果我在读取文件句柄之前,先查看它的状态,ready了就进行处理,不ready就不进行处理,这样就解决了这个问题了
- select 使用fd_set结构体来告诉内核同时监控多个文件句柄,当其中有文件句柄的状态发生制定变化或超时,则调用返回。之后使用FD_ISSET来捉个查看是哪个文件句柄发生变化。
- fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。
- fd_set集合可以通过一些宏由人为的来操作,FD_ZERO(fd_set*):清空集合;FD_SET(int, fd_set*):将指定的文件描述符加入到集合;FD_CLR(int, fd_set*):将一个给定的文件描述符从集合中删除;FD_ISSET(int, fd_set*):判断指定的文件描述符是否可以读写。
- select函数原型:int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
- maxfdp:集合中所有文件描述符的范围,即所有文件描述符的最大值+1;
- readfds:指定我们要监视的读变化的文件描述符集合,即我们是否可以从这些文件中读取数据;
- writefds:指定我们要监视的写变化的文件描述符集合,即我们是否可以从这些文件中写数据;
- errorfds:用来监视文件错误异常;
- timeout:select 的超时时间,若将此参数设为NULL,表示阻塞,直到有文件描述符状态发生变化;若将此参数设为0秒0毫秒,表示非阻塞,直接返回,返回值为0表示没有文 件描述符发生变化,返回值为非0表示有文件描述符发生变化;若将此参数设为大于0,表示等待的时间,期间如果有事件发生则返回非0值,如果一直没有事件, 则等待此参数设置的时间,然后返回0。
- select可以实现小规模的连接,但当连接数很多的时候,逐个检查状态就很慢了,因此,select往往存在管理的句柄上限(FD_SETSIZE),同时,在使用上,因为只有一个字段记录关注和发生事件,每次调用之前要重新初始化fd_set结构体。
- select例子:
- server.c:
#include <sys/time.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h> int main(){
int socket1, socket2;
struct sockaddr_in socket1addr, socket2addr;
fd_set fds;
struct timeval timeout = {, };
char buffer[] = {}; socket1 = socket(PF_INET, SOCK_DGRAM, );
//bzero(&socket1addr, sizeof(socket1addr));
socket1addr.sin_family = AF_INET;
socket1addr.sin_addr.s_addr = htonl(INADDR_ANY);
socket1addr.sin_port = htons();
bind(socket1, (struct sockaddr*)&socket1addr, sizeof(socket1addr)); socket2 = socket(PF_INET, SOCK_DGRAM, );
//bzero(&socket2addr, sizeof(socket2addr));
socket2addr.sin_family = AF_INET;
socket2addr.sin_addr.s_addr = htonl(INADDR_ANY);
socket2addr.sin_port = htons();
bind(socket2, (struct sockaddr*)&socket2addr, sizeof(socket2addr)); while(){
FD_ZERO(&fds);
FD_SET(socket1, &fds);
FD_SET(socket2, &fds);
int maxfdp = (socket1 > socket2) ? (socket1+) : (socket2 + );
int retval = select(maxfdp, &fds, NULL, NULL, &timeout);
if(retval == -){
printf("error\n");
return -;
}else if(retval == ){
continue;
}else{
struct sockaddr_in client;
int len = sizeof(client);
if(FD_ISSET(socket1, &fds)){
recvfrom(socket1, buffer, , , (struct sockaddr*)&client, &len);
printf("%u says:%s\n", ntohs(client.sin_port), buffer);
}
if(FD_ISSET(socket2, &fds)){
recvfrom(socket2, buffer, , , (struct sockaddr*)&client, &len);
printf("%u says:%s\n", ntohs(client.sin_port), buffer);
}
} }
} - client.c
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h> int main(int argc, char **argv)
{
if(argc < ){
printf("usage: %s port\n", argv[]);
return -;
}
int sockfd;
struct sockaddr_in servaddr; sockfd = socket(PF_INET, SOCK_DGRAM, ); bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[]));
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); char sendline[];
sprintf(sendline, "Hello, world!"); sendto(sockfd, sendline, strlen(sendline), , (struct sockaddr *)&servaddr, sizeof(servaddr)); close(sockfd); return ;
}
- server.c:
- poll解决了select的两个问题,通过一个pollfd数组向内核传递需要关注的事件消除文件句柄上限,同时使用不同字段分别标注关注事件和发生事件,来避免重复初始化。
poll函数原型:int poll(struct pollfd[], nfds_t nfds, int timeout);
1)struct pollfd的结构如下:
struct pollfd{
int fd; //文件描述符
short events; //请求的事件
short revents; //返回的事件
};
events和revents是通过对代表各种事件的标志进行逻辑或运算构建而成的。其中events是传入参数,revents是传出参数,即revents的填充是由内核来完成的。
fd代表一个文件描述符,当fd设置为负值时,则忽略events的设置并将revents设置为0,
2)nfds:要监视的描述符的个数,即pollfd[]的大小。
3)timeout:指定poll在返回前没有接受事件应该等待的事件。INFTIM表示永远等待,0表示立即返回不阻塞进程,>0表示等待指定时间。
poll函数事件的标识符值:
常量 | 说明 |
POLLIN | 普通或优先级带数据可读 |
POLLRDNORM | 普通数据可读 |
POLLRDBAND | 优先级带数据可读 |
POLLPRI | 高优先级数据可读 |
POLLOUT | 普通数据可写 |
POLLWRNORM | 普通数据可写 |
POLLWRBAND | 优先级带数据可写 |
POLLERR | 发生错误 |
POLLHUP | 发生挂起 |
POLLNVAL | 描述字不是一个打开的文件 |
poll问题:虽然poll解决了select中的两个问题,但是它仍然需要逐个排查所有文件句柄状态,效率不高。
poll函数例子:
- server.c:
#include <stdio.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/poll.h> int main(){
struct pollfd pollfd[];
int socket1, socket2;
struct sockaddr_in socket1addr, socket2addr; socket1 = socket(PF_INET, SOCK_DGRAM, );
socket1addr.sin_family = AF_INET;
socket1addr.sin_addr.s_addr = htonl(INADDR_ANY);
socket1addr.sin_port = htons();
bind(socket1, (struct sockaddr*)&socket1addr, sizeof(socket1addr)); socket2 = socket(PF_INET, SOCK_DGRAM, );
socket2addr.sin_family = AF_INET;
socket2addr.sin_addr.s_addr = htonl(INADDR_ANY);
socket2addr.sin_port = htons();
bind(socket2, (struct sockaddr*)&socket2addr, sizeof(socket2addr)); pollfd[].fd = socket1;
pollfd[].events = POLLIN;
pollfd[].fd = socket2;
pollfd[].events = POLLIN; while(){
int numready = poll(pollfd, , -);
if(numready == -){
break;
}else if(numready == ){
continue;
}else{
char buffer[]={};
struct sockaddr_in client;
int i, len = sizeof(client);
for(i=; i< ; i++){
if(pollfd[i].revents & POLLIN){
recvfrom(pollfd[i].fd, buffer, , , (struct sockaddr*)&client, &len);
printf("%d says %s\n", ntohs(client.sin_port), buffer);
numready--;
}
}
}
}
int i;
for(i=; i<; i++){
close(pollfd[i].fd);
} return ;
} - client.c:
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h> int main(int argc, char **argv)
{
if(argc < ){
printf("usage: %s port\n", argv[]);
return -;
}
int sockfd;
struct sockaddr_in servaddr; sockfd = socket(PF_INET, SOCK_DGRAM, ); bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[]));
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); char sendline[];
sprintf(sendline, "Hello, world!"); sendto(sockfd, sendline, strlen(sendline), , (struct sockaddr *)&servaddr, sizeof(servaddr)); close(sockfd); return ;
}
- server.c:
- epoll
既然逐个排查所有文件句柄状态效率不高,很自然的,如果调用返回的时候只给应用提供发生了状态变化的文件句柄,这样效率就会高很多,epoll就是采用了这种设计,使用与大规模 的应用场景。
epoll的使用:epoll用到三个函数:epoll_create、epoll_ctl、epoll_wait。
函数原型:
int epoll_create(int size)
该函数生成一个epoll专用的文件描述符(即返回值),size指定生成描述符的最大范围。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event event)
该函数用于控制某个文件描述符上的事件,可以注册事件,修改事件,删除事件。调用成功返回0,失败返回-1。
epfd:由epoll_create生成的专用文件描述符
op:要进行的操作,可能取值:EPOLL_CTL_ADD(注册),EPOLL_CTL_MOD(修改),EPOLL_CTL_DEL(删除)。
fd:关联的文件描述符
event:指向epoll_event的指针
int epoll_wait(int epfd,struct epoll_event events,int maxevents,int timeout)
该函数用于轮询事件的发生。返回发生的事件数,-1表示有错误。
epfd:由epoll_create生成的专用文件描述符
epoll_event:用于回传待处理事件的数组
maxevents:每次能处理的事件数
timeout:等待事件发生的超时值,-1永远等待直到有事件发生。
数据结构:
typedef union epoll_data {
void ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t; struct epoll_event {
__uint32_t events; / Epoll events /
epoll_data_t data; / User data variable /
};
结构体epoll_event 被用于注册所感兴趣的事件和回传所发生待处理的事件,其中epoll_data 联合体用来保存触发事件的某个文件描述符相关的数据,例如一个client连接到服务器, 服务器通过调用accept函数可以得到于这个client对应 的socket文件描述符,可以把这文件描述符赋给epoll_data的fd字段以便后面的读写操作在这个文件描述符上进行。 epoll_event 结构体的events字段是表示感兴趣的事件和被触发的事件可能的取值为:
EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET:表示对应的文件描述符设定为edge模式;
使用例子:
server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h> int main(){
struct epoll_event ev1, ev2, events[];
int epfd = epoll_create();
int socket1, socket2;
struct sockaddr_in socket1addr, socket2addr; socket1 = socket(PF_INET, SOCK_DGRAM, );
socket1addr.sin_family = AF_INET;
socket1addr.sin_addr.s_addr = htonl(INADDR_ANY);
socket1addr.sin_port = htons();
bind(socket1, (struct sockaddr*)&socket1addr, sizeof(socket1addr)); socket2 = socket(PF_INET, SOCK_DGRAM, );
socket2addr.sin_family = AF_INET;
socket2addr.sin_addr.s_addr = htonl(INADDR_ANY);
socket2addr.sin_port = htons();
bind(socket2, (struct sockaddr*)&socket2addr, sizeof(socket2addr)); ev1.data.fd = socket1;
ev1.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, socket1, &ev1); ev2.data.fd = socket2;
ev2.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, socket2, &ev2); while(){
int nfds = epoll_wait(epfd, events, , );
int i;
for(i=; i<nfds; i++){
char buffer[];
struct sockaddr_in client;
int len = sizeof(client);
if((events[i].data.fd == socket1) && (events[i].events&&EPOLLIN)){
recvfrom(socket1, buffer, , , (struct sockaddr*)&client, &len);
printf("%u says:%s\n", ntohs(client.sin_port), buffer);
}else if((events[i].data.fd == socket2) && (events[i].events&&EPOLLIN)){
recvfrom(socket2, buffer, , , (struct sockaddr*)&client, &len);
printf("%u says:%s\n", ntohs(client.sin_port), buffer);
}
}
}
}
client.c
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h> int main(int argc, char **argv)
{
if(argc < ){
printf("usage: %s port\n", argv[]);
return -;
}
int sockfd;
struct sockaddr_in servaddr; sockfd = socket(PF_INET, SOCK_DGRAM, ); bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[]));
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); char sendline[];
sprintf(sendline, "Hello, world!"); sendto(sockfd, sendline, strlen(sendline), , (struct sockaddr *)&servaddr, sizeof(servaddr)); close(sockfd); return ;
}
Epoll的ET模式与LT模式
ET(Edge Triggered)与LT(Level Triggered)的主要区别可以从下面的例子看出
eg:
1. 标示管道读者的文件句柄注册到epoll中;
2. 管道写者向管道中写入2KB的数据;
3. 调用epoll_wait可以获得管道读者为已就绪的文件句柄;
4. 管道读者读取1KB的数据
5. 一次epoll_wait调用完成
如果是ET模式,管道中剩余的1KB被挂起,再次调用epoll_wait,得不到管道读者的文件句柄,除非有新的数据写入管道。如果是LT模式,只要管道中有数据可读,每次调用epoll_wait都会触发。
另一点区别就是设为ET模式的文件句柄必须是非阻塞的
select、poll、epoll用法的更多相关文章
- select,poll,epoll用法
http://blog.csdn.net/sunboy_2050/article/details/6126712 select用法 #include <sys/time.h> ...
- select/poll/epoll on serial port
In this article, I will use three asynchronous conferencing--select, poll and epoll on serial port t ...
- Linux下select&poll&epoll的实现原理(一)
最近简单看了一把 linux-3.10.25 kernel中select/poll/epoll这个几个IO事件检测API的实现.此处做一些记录.其基本的原理是相同的,流程如下 先依次调用fd对应的st ...
- Python之路-python(Queue队列、进程、Gevent协程、Select\Poll\Epoll异步IO与事件驱动)
一.进程: 1.语法 2.进程间通讯 3.进程池 二.Gevent协程 三.Select\Poll\Epoll异步IO与事件驱动 一.进程: 1.语法 简单的启动线程语法 def run(name): ...
- 多进程、协程、事件驱动及select poll epoll
目录 -多线程使用场景 -多进程 --简单的一个多进程例子 --进程间数据的交互实现方法 ---通过Queues和Pipe可以实现进程间数据的传递,但是不能实现数据的共享 ---Queues ---P ...
- Python自动化 【第十篇】:Python进阶-多进程/协程/事件驱动与Select\Poll\Epoll异步IO
本节内容: 多进程 协程 事件驱动与Select\Poll\Epoll异步IO 1. 多进程 启动多个进程 进程中启进程 父进程与子进程 进程间通信 不同进程间内存是不共享的,要想实现两个进程间 ...
- select,poll,epoll的归纳总结区分
Select.Poll与Epoll比较 以下资料都是来自网上搜集整理.引用源详见文章末尾. 1 Select.Poll与Epoll简介 Select select本质上是通过设置或者检查存放fd标志位 ...
- 转一贴,今天实在写累了,也看累了--【Python异步非阻塞IO多路复用Select/Poll/Epoll使用】
下面这篇,原理理解了, 再结合 这一周来的心得体会,整个框架就差不多了... http://www.haiyun.me/archives/1056.html 有许多封装好的异步非阻塞IO多路复用框架, ...
- select.poll,epoll的区别与应用
先讲讲同步I/O的五大模型 阻塞式I/O, 非阻塞式I/O, I/O复用,信号驱动I/O(SIGIO),异步I/O模型 而select/poll/epoll属于I/O复用模型 select函数 该函数 ...
- select poll epoll三者之间的比较
一.概述 说到Linux下的IO复用,系统提供了三个系统调用,分别是select poll epoll.那么这三者之间有什么不同呢,什么时候使用三个之间的其中一个呢? 下面,我将从系统调用原型来分析其 ...
随机推荐
- yarn的初步理解
查考site: http://hadoop.apache.org/docs/r2.6.0/hadoop-yarn/hadoop-yarn-site/YARN.html yarn结构图如下: 1.yar ...
- 【Javascript&Jquery基础归纳】- 加载相关
1.window.onload 必须等到Dom所有元素.包括图片加载完毕后加载,只能编写一个. 2.$(document).ready() DOM结构加载完毕后马上执行,并且可以编写多个. ...
- redis中各种数据类型对应的jedis操作命令
redis中各种数据类型对应的jedis操作命令 一.常用数据类型简介: redis常用五种数据类型:string,hash,list,set,zset(sorted set). 1.String类型 ...
- Hadoop最基本的wordcount(统计词频)
package com.uniclick.dapa.dstest; import java.io.IOException; import java.net.URI; import org.apache ...
- Java keyword具体解释
訪问控制修饰符号 1) private 私有的 private keyword是訪问控制修饰符,能够应用于类.方法或字段(在类中声明的变量). 仅仅能在声明 private(内部)类.方 ...
- highgui.h备查 分类: C/C++ OpenCV 2014-11-08 18:11 292人阅读 评论(0) 收藏
/*M/////////////////////////////////////////////////////////////////////////////////////// // // IMP ...
- tcp ip参数详解
http://www.cnblogs.com/digdeep/p/4869010.html 1. TCP/IP模型 我们一般知道OSI的网络参考模型是分为7层:“应表会传网数物”——应用层,表示层,会 ...
- [转] SSH 密钥认证机制
使用 RSA 密钥对进行 SSH 登录验证 使用 RSA 密钥对验证 SSH 的优点是 1) 不用打密码 2) 比密码验证更安全:缺点是 1) 第一次配置的时候有点麻烦 2) 私钥需要小心保存.Any ...
- WKWebView使用过程中的那些坑
问题产生背景: 新开发的页面中有一部分的界面是需要展示后端接口返回的HTML代码,包括文字和图片.所以就自然而然的要使用iOS原生的WebKit. 鉴于Xcode 8发布以后,编译器支持的最低版本(D ...
- 第三篇:python基础之编码问题
python基础之编码问题 python基础之编码问题 本节内容 字符串编码问题由来 字符串编码解决方案 1.字符串编码问题由来 由于字符串编码是从ascii--->unicode---&g ...