优于 select 的 epoll (I/O 复用)

select 速度慢的原因

  • 调用select后针对全部文件描写叙述符的循环
  • 每次调用函数时都须要向该函数传递监视对象信息

select并非把发生变化的文件描写叙述符单独集中到一起。而是通过观察作为监视对象的fd_set函数的变化,因此不能避免对全部监视对象的循环语句。

并且,监视对象变量会发生变化,在调用select函数之前要复制并保存原有信息。并在每次调用时传递新的监视对象信息

传递新的监视对象信息是资源消耗的主要原因,由于每次都是向操作系统传递监视对象信息。

对程序负担非常大。

“为何要向操作系统传递监视对象信息呢?”

select须要监视套接字变化,而套接字属于操作系统全部,因此陷入内核态运行会导致非常大的上下文切换负担。

“仅向操作系统传递1次监视对象,监视范围或内容发生变化时仅仅通知发生变化的事项”,是不是非常棒的想法???

Linux支持的方式是epollWindows支持的方式是IDCP

select 的长处

  • server端接入者少
  • 程序应该具有兼容性

实现 epoll 必要的函数和结构体

epoll 长处

  • 无需编写以监视状态变化为目的的针对全部文件描写叙述符的循环语句
  • 调用对应于select函数的epoll_wait函数时无需每次传递监视对象

epoll server端实现须要的3个函数

  1. epoll_create: 创建保存epoll文件描写叙述符的空间(在操作系统中申请)
  2. epoll_ctl: 向空间注冊(注销)文件描写叙述符
  3. epoll_wait: 等待文件描写叙述符发生变化
struct epoll_event
{
__uint32_t events;
epoll_data_t data;
} typedef union epoll_data
{
void * ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;

声明足够大的epoll_event结构体数组后,传递给epoll_wait函数,发生变化的文件描写叙述符信息将被填入该数组。

#include <sys/epoll.h>

int epoll_create(int size); // 成功返回 epoll 文件描写叙述符,失败返回 -1

~ size: epoll 实例的大小

调用该函数时创建的文件描写叙述符保存空间称为“epoll例程”。函数返回的文件描写叙述符主要用于区分epoll例程。

生成epoll例程后。应在其内部注冊监视对象文件描写叙述符。

#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event * event); // 成功返回0。 失败返回-1

~ epdf: 用于注冊监视对象的epoll例程的文件描写叙述符
~ op: 用于指定监视对象的加入、删除和更改等操作
~ fd: 须要注冊的监视对象文件描写叙述符
~ event: 监视对象的事件类型

调用形式例如以下:

epoll_ctl(A, EPOLL_CTL_ADD, B, C);

epoll 例程A中注冊文件描写叙述符B,主要目的是监视參数C中的事件

epoll_ctl(A, EPOLL_CTL_DEL, B, NULL);

从epoll例程A中删除文件描写叙述符B

EPOLL_CTL_MOD:更改注冊的文件描写叙述符的关注事件发生情况

epoll_event结构体用于保存发生事件的文件描写叙述符集合。但也能够在epoll例程中注冊文件描写叙述符时。用于注冊关注的事件。

struct epoll_event event;
...
event.events = EPOLLIN; // 发生须要读取数据的情况时
event.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);
...

上述代码将sockfd注冊到epoll例程epfd中。并在须要读取数据的情况下产生响应事件。

  • EPOLL_IN: 须要读取数据的情况
  • EPOLL_OUT: 输出缓冲为空。能够马上发送数据的情况
  • EPOLLPRI: 收到OOB数据的情况
  • EPOLLRDHUP: 断开连接或半关闭的情况
  • EPOLLERR:错误发生的情况
  • EPOLLET: 以边缘触发的方式得到事件通知
  • EPOLLONESHOT: 发生一次事件后,对应文件描写叙述符不再收到事件通知。
#incude <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); // 成功返回发生事件的文件描写叙述符数。失败返回 -1

~ epdf: epoll 例程文件描写叙述符
~ events: 保存发生事件的文件描写叙述符集合的结构体地址值
~ maxevents: 第二个參数中保存的最大事件数
~ timeout: 以毫秒为单位的等待时间。-1 表示一直等待

使用方式例如以下:

int event_cnt;
struct epoll_event * ep_events;
...
ep_events = malloc(sizeof(struct epoll_event)*EPOLL_SIZE);
...
enent_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
...

调用函数后。返回发生事件的文件描写叙述符数,同一时候在第二个參数指向的缓冲中保存发生事件的文件描写叙述符集合。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h> #define BUF_SIZE 100
#define EPOLL_SIZE 50
void error_handling(char *bug); int main(int argc, char *argv[])
{
int serv_sock, clnt_sock; //server端的套接字和client套接字
struct sockaddr_in serv_adr, clnt_adr; //用于保存client和server端的地址信息
socklen_t adr_sz;
int str_len, i;
char buf[BUF_SIZE]; //输入输出的缓冲区 struct epoll_event *ep_events; // 保存发生事件的文件描写叙述符集合的结构体地址值
struct epoll_event event; // 用于注冊须要对应的事件
int epfd, event_cnt; if(argc != 2)
{
printf("Usage : %s <port> \n", argv[0]);
exit(1);
} serv_sock = socket(PF_INET, SOCK_STREAM, 0); // server端套接字
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_adr.sin_port = htons(atoi(argv[1])); if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1) // 绑定
error_handling("bind() error");
if(listen(serv_sock, 5) == -1) // 监听
error_handling("listen() error"); epfd = epoll_create(EPOLL_SIZE); // 创建epoll例程空间, 返回例程空间的文件描写叙述符
ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE); // 申请空间给epoll_wait函数,发生事件的文件描写叙述符将写入这个数组中。 数组大小(50)和之前创建例程空间的大小同样 event.events = EPOLLIN;
event.data.fd = serv_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event); // 将文件描写叙述符serv_sock加入到epoll例程epfd中,监视serv_sock的EPOLLIN事件 while(1)
{
event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1); // 返回值是发生事件的文件描写叙述符个数,操作系统将其依次填充在epoll_event数组中
if(event_cnt == -1)
{
puts("epoll_wait() error");
break;
} for(i=0; i<event_cnt; i++)
{
// 这里要区分是server端套接字还是client套接字发生事件
// server端发生事件那么要调用 accept 函数生成新的client套接字
// client发生事件那么会是发生了读写请求,后面还会再区分一次
// epoll 例程监视全部的套接字。仅仅能循环一遍才干发现发生事件的
// 套接字是不是server端套接字
if(ep_events[i].data.fd == serv_sock) // 发生事件的是server端套接字
{
adr_sz = sizeof(clnt_adr);
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
event.events = EPOLLIN;
event.data.fd = clnt_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event); //复用 event 将新的client套接字加入进epoll 例程
printf("connected client : %d \n", clnt_sock);
}
else // 发生事件的是client套接字,可是还要区分是读还是写
{
str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
if(str_len == 0) // 没有读到数据。由于client请求关闭 FIN
epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
close(ep_events[i].data.fd);
printf("closed client : %d \n", ep_events[i].data.fd);
}
else // 读取到了数据。将数据回显
{
write(ep_events[i].data.fd, buf, str_len);
}
}
} // end for
} // end while close(serv_sock);
close(epfd); // epoll 例程空间也是文件描写叙述符
return 0;
} void error_handling(char *buf)
{
fputs(buf, stderr);
fputc('\n', stderr);
exit(1);
}

TCP/IP 网络编程(五)的更多相关文章

  1. TCP/IP网络编程之多播与广播

    多播 多播方式的数据传输是基于UDP完成的,因此,与UDP服务端/客户端的实现非常接近.区别在于,UDP数据传输以单一目标进行,而多播数据同时传递到加入(注册)特定组的大量主机.换言之,采用多播方式时 ...

  2. TCP/IP网络编程之多进程服务端(二)

    信号处理 本章接上一章TCP/IP网络编程之多进程服务端(一),在上一章中,我们介绍了进程的创建和销毁,以及如何销毁僵尸进程.前面我们讲过,waitpid是非阻塞等待子进程销毁的函数,但有一个不好的缺 ...

  3. TCP/IP网络编程之基于TCP的服务端/客户端(一)

    理解TCP和UDP 根据数据传输方式的不同,基于网络协议的套接字一般分为TCP套接字和UDP套接字.因为TCP套接字是面向连接的,因此又称为基于流(stream)的套接字.TCP是Transmissi ...

  4. 浅谈TCP/IP网络编程中socket的行为

    我认为,想要熟练掌握Linux下的TCP/IP网络编程,至少有三个层面的知识需要熟悉: 1. TCP/IP协议(如连接的建立和终止.重传和确认.滑动窗口和拥塞控制等等) 2. Socket I/O系统 ...

  5. 《TCP/IP网络编程》

    <TCP/IP网络编程> 基本信息 作者: (韩)尹圣雨 译者: 金国哲 丛书名: 图灵程序设计丛书 出版社:人民邮电出版社 ISBN:9787115358851 上架时间:2014-6- ...

  6. TCP/IP网络编程系列之四(初级)

    TCP/IP网络编程系列之四-基于TCP的服务端/客户端 理解TCP和UDP 根据数据传输方式的不同,基于网络协议的套接字一般分为TCP和UDP套接字.因为TCP套接字是面向连接的,因此又称为基于流的 ...

  7. TCP/IP网络编程系列之三(初级)

    TCP/IP网络编程系列之三-地址族与数据序列 分配给套接字的IP地址和端口 IP是Internet Protocol (网络协议)的简写,是为首发网络数据而分配给计算机的值.端口号并非赋予计算机值, ...

  8. TCP/IP网络编程系列之二(初级)

    套接字类型与协议设置 我们先了解一下创建套接字的那个函数 int socket(int domain,int type,int protocol);成功时返回文件描述符,失败时返回-1.其中,doma ...

  9. TCP/IP网络编程之多线程服务端的实现(二)

    线程存在的问题和临界区 上一章TCP/IP网络编程之多线程服务端的实现(一)的thread4.c中,我们发现多线程对同一变量进行加减,最后的结果居然不是我们预料之内的.其实,如果多执行几次程序,会发现 ...

  10. TCP/IP网络编程之优于select的epoll(二)

    基于epoll的回声服务端 在TCP/IP网络编程之优于select的epoll(一)这一章中,我们介绍了epoll的相关函数,接下来给出基于epoll的回声服务端示例. echo_epollserv ...

随机推荐

  1. python通过SSH登陆linux并操作

    使用python通过SSH登陆linux并操作 用的昨天刚接触到的库,在windows下通过paramiko来登录linux系统并执行了几个命令,基本算是初试成功,后面会接着学习的. 代码: > ...

  2. cygwin设置

    解决乱码问题 # 设置为中文环境,使提示成为中文  export LANG =" zh_CN.UTF-8 " # 输出为中文编码  export OUTPUT_CHARSET =& ...

  3. UVA 10229 Modular Fibonacci

    斐波那契取MOD.利用矩阵快速幂取模 http://www.cnblogs.com/Commence/p/3976132.html 代码: #include <map> #include ...

  4. C++ bitset类的使用与简介 [转载]

    有些程序要处理二进制位的有序集,每个位可能包含的是0(关)或1(开)的值.位是用来保存一组项或条件的yes/no信息(有时也称标志)的简洁方法.标准库提供了bitset类使得处理位集合更容易一些.要使 ...

  5. C#读取JSON字符串

    下面这个是一段JSON字符串宏观图 下面我们通过C#读取JSON字符串里的任何一个数值 string jsonString="上面JSON字符串"; //需要引用Newtonsof ...

  6. AC日记——Count on a tree II spoj

    Count on a tree II 思路: 树上莫队: 先分块,然后,就好办了: 来,上代码: #include <cmath> #include <cstdio> #inc ...

  7. (转载)管道命令和xargs的区别(经典解释)

    一直弄不懂,管道不就是把前一个命令的结果作为参数给下一个命令吗,那在 | 后面加不加xargs有什么区别 NewUserFF 写道:懒蜗牛Gentoo 写道:管道是实现“将前面的标准输出作为后面的标准 ...

  8. HDU 6271 Master of Connected Component(2017 CCPC 杭州 H题,树分块 + 并查集的撤销)

    题目链接  2017 CCPC Hangzhou Problem H 思路:对树进行分块.把第一棵树分成$\sqrt{n}$块,第二棵树也分成$\sqrt{n}$块.    分块的时候满足每个块是一个 ...

  9. Python的功能模块[2] -> abc -> 利用 abc 建立抽象基类

    abc模块 / abc Module 在定义抽象方法时,为了在初始化阶段就检测是否对抽象方法进行了重定义,Python 提供了 abc 模块. from abc import ABCMeta, abs ...

  10. Lightoj 1348 Aladdin and the Return Journey (树链剖分)(线段树单点修改区间求和)

    Finally the Great Magical Lamp was in Aladdin's hand. Now he wanted to return home. But he didn't wa ...