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复用函数. 这 ...
随机推荐
- BATJ经典软件测试面试题我连答案都告诉你了,如果你不想月薪上万,那你别看!
单项选择题:共20小题,每小题1 分,满分20分:请将答案填入题后括号中. 1. 在软件生命周期的哪一个阶段,软件缺陷修复费用最低(A) A.需求分析(编制产品说明书) B.设计 C.编码 ...
- IdentityServer4[5]简化模式
Implicit简化模式(直接通过浏览器的链接跳转申请令牌) 简化模式是相对于授权码模式而言的.其不再需要[Client]的参与,所有的认证和授权都是通过浏览器来完成的. 创建项目 IdentityS ...
- sonarqube C# 单元测试覆盖率一栏总是0%解决办法
一.什么叫单元测试(unit testing)? 是指对软件中的最小可测试单元进行检查和验证.对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函 数,Java里 ...
- 四、mybatis动态sql
# 前言前面mysql都是通过静态sql进行查询的,但是如果业务复杂的时候,我们会遇到引号问题,或者多一个空格,这就使得sql代码编写错误了,所以为了解决这个问题,我们有了动态sql. Mybatis ...
- RuntimeError: DataLoader worker (pid 18255) is killed by signal: Killed.
RuntimeError: DataLoader worker (pid 18255) is killed by signal: Killed. 通过观察内存发现,数据加载过程中内存会被耗尽.
- 基于C++的ButeBuf封装
目录 1.说明 2.代码 1.说明 netty 的 ByteBuf 中的 readerIndex 和 writerIndex 的设置十分巧妙,它内部对读取和写入位置进行控制,避免自己处理index的时 ...
- 使用Golang搭建gRPC服务提供给.NetCore客户端调用
目录 gRPC概述 RPC gRPC又是什么呢 安装 Golang IDE(Goland) Protocol Buffer 下载Protocal Buffer 配置Protocal Buffer编译器 ...
- uniapp内嵌H5页面和uniapp页面相互传值
最近项目有一个需求 -- 做一个百人抽奖的模块,要求展示百人的头像并且不断变化排列组合 先展示一部分的用户头像,然后每增加一个用户就增加一个头像在百人排列里面 我整一个gif图来展示一下 大概就是这种 ...
- JVM 面试题,安排上了!!!
肝了一篇非常硬核的 JVM 基础总结,写作不易,小伙伴们赶紧点赞.转发安排起来! 原文链接 据说看完这篇 JVM 要一小时 JVM 的主要作用是什么? JVM 就是 Java Virtual Mach ...
- C#开发BIMFACE系列40 服务端API之模型集成
BIMFACE二次开发系列目录 [已更新最新开发文章,点击查看详细] 随着建筑信息化模型技术的发展,越来越多的人选择在云端浏览建筑模型.现阶段的云端模型浏览大多是基于文件级别,一次只可以浏览一 ...