linux网络编程 IO多路复用 select epoll
本文以我的小型聊天室为例,对于服务器端的代码,做了三次改进,我将分别介绍阻塞式IO
,select
,epoll
.
一:阻塞式IO
对于聊天室这种程序,我们最容易想到的是在服务器端accept
之后,然后fork
一个进程或者pthread_create
创建一个线程去处理相应的连接,代码如下 :
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/time.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#define PORT 8888
#define MAX_QUEEN_LENGTH 5000
void my_err(const char* msg,int line)
{
fprintf(stderr,"line:%d",line);
perror(msg);
}
int main(int argc,char *argv[])
{
int i;
int conn_len;
int sock_fd,conn_fd;
struct sockaddr_in serv_addr,conn_addr;
char recv_buf[1024];
int pid;
if((sock_fd = socket(AF_INET,SOCK_STREAM,0)) == -1) { //第三个参数的意思为0表示自动选择与第二个参数对应的协议.
my_err("socket",__LINE__);
exit(1);
}
memset(&serv_addr,0,sizeof(struct sockaddr_in));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sock_fd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr_in)) == -1) {
my_err("bind",__LINE__);
exit(1);
}
if(listen(sock_fd,MAX_QUEEN_LENGTH) == -1) {
my_err("sock",__LINE__);
exit(1);
}
conn_len = sizeof(struct sockaddr_in);
while(1) {
conn_fd = accept(sock_fd,(struct sockaddr *)&conn_addr,&conn_len);
pid = fork();
if(pid == 0) {
//有关conn_fd的操作
}
}
return 0;
}
问题:
当连接变的较多时,我们创建进程或者线程的开销很大,并且系统在不同的线程之间切换处理也非常的耗费资源.因此我们想少开线程.于是,select
就是很好的选择,我们可以在一个线程内监控多个文件描述符的状态,下面我们来学习select.
二:select
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
//参数解释
nfds:是select监控的三个文件描述符集合中所包括的最大文件描述符加一
readfds:fd_set类型,用来检测输入是否就绪的文件描述符集合
writefds:同readfds,是用来检测输出是否就绪的文件描述符集合
exceptfds:检测异常情况是否发生文件描述符集合
timeout:设置select的等待时间,如果两个域都为0,则select不会阻塞.
struct timeval {
time_t tv_sec;
suseconds_t tv_usec;
};
//有关select监控文件描述符集合的操作
void FD_ZERO(fd_set *fdset); //清空集合
void FD_SET(int fd,fd_set *fdset); //将fd加入fdset集合中
void FD_CLR(int fd,fd_set *fdset); //将fd从fdset中清除
int FD_ISSET(int fd,fd_set *fdset); //判断fd是否在集合中
下面我们看有关select的代码
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/time.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#define PORT 8888
#define MAX_QUEEN_LENGTH 5000
void my_err(const char* msg,int line)
{
fprintf(stderr,"line:%d",line);
perror(msg);
}
int main(int argc,char *argv[])
{
int i;
int conn_len; //
int sock_fd,conn_fd;
struct sockaddr_in serv_addr,conn_addr;
char recv_buf[1024];
int pid;
struct timeval tv; //select第5个参数,可以确定轮询检查的时间
fd_set rfds; //select的检查集合
int conn_fd_array[MAX_QUEEN_LENGTH] = {0}; //客户端连接fd数组
int conn_amount = 0; //目前客户端连接的数量
int maxsock,ret; //maxsock是select的第一个参数
if((sock_fd = socket(AF_INET,SOCK_STREAM,0)) == -1) { //第三个参数的意思为0表示自动选择与第二个参数对应的协议.
my_err("socket",__LINE__);
exit(1);
}
memset(&serv_addr,0,sizeof(struct sockaddr_in));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sock_fd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr_in)) == -1) {
my_err("bind",__LINE__);
exit(1);
}
if(listen(sock_fd,MAX_QUEEN_LENGTH) == -1) {
my_err("sock",__LINE__);
exit(1);
}
conn_len = sizeof(struct sockaddr_in);
maxsock = sock_fd; //目前的maxsock就是listen socket
while(1) {
FD_ZERO(&rfds); //清空集合,因为select每次都需要重新检查,所以清空操作每次都需要
FD_SET(sock_fd,&rfds); //将listen socket加入rfds集合中
tv.tv_sec = 1; //设置时间为1s,即1s后无事件发生就返回
tv.tv_usec = 0;
for(i = 0;i < MAX_QUEEN_LENGTH;i++) {
if(conn_fd_array[i] != 0) {
FD_SET(conn_fd_array[i],&rfds); //将所有都设置
}
}
ret = select(maxsock+1,&rfds,NULL,NULL,&tv);
if(ret < 0) {
my_err("select",__LINE__);
exit(1);
}
for(i = 0;i < conn_amount;i++) { //conn_amount:连接数量
if(FD_ISSET(conn_fd_array[i],&rfds)) {
if(recv(conn_fd_array[i],recv_buf,1024,0) != 1024) {
close(conn_fd_array[i]); //关闭文件描述符
FD_CLR(conn_fd_array[i],&rfds); //从rfds中清除
conn_fd_array[i] = 0; //让fd数组中归零
} else {
printf("%s\n",recv_buf);
}
}
}
if(FD_ISSET(sock_fd,&rfds)) { //如果有新的连接
if((conn_fd = accept(sock_fd,(struct sockaddr *)&conn_addr,&conn_len)) <= 0 ) {
my_err("accept",__LINE__);
exit(1);
}
for(i = 0;i < MAX_QUEEN_LENGTH;i++) { //实现了conn_fd_array[i]中元素重复使用
if(conn_fd_array[i] == 0) {
conn_fd_array[i] = conn_fd;
break;
}
}
conn_amount++;
if(conn_fd > maxsock) { //始终保证传给select的第一个参数是maxsock+1
maxsock = conn_fd;
}
}
}
return 0;
}
问题:
select虽然减小了开销,但是由于文件描述符集合有一个最大容量限制,由常量FD_SETSIZE来限制,在linux上,此值一般为1024,我们无法轻易的修改它,因此select的连接限制一般就是1024以下,这显然是不够用的,因此对于需要大量连接,需要大量文件描述符的情况,epoll更加有用.
三:epoll
epoll的核心数据结构是epoll实例,它和一个打开的文件描述符相关联,这个文件描述符不是用来做IO操作的,它是内核数据结构的句柄.epoll主要有下面三个API
#include<epoll.h>
int epoll_create(int size);
参数:size指定了我们想要通过epoll案例来检查的文件描述符个数
返回值:代表创建的epoll实例的文件描述符
int epoll_ctl(int epfd,int op,int fd,struct epoll *ev);
epfd:是epoll_create函数返回的代表epoll实例的文件描述符
op:定义了需要执行的操作
EPOLL_CTL_ADD:将描述符fd添加到epoll实例epfd的兴趣列表中去.
EPOLL_CTL_MOD:修改描述符fd上设定的事件,需要用到由ev所指向的结构体的信息.
EPOLL_CTL_DEL:将文件描述符fd从epfd的兴趣列表中删除.
events:定义了下面的操作
EPOLLIN:读操作
EPOLLOUT:写操作
EPOLLRDHUP:套接字对端关闭
EPOLLLONESHOT:在完成事件通知之后禁用检查
//event的结构体
struct epoll_event {
uint32_t events;
epoll_data_t data;
};
typedef union epoll_data {
void *ptr;
int fd; //文件描述符
unit32_t u32;
unit64_t u64;
}epoll_data_t;
int epoll_wait(int epfd,struct epoll_event *evlist,int maxevents,int timeout);
epfd:是epoll_create函数返回的代表epoll实例的文件描述符
evlist:所指向的结构体数组中返回的是有关就绪状态文件描述符的信息
maxevents:最大的连接数
timeout:
-1:调用将一直被阻塞,直到兴趣列表中文件描述符上有事件发生.
0:执行一次非阻塞的检查,看兴趣列表中文件描述符上产生了哪个事件
>0:阻塞最多timeout毫秒,直到文件描述符上有事件发生.
下面我们看epoll的使用代码
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<errno.h>
#include<unistd.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<sys/epoll.h>
#define PORT 8888
#define MAX_LENGTH 1024
void my_err(const char* msg,int line)
{
fprintf(stderr,"line:%d",line);
perror(msg);
exit(1);
}
int main(int argc,char *argv[])
{
int i;
int listen_fd,conn_fd,sock_fd;
struct sockaddr_in serv_addr,conn_addr;
struct epoll_event ev,events[MAX_LENGTH] = {-1};
int nfds,epollfd;
int conn_len = sizeof(struct sockaddr_in);
char recv_buf[1024];
if((listen_fd = socket(AF_INET,SOCK_STREAM,0)) == -1) {
my_err("socket",__LINE__);
}
memset(&serv_addr,0,sizeof(struct sockaddr_in));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(listen_fd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr_in)) == -1) {
my_err("bind",__LINE__);
}
if(listen(listen_fd,MAX_LENGTH) == -1) {
my_err("listen",__LINE__);
}
if((epollfd = epoll_create(MAX_LENGTH)) == -1) { //创建一个epoll实例
my_err("epoll",__LINE__);
}
ev.events = EPOLLIN; //设置事件为读
ev.data.fd = listen_fd; //设置文件描述符为监听套接字
epoll_ctl(epollfd,EPOLL_CTL_ADD,listen_fd,&ev); .//将监听套接字加入epollfd
while(1) {
nfds = epoll_wait(epollfd,events,1024,500); //event是一个数组
for(i = 0;i < nfds;i++) {
if(events[i].data.fd == listen_fd) { //连接请求
conn_fd = accept(listen_fd,(struct sockaddr *)&conn_addr,&conn_len);
printf("accept a new collection : %s\n",inet_ntoa(conn_addr.sin_addr));
ev.data.fd = conn_fd;
ev.events = EPOLLIN;
epoll_ctl(epollfd,EPOLL_CTL_ADD,conn_fd,&ev);
}
else if(events[i].events & EPOLLIN) { //读事件
if((sock_fd = events[i].data.fd) < 0 ) {
continue;
}
if((i = recv(sock_fd,recv_buf,1024,0)) != 1024) {
close(sock_fd);
events[i].data.fd = -1;
} else {
printf("%s\n",recv_buf);
}
ev.data.fd = sock_fd;
ev.events = EPOLLOUT;
epoll_ctl(epollfd,EPOLL_CTL_MOD,sock_fd,&ev);
}
else if(events[i].events & EPOLLOUT) { //写事件
sock_fd = events[i].data.fd;
ev.data.fd = sock_fd;
ev.events = EPOLLIN; epoll_ctl(epollfd,EPOLL_CTL_MOD,sock_fd,&ev);
}
}
}
return 0;
}
下面我们看下epoll和select的性能对比表(来自Linux/Unix 系统编程手册)
被监视的文件描述符数量 | select占用CPU时间(秒) | epoll占用CPU时间(秒) |
---|---|---|
10 | 0.73 | 0.41 |
100 | 3.0 | 0.42 |
1000 | 35 | 0.53 |
10000 | 930 | 0.66 |
因此我们不难看出epoll
的强大,它更加适合需要同时处理许多客户端的服务器,特别是需要监控的文件描述符数量巨大,但是大多数处于空闲状态,只有少部分处于就绪状态.
select和epoll性能差别原因:
- 每次调用select,内核都必须检查所有被指定的文件描述符,当大量检查时,耗费时间大.
- 每次调用select,程序都必须传递一个表示所有被检查的文件描述符到内核,内核通过检查文件描述符后,修改这个数据结构返回给程序,但是内核态和用户态之间切换的效率非常低.
- select完成之后,之后的程序必须检查返回的数据结构中的每一个元素.这样每次循环消耗非常大.
linux网络编程 IO多路复用 select epoll的更多相关文章
- python 网络编程 IO多路复用之epoll
python网络编程——IO多路复用之epoll 1.内核EPOLL模型讲解 此部分参考http://blog.csdn.net/mango_song/article/details/4264 ...
- python网络编程——IO多路复用之epoll
1.内核EPOLL模型讲解 此部分参考http://blog.csdn.net/mango_song/article/details/42643971博文并整理 首先我们来定义流的概念,一个流 ...
- python网络编程——IO多路复用select/poll/epoll的使用
转载博客: http://www.haiyun.me/archives/1056.html http://www.cnblogs.com/coser/archive/2012/01/06/231521 ...
- python网络编程——IO多路复用之select
1 IO多路复用的概念 原生socket客户端在与服务端建立连接时,即服务端调用accept方法时是阻塞的,同时服务端和客户端在收发数据(调用recv.send.sendall)时也是阻塞的.原生so ...
- Linux网络编程-IO复用技术
IO复用是Linux中的IO模型之一,IO复用就是进程预先告诉内核需要监视的IO条件,使得内核一旦发现进程指定的一个或多个IO条件就绪,就通过进程进程处理,从而不会在单个IO上阻塞了.Linux中,提 ...
- linux网络编程IO模型
同步与异步: 同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成. 异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要 ...
- linux 网络编程 3---(io多路复用,tcp并发)
1,io模型: 阻塞io.非阻塞io.io多路复用,信号驱动io. 阻塞Io与非阻塞io的转换,可用fcntl()函数 #include<unistd.h> #include<fcn ...
- Linux 网络编程九(select应用--大并发处理)
//网络编程服务端 /* * 备注:因为客户端代码.辅助方法代码和epoll相同,所以select只展示服务器端代码 */ #include <stdio.h> #include < ...
- LINUX网络编程 IO 复用
参考<linux高性能服务器编程> LINUX下处理多个连接时候,仅仅使用多线程和原始socket函数,效率十分低下 于是就出现了selelct poll epoll等IO复用函数. 这 ...
随机推荐
- git 合并分支 git merge branch_name
* 查看分支 git branch * 更新 git pull * 切换到master分支 git checkout master Checking out files: 100% (907/907) ...
- django 自定义auth中user登陆认证以及自写认证
第一种: 重写自定义auth中user登陆认证模块, 引入MobelBackend from django.contrib.auth.backends import ModelBackend 重写验证 ...
- vivo全球商城-营销价格监控方案的探索
一.背景 现在日常官网商城的运营中有一定概率出现以下两个问题: 1)优惠信息未对齐 官网商城促销优惠的类型越来越多,能影响最终用户实付价的优惠就有抢购.满减.优惠券.代金券等.实际业务操作中存在不同促 ...
- 数据库语法整理及WAF绕过方式
关系型数据库 关系型数据库:指采用了关系模型来组织数据的数据库. 直白的说就是:关系型数据库最典型的数据结构是表,由二维表及其之间的联系所组成的一个数据组织 当今主流的关系型数据库有:Oracle,M ...
- 【.NET 与树莓派】气压传感器——BMP180
BMP180 是一款数字气压计传感器,实际可读出温度和气压值.此模块使用 IIC(i2c)协议.模块体积很小,比老周的大拇指指甲还小:也很便宜,一般是长这样的.螺丝孔只开一个,也有开两个孔的. 这货基 ...
- 讲师征集| .NET Conf China 2021正式启动
最近社区小伙伴们一直在为11月即将在武汉举办的 第三届.NET中国峰会而忙碌,社区活动官网设计和开发工作还在进行,我们在国庆节的前一天晚上向社区正式启动了活动的序幕,也就是我们确定好了举办地点.时间, ...
- 密码学系列之:加密货币中的scrypt算法
目录 简介 scrypt算法 scrypt算法详解 scrypt的使用 简介 为了抵御密码破解,科学家们想出了很多种方法,比如对密码进行混淆加盐操作,对密码进行模式变换和组合.但是这些算法逐渐被一些特 ...
- std::sort 的注意事项
Luogu P1177 [模板]快速排序 \(\Large{AC}\) 代码: #include<bits/stdc++.h> using namespace std; int n,a[1 ...
- 题解 「SDOI2017」硬币游戏
题目传送门 Description 周末同学们非常无聊,有人提议,咱们扔硬币玩吧,谁扔的硬币正面次数多谁胜利. 大家纷纷觉得这个游戏非常符合同学们的特色,但只是扔硬币实在是太单调了. 同学们觉得要加强 ...
- Oracle-绑定执行计划
一.绑定执行计划 Oracle存在某个SQL多个执行计划的情况,那么如何快速将Oracle 好的执行计划,绑定到不好的SQL上呢? 由于版本的演进,绑定执行计划在10g 一般使用sql profile ...