Linux网络编程(四)

1.IO多路复用(IO多路转接)

I/O多路复用使得程序能同时监听多个文件描述符,能够提高程序的性能,Linux 下实现 I/O 多路复用的系统调用主要有 selectpollepoll

BIO(blocking IO)模型

通过多线程或进程处理多个客户端,每个线程/进程对应一个客户端
在这里插入图片描述

NIO模型

在这里插入图片描述

IO多路转接技术

select/poll:
在这里插入图片描述
epoll:
在这里插入图片描述

2.select

select原理

  1. 首先要构造一个关于文件描述符的列表,将要监听的文件描述符添加到该列表中;
  2. 调用一个系统函数(函数是阻塞的),监听该列表中的文件描述符,直到这些描述符中的一个或者多个进行I/O操作时,该函数才返回(函数对文件描述符的检测的操作是由内核完成的)。
  3. 在返回时,函数会告诉进程有多少(哪些)描述符要进行I/O操作。

select相关函数

// sizeof(fd_set) = 128 1024 
#include <sys/time.h> 
#include <sys/types.h> 
#include <unistd.h> 
#include <sys/select.h> 
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 
    参数: 
         - nfds : 委托内核检测的最大文件描述符的值 + 1 
         - readfds : 要检测的文件描述符的读的集合,委托内核检测哪些文件描述符的读的属性 
                     - 一般只检测读操作 
                     - 对应的是对方发送过来的数据,因为读是被动的接收数据,检测的就是读缓冲区 
                     - 是一个传入传出参数 
         - writefds : 要检测的文件描述符的写的集合,委托内核检测哪些文件描述符的写的属性 
                      - 委托内核检测写缓冲区是不是还可以写数据(不满的就可以写) 
         - exceptfds : 检测发生异常的文件描述符的集合
         - timeout : 设置的超时时间
                     struct timeval { 
                            long tv_sec; /* seconds */ 
                            long tv_usec; /* microseconds */ 
                     };
                     - NULL : 永久阻塞,直到检测到了文件描述符有变化 
                     - tv_sec = 0 tv_usec = 0, 不阻塞 
                     - tv_sec > 0 tv_usec > 0, 阻塞对应的时间 
         - 返回值 : - -1 : 失败 
                   - >0(n) : 检测的集合中有n个文件描述符发生了变化

// 将参数文件描述符fd对应的标志位设置为0 
void FD_CLR(int fd, fd_set *set); 
// 判断fd对应的标志位是0还是1, 返回值 : fd对应的标志位的值,0,返回0, 1,返回1 
int FD_ISSET(int fd, fd_set *set); 
// 将参数文件描述符fd 对应的标志位,设置为1 
void FD_SET(int fd, fd_set *set);
// fd_set一共有1024 bit, 全部初始化为0 
void FD_ZERO(fd_set *set);

select()工作过程分析

客户端A、B、C、D对应的描述符为3、4、100、101,并连接到服务器,
假设要检测读时间,那么首先创建fd_set_reads集合,fd_set有1024个比特位,每一个比特位对应一个文件描述符;
要检测这四个客户端是否有数据到达,就通过FD_SET()将客户端对应的描述符的标志位置为1,然后调用select(),将fd_set从用户态拷贝到内核态;
假设A、B发送了数据,那么内核从0遍历到101,当遍历到3-4时发现有数据,就将3-4置为1,当遍历到100-101时发现没有数据,就将100-101置为0;
内核检测完成后将fd_set从内核态拷贝到用户态,遍历fd_set发现3-4发生变化、接收到了数据。
在这里插入图片描述

在这里插入图片描述

select()缺点

在这里插入图片描述

3.poll

poll相关函数

对select()的改进,实现原理类似

#include <poll.h> 
struct pollfd { 
    int fd;         //委托内核检测的文件描述符
    short events;   //委托内核检测文件描述符的什么事件
    short revents;  //文件描述符实际发生的事件
};

例如:
struct pollfd myfd; 
myfd.fd = 5; 
myfd.events = POLLIN | POLLOUT; 

int poll(struct pollfd *fds, nfds_t nfds, int timeout); 
参数:
     - fds : 是一个struct pollfd 结构体数组,需要检测的文件描述符的集合 
     - nfds : 第一个参数数组中最后一个有效元素的下标 + 1 
     - timeout : 阻塞时长 
                 0 : 不阻塞 
                 -1 : 阻塞,当检测到需要检测的文件描述符有变化,解除阻塞 
                 >0 : 阻塞的时长 
返回值: 
       -1 : 失败 
       >0(n) : 成功,n表示检测到集合中有n个文件描述符发生变化

在这里插入图片描述

poll()缺点

poll()改进了select()支持的文件描述符数量太小和fds集合不能重用的缺点,但依然每次调用poll()时需要把fd集合从用户态拷贝到内核态,并且在内核态遍历传递进来的所有fd.

4.epoll()

epoll原理

通过epoll_create在内核创建一个epoll实例(eventpoll结构体类型),返回值是一个文件描述符,可以通过文件描述符使用epoll API操作内核中的epoll实例;
通过epoll_ctl()委托内核向eventpoll数据结构中添加要检测数据发送改变的文件描述符的信息,在eventpoll数据结构中,有两个重要的成员:rbr是需要检测的文件描述符的信息(红黑树)rdlist是就绪列表,存放检测到数据发生改变的文件描述符信息(双向链表)
通过epoll_wait()委托内核检测eventpoll数据结构中是否有数据发生改变,如果有就发生改变的文件描述符信息存储到rdlist中,并将改变了的数据返回;
最后通过for循环遍历数据发生改变的数据进行读写操作。
在这里插入图片描述

epoll相关函数

#include <sys/epoll.h> 
int epoll_create(int size); 
参数:
     size : 没有意义,任意一个大于0的数都可 
返回值: -1 : 失败 
        > 0 : 文件描述符,操作epoll实例的

// 对epoll实例进行管理:添加文件描述符信息,删除信息,修改信息 
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 
    参数: 
         - epfd : epoll实例对应的文件描述符 
         - op : 要进行什么操作 
                EPOLL_CTL_ADD: 添加 
                EPOLL_CTL_MOD: 修改 
                EPOLL_CTL_DEL: 删除 
         - fd : 要检测的文件描述符 
         - event : 检测文件描述符什么事情,读还是写

struct epoll_event { 
    uint32_t events;      //Epoll检测事件
    epoll_data_t data;    //用户数据信息
};
常见的Epoll检测事件: 
   - EPOLLIN
   - EPOLLOUT 
   - EPOLLERR 
typedef union epoll_data { 
    void *ptr; 
    int fd; 
    uint32_t u32; 
    uint64_t u64; 
} epoll_data_t; 

// 检测函数 
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 
    参数:
         - epfd : epoll实例对应的文件描述符 
         - events : 传出参数,保存了发送了变化的文件描述符的信息(与epoll_ctl不一样)
         - maxevents : 第二个参数结构体数组的大小 
         - timeout : 阻塞时间 
                     0 : 不阻塞 
                     -1 : 阻塞,直到检测到fd数据发生变化,解除阻塞 - 
                     > 0 : 阻塞的时长(毫秒) 
返回值: - 成功,返回发送变化的文件描述符的个数 > 0 
        - 失败,返回-1

epoll的两种工作模式

LT模式(水平触发)

LT(level - triggered)是缺省的工作方式,并且同时支持 block 和 no-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的 fd 进行 IO 操作。如果你不作任何操作,内核还是会继续通知你的。

示例:
假设委托内核检测读事件 -> 检测fd的读缓冲区
读缓冲区有数据 - > epoll检测到了会给用户通知
a.用户不读数据,数据一直在缓冲区,epoll 会一直通知
b.用户只读了一部分数据,epoll会通知
c.缓冲区的数据读完了,不通知

ET模式(边沿触发)

ET(edge - triggered)是高速工作方式,只支持 no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。但是请注意,如果一直不对这个 fd 作 IO 操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。

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

示例:
假设委托内核检测读事件 -> 检测fd的读缓冲区
读缓冲区有数据 - > epoll检测到了会给用户通知
a.用户不读数据,数据一致在缓冲区中,epoll下次检测的时候就不通知了
b.用户只读了一部分数据,epoll不通知
c.缓冲区的数据读完了,不通知

struct epoll_event { 
    uint32_t events;      //Epoll检测事件
    epoll_data_t data;    //用户数据信息
};
常见的Epoll检测事件: 
   - EPOLLIN
   - EPOLLOUT 
   - EPOLLERR
   - EPOLLET     //设置边沿触发

EPOLLONESHOT事件

即使使用 ET 模式,一个socket 上的某个事件还是可能被触发多次。这在并发程序中就会引起一个问题。比如一个线程在读取完某个socket上的数据后开始处理这些数据,而在数据的处理过程中该socket 上又有新数据可读(EPOLLIN 再次被触发),此时另外一个线程被唤醒来读取这些新的数据。于是就出现了两个线程同时操作一个 socket 的局面。一个socket连接在任一时刻都只被一个线程处理,可以使用epoll 的 EPOLLONESHOT 事件实现。

对于注册了 EPOLLONESHOT 事件的文件描述符,操作系统最多触发其上注册的一个可读、可写或者异常事件,且只触发一次,除非我们使用 epoll_ctl 函数重置该文件描述符上注册的 EPOLLONESHOT 事件。这样,当一个线程在处理某个 socket 时,其他线程是不可能有机会操作该 socket 的。但反过来思考,注册了 EPOLLONESHOT 事件的 socket 一旦被某个线程处理完毕, 该线程就应该立即重置这个socket 上的 EPOLLONESHOT 事件,以确保这个 socket 下一次可读时,其 EPOLLIN 事件能被触发,进而让其他工作线程有机会继续处理这个 socket。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值