多路复用_java多路复用

多路复用_java多路复用1、说明socket编程的demo中使用的都是最基本的,但是一般不会真正用在项目中的代码。而实际项目中,需要面临复杂多变的需求环境,比如有多个socket连接,或者服务需要监听的时候,可能有很多so

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

1、说明

socket编程的demo中使用的都是最基本的,但是一般不会真正用在项目中的代码。而实际项目中,需要面临复杂多变的需求环境,比如有多个socket连接,或者服务需要监听的时候,可能有很多socket连接进来。面对这种情况,最直接最简单的想法是,一个socket连接创建一个线程去处理。当然,在socket连接数较少的情况下,这种方式无可厚非,但是如果连接数量较大,就会出现意外情况。

我们都知道,linux下一个线程默认所占的内存是8M(可以使用ulimit -s查看),那么加入,1000个socket连接,建立1000线程,光线程的开销就高达8G多,更遑论其他业务还要使用内存了。

而且,在很多情况下,socket建立连接之后,并不是要一直通信,而是间隔通信,那么占用一个独立的线程来“照顾”这个连接显得很不明智。

针对这种情况,就需要采用多路复用机制,所谓多路复用,就是一个进程见识多个socket描述符,一旦某个socket描述符就绪(可读写或者异常)了,就会通知应用程序,进行相应的处理。

1.1、多路复用的几种机制

目前的多路复用机制有三种,select、poll 和 epoll。这三种机制各有优劣

2、函数简介

2.1、select

头文件和函数声明:

#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

FD_ZERO(int fd, fd_set* fds)   // 清空集合
FD_SET(int fd, fd_set* fds)    // 将给定的描述符加入集合
FD_ISSET(int fd, fd_set* fds)  // 判断指定描述符是否在集合中 
FD_CLR(int fd, fd_set* fds)    // 将给定的描述符从文件中删除

描述:

监听多个文件描述符的属性变化。

函数返回后,需要便利fd_set来找到就绪的描述符

参数说明:

nfds: 需要监听的描述符的范围,一般是最大描述符+1,比如,现在需要监听 0/1/2/3/4/5 这几个描述符,则参数设置为6,在linux下,值最大是1024

readfds: 监听到的可读的描述符的set,所有可读的描述符都会存储到这里

writefds: 监听到的可写的描述符的set

exceptfds: 监听到的异常的描述符set

timeout: select 方法的超时时间,这个参数决定 select 的运行机制,可能有三种值

  1. NULL,设置为空指针,则select阻塞运行
  2. 0,select 非阻塞运行,机制变为轮询,注意,不是参数传递0,参数传递0表示NULL
  3. >0,在指定的时间内阻塞,但有事件或者超时之后返回,返回值为有事件的描述符数量

返回值:

select 返回有事件的描述符数量,可以在对应的set中找到具体的描述符,错误则返回-1

优点: 跨平台

缺点:

  1. 描述符的数量受到限制,比如linux下最大1024;
  2. 每次调用,都需要将set从用户态复制到内核态,这在描述符多时,开销很大;
  3. 采用便利轮询的方式,多了无意义的损耗,比如,只需要监听99的描述符,但是内核会遍历0~100的描述符;

2.2、poll

头文件和函数声明:

#include <poll.h>

int poll (struct pollfd *fds, unsigned int nfds, int timeout);

struct pollfd {
    int fd; 	   /* 描述符 */
    short events;  /* 需要监听的事件 */
    short revents; /* 实际发生的事件 */
};

函数描述: 监听多个文件描述符的属性变化,和select类似,但是有很大区别,使用一个 pollfd 指针来替代 select 的三个set的功能

参数描述:

fd: 结构体指针,可以传入多个结构体,每个结构体都是一个被监听的描述符

nfds: 指定传入的结构体的数量

timeout: 超时时间,单位毫秒,-1 表示阻塞

返回值: 返回有事件的描述符数量,函数返回后,需要轮询来找到发生事件的描述符,错误则返回-1

pollfd 结构体:

fd: 表示描述符

events: 需要监听的事件掩码,取值如下

revents: 实际发生的事件掩码,取值如下

宏定义 可作events的值 可作revents的值 说明
POLLIN y y 数据可读
POLLRDNORM y y 普通数据可读
POLLRDBAND y y 优先数据可读
POLLPRI y y 紧迫带数据可读
POLLOUT y y 数据可写,不会阻塞
POLLWRNORM y y 普通数据可写,不会阻塞
POLLWRBAND y y 优先级带数据可写,不会阻塞
POLLMSGSIGPOLL y y 消息可用

非法事件

宏定义 可作events的值 可作revents的值 说明
POLLERR y 发生错误
POLLHUP y 发生挂起
POLLNVAL y 描述不是打开的文件

POLLIN | POLLPRI 等价于 select 的读事件,而 POLLIN 等价于 POLLRDNORM | POLLRDBAND

POLLOUT | POLLWRBAND 等价于 select 的写事件,而 POLLOUT 等价于 POLLWRNORM

这些事件不是互斥的,可以同时设置

优缺点:

和 select 相比,poll 没有了数量的限制,但是数量太大也会影响效率

poll 同样有着将传入的描述符从用户态复制到内核态的缺点,开销随着描述符数量的增大而线性增大

2.3、epoll

epoll是后来提出的,作为 select 和 poll 的增强版本

epoll 的操作需要三个接口,头文件和声明如下:

#include <sys/epoll.h> 

int epoll_create(int size);  
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);  

2.3.1、epoll_create

int epoll_create(int size); 

创建一个 epoll 专用的描述符,size 为监听的数目

size 参数并没有限制 epoll 监听的描述符的最大限制,而是作为内部分配数据结构的一个建议,linux自动2.6.8版本之后,该参数被忽略,只要大于0就行

返回一个描述符,使用结束时,需要close(),错误则返回-1

2.3.2、epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll 的事件注册,告诉 epoll 要监听哪些事件

返回0表示注册成功,-1表示失败

参数:

epfd: epoll 专用的描述符,由epoll_create() 返回

op: 该函数的作用,即注册(EPOLL_CTL_ADD)、修改(EPOLL_CTL_MOD)和删除(EPOLL_CTL_DEL)

fd: 需要监听的描述符

event: 告诉内核要监听什么事件,声明如下:

// 保存触发事件的某个文件描述符相关的数据(与具体使用方式有关)  
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 中的 events 可以是以下宏定义的集合

宏定义 说明
EPOLLIN 表示对应的文件描述符可以读(包括对端 SOCKET 正常关闭)
EPOLLOUT 表示对应的文件描述符可以写
EPOLLPRI 表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
EPOLLERR 表示对应的文件描述符发生错误
EPOLLHUP 表示对应的文件描述符被挂断
EPOLLET 将 EPOLL 设为边缘触发(Edge Trigger)模式,这是相对于水平触发(Level Trigger)来说的
EPOLLONESHOT 只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个 socket 的话,需要再次把这个 socket 加入到 EPOLL 队列里

2.3.3、epoll_wait

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); 

等待 epoll 监听下的 IO 事件

参数:

epfd: epoll 描述符

events: epoll 会把发生的事件赋值到events中

maxevents: 表明这个 events 的大小

timeout: 超时时间,单位毫秒,-1表示阻塞

返回值: 返回需要处理的事件数目,0表示超时未有事件,-1表示失败

2.4、其他方法

FD_ZERO(fd_set *fdset);	//清空一个描述符集合
FD_SET(fd_set *fdset, int fd);	//添加fd到描述符集合中
FD_CLR(fd_set *fdset, int fd);	//从描述符集合中删除一个fd
FD_ISSET(int fd,fd_set *fdset);	//检查fd是否在描述符集合中

3、epoll

epoll 的工作模式有两种:LT(level trigger) 和 ET(edge trigger),默认LT模式,区别如下:

LT模式: 当 epoll_wait 检测到描述符事件发生,并通知应用程序,应用程序可以不利己处理该事件,下次调用 epoll_wait 时,还是会通知此事件

ET模式: 当 epoll_wait 检测到描述符事件发生,并通知应用程序,应用程序必须立即处理该事件。如果不处理,则下次调用时,不会再次通知此事件

3.1、LT模式和ET模式

LT模式:

默认模式,支持阻塞和非阻塞方式,内核会通知事件发生,若果不做任何操作,则内核下次还是会通知,编程不易出错,select/poll都是这种模式

ET模式:

告诉你工作模式,只支持非阻塞。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)

ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

在 select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一 个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait() 时便得到通知。(此处去掉了遍历文件描述符,而是通过监听回调的的机制。这正是epoll的魅力所在。)

3.2、优缺点

  1. 监听数量不受限制,理论上上限是最大可以打开的文件数目,这个数目一般远大于2048,linux上可以使用 cat /proc/sys/fs/file-max 命令查看。select的最大缺点就是进程打开的fd是有数量限制的。这对 于连接数量比较大的服务器来说根本不能满足。虽然也可以选择多进程的解决方案( Apache就是这样实现的),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。
  2. IO的效率不会随着监视fd的数量的增长而下降。epoll不同于select和poll轮询的方式,而是通过每个fd定义的回调函数来实现的。只有就绪的fd才会执行回调函数。
  3. 文件描述符只需要复制一次到内核,不需要每一次调用函数都进行文件描述符的内核复制

如果没有大量的idle -connection或者dead-connection,epoll的效率并不会比select/poll高很多,但是当遇到大量的idle- connection,就会发现epoll的效率大大高于select/poll。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/168186.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 神器 Nginx 的学习手册 ( 建议收藏 )

    神器 Nginx 的学习手册 ( 建议收藏 )转载:DevOps技术栈Nginx是一个高性能的HTTP和反向代理服务器,特点是占用内存少,并发能力强,事实上Nginx的并发能力确实在同类型的网页服务器中表现较好。Nginx…

  • web安全色_WEB标准颜色

    web安全色_WEB标准颜色web安全色产生的原因不同的平台(Mac、PC等)有不同的调色板,不同的浏览器也有自己的调色板。这就意味着对于一幅图,显示在Mac上的Web浏览器中的图像,与它在PC上相同浏览器中显示的效果可能差别很

  • SilverlightQQ体验报告…

    SilverlightQQ体验报告…显示风格…太华丽了..风格2MacOS的滑动效果..太暴力了 另外一种显示风格.. 嘛是起飞??? 好友搜索 

  • EBC外汇交易平台_ecn外汇交易商排名

    EBC外汇交易平台_ecn外汇交易商排名外汇凭借公开透明、交易公平、24小时实时交易、高倍杠杆等独特的优势备受投资者推崇。相比于其他产品,外汇交易更需要做好风险和收益的动态平衡,建议投资者在选择投资平台时,优先选择专业高效的金融券商。EBC金融投资平台凭借其安全与收益的双重保障,受到越来越多投资者的追捧。安全保障:FCA+ASIC双认证通过,搭起坚实投资防护墙EBC金融始终将合规专业、为客户提供最大限度的资金安全保障作为基本要求,积极付出行动。EBC金融自成立初期起,将所有的资金都托管在世界顶级银行,接受监管机构严格、健全的监管体系之严格

  • mybatis自定义分页_java分页查询接口的实现

    mybatis自定义分页_java分页查询接口的实现问题出现原因是集成mybaits时会自动加上selecttmp_page.*,rownumrow_idfrom(查询语句)tmp_page出现这个问题的原因是查询语句的列有重复的,直接查询是看不出来原因的,把重复的列名找出来然后修改…

  • python中griddata的外插值_SciPyTutorial-多元插值griddata

    python中griddata的外插值_SciPyTutorial-多元插值griddata11.ScipyTutorial-多维插值griddatascipy.interpolate模块下的griddata函数可以处理多元(维)函数的插值,以二元函数$f(x,y)$为例说明一下griddata的使用。与之前的一元函数插值interp1d相区别,interp1d是通过已知的点集$P={(x_i,y_i)|x_i\inR,y_i\inR}$通过interp1d可以找…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号