多路复用_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)


相关推荐

  • CTK Plugin Framework 基本原理

    CTK Plugin Framework 基本原理“CTKPluginFramework技术是面向C++的动态模型系统。该系统允许插件之间的松散耦合,并且提供了设计良好的方式来进行功能和数据…

  • 打包的技巧图解(手机软件打包)

    我们打包APP需要用到HBuilder,所以先讲解如何安装使用HBuilder的下载与安装HBuilder的官网下载地址:https://www.dcloud.io/点击DOWNLOAD后会弹出如上显示的两种版本下载Windows版和MacOS版根据我们电脑版本下载即可,我们一定要下载标准版哦(正式版中的)我这里是Windows系统,所以我选择的是window版下载完是一个压缩包右击压缩一下即可打开文件夹后找到如下HBuilderX.exe文件双击即可运行为了方便我们

  • 华为Mate40/华为Mate40Pro忘记密码怎么解锁激活手机设备已锁定恢复出厂无法解锁账户ID屏幕锁解除刷机方法教程[通俗易懂]

    华为Mate40/华为Mate40Pro忘记密码怎么解锁激活手机设备已锁定恢复出厂无法解锁账户ID屏幕锁解除刷机方法教程[通俗易懂]今天带来一台用户华为Mate40Pro手机强制清除华为账号锁案例分享,这个台手机是用户公司手机,由于前使用者离职后未能退出手机的华为账号和锁屏密码,导致手机无法使用。自己通过简单的恢复出厂设置后,发现手机有华为账号锁无法激活手机,这才联系到刷机爱好者技术人员,给予远程强制刷机移除华为Mate40Pro的账号锁。在此提醒广大用户,登录的华为账号建议绑定经常使用的手机号码,防止无法找回密码从而到时手机无法使用。在刷机解锁过程中需要准备以下工具:链接:百度网盘请输入提取码提取码:8888–来

  • 计算机-普林斯顿结构

    计算机-普林斯顿结构冯·诺伊曼结构,也称冯·诺伊曼模型或普林斯顿结构,是一种将程序指令存储器和数据存储器合并在一起的计算机设计概念结构。依据冯·诺伊曼结构设计出的计算机称做冯.诺依曼计算机,又称存储程序计算机。特点结构…

  • loadrunner安装包下载_loadrunner2021安装

    loadrunner安装包下载_loadrunner2021安装LoadRunner安装包下载:下载链接:https://pan.baidu.com/s/10BdYFXLPYdW6N7Q67D8mjQ提取码:a3piLoadRunner安装:下载后找到路径,点击下载后的HPELoadRunner12.55CommunityEdition.exe(安装版)运行,建议鼠标右键以管理员身份运行(运行的时候尽量右键管理员运行,如果没有选择右键管理员运行,会提示权限,点击是就可以啦)选择安装的地址(所有的安装地址不能有中文),可以选择默认地.

  • 在线检测笔记本电脑屏幕坏点

    新买了台电脑,装的linux.不知道怎么测试屏幕坏点.后来看到可以在线查看有没有坏点.链接地址记下来.给需要的朋友http://www.biyouti.com/tool/index.htm 转载于:https://blog.51cto.com/hebsun/1036768…

发表回复

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

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