网络通信IO(二)

本文介绍了Linux中三种IO多路复用机制:select、poll和epoll。select允许程序监视多个文件描述符,等待其中一个就绪。poll与select类似,但没有文件描述符数量限制。epoll作为增强版,解决了select和poll的限制,提供更高效的方式管理大量描述符,通过内核空间保存并只返回就绪的描述符,降低了系统调用的开销。
摘要由CSDN通过智能技术生成

多路复用器

概述

select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

select

 int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
...
DESCRIPTION
       select() and pselect() allow a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O operation (e.g., input possible).  A file descriptor is considered ready
       if it is possible to perform a corresponding I/O operation (e.g., read(2) without blocking, or a sufficiently small write(2)).

       select() can monitor only file descriptors numbers that are less than FD_SETSIZE; poll(2) does not have this limitation.  See BUGS.

select函数监视的文件描述分3类,分别是writefds、readfds和execptfds。调用后函数会阻塞,直到有描述符就绪(有数据 可读 可写 异常或者超时(timeout指定等待时间))函数返回;当select函数返回后,在通过遍历fdset,来找到就绪的描述符。
优点:select目前几乎在所有的平台上支持,
缺点:单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024;可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低。

poll

 int poll(struct pollfd *fds, nfds_t nfds, int timeout);
 ...
 DESCRIPTION
       poll() performs a similar task to select(2): it waits for one of a set of file descriptors to become ready to perform I/O.

       The set of file descriptors to be monitored is specified in the fds argument, which is an array of structures of the following form:

           struct pollfd {
               int   fd;         /* file descriptor */
               short events;     /* requested events */
               short revents;    /* returned events */
           };

       The caller should specify the number of items in the fds array in nfds.

不同于select使用三个位图来表示三个fdset的方式,poll使用一个pollfd的指针实现。
pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式。同时,pollfd并没有最大数量限制(但是数量过大后性能也是会下降)。 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。

在这里插入图片描述
select与poll的弊端:

  • 每次都需要重复传递fd
  • 每次都要重新遍历全量fd
    综上引出epoll;内核开辟空间保留fd

epoll

epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

解决select和poll的两个问题:

  • 在内核开辟空间,使每次系统调用的时候,不传递全量的fds
  • 通过epoll_ctl,让内核在cpu工作时,把IO状态变化的fd放入到ready区,用户程序一旦调用epoll_wait,则直接获取到那些fd。最优的时间复杂度变为O(1)
//创建一个epoll的句柄,size用来控制内核这个监听的数目一共有多大
int epoll_create(int size)/*
函数是对指定描述符fd执行op操作。
	epfd:是epoll_create()的返回值。
	op:表示op操作,用三个宏来表示:添加EPOLL_CTL_ADD,删除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分别添加、删除和修改对fd的监听事件。
	fd:是需要监听的fd(文件描述符)
	epoll_event:是告诉内核需要监听什么事,struct epoll_event结构如下:
*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)//等待epfd上的io事件,最多返回maxevents个事件
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

伪代码及日志片段

#伪代码
socket()=3   #返回socket文件描述符3
bind(3, 9090) #把socket和9090端口绑定起来
listen(3)     #监听FD 3
epoll_create() = 7    #通过epoll创建内核空间的fd存储空间:假设为7
epoll_ctl(7, ADD, 3, accept);    #往空间7里添加对socket3,关注事件为accept。即客户端一旦连接到3这个socket,这个fd就被选中
#用户程序调用内核(APP调用),获取哪些IO状态发生了变化。本方法是阻塞方法,但是可以传参代表超时时间,如阻塞500毫秒,超时返回-1
epoll_wait();                

#日志片段
socket(PF_INTE,SOCK_STREAM,IPPROTO_IP) = 4
fcntl(4,F_SETFL,O_RDWR|O_NONBLOCK) = 0
bind(4,{sa_family=AF_INET,sin_port=htons(9090)})
listen(4,50)
...
epoll_create(256)  = 7
epoll_ctl(7,EPOLL_CTL_ADD,4,
epoll_wait(7
...
#有连接时 在添加一个文件描述符 8
accept(4,{sa_family=AF_INET,sin_port=htons(53687)}) = 8
epoll_ctl(7,EPOLL_CTL_ADD,8,
#这时 7的空间里 有4和8文件描述符
epoll_wait(7

在这里插入图片描述

总结

无论BIO、NIO、多路复用,在linux系统下的网络通信都离不开: socket、bind、listen这三个系统调用。

佐证

java代码示例

public class SocketMultiplexingThread1 {

    private ServerSocketChannel server = null;
    private Selector selector = null;

    public void initServer(){
        try{
            //实例server 约等于listen状态的fd3
            server = ServerSocketChannel.open();
            server.configureBlocking(false);
            server.bind(new InetSocketAddress(9090));
            //得到的这个selector是jvm抽象的多路复用器 具体是select、poll还是epoll由不同的系统决定
            //open等价于底层epoll_create,在内核开辟了空间,存放所有的文件描述符fd
            //假设这里epoll_create 返回的是fd7,fd7代表的就是内核里面存放fd的空间,他自己也是一个fd,即fd7
            selector = Selector.open();
            //对select、poll来说,JVM开辟了空间,fd传进去
            //对epoll,epoll_ctl(fd7, EPOLL_CTL_ADD, fd3, EPOLLIN)  EPOLLIN既可能是客户端连接,也可能是数据到达
            server.register(selector, SelectionKey.OP_ACCEPT);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void start(){
        initServer();
        try{
            while (true){
                Set<SelectionKey> keys = selector.keys();
                /**
                 * 调用多路复用器
                 * selector.select(500)
                 * 对select、poll来说,== 内核的系统调用select(fd3)、poll(fd3)
                 * 对epoll来说,==内核的系统调用epoll_wait(500)
                 */
                while (selector.select(500)>0){
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();

                    while (iterator.hasNext()){
                        SelectionKey key = iterator.next();
                        iterator.remove();
                        if(key.isAcceptable()){

                        }else if(key.isReadable()){

                        }
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

strace追踪… 未完待续…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值