Linux网络编程三(多路IO转接、select函数、epoll函数)

本文介绍了多路IO服务器的实现,包括select函数的工作原理,以及poll和epoll高级IO复用技术的对比,重点讨论了它们在TCP和UDP通信中的应用和区别。还涉及了线程池模块的分析和不同事件模型的探讨。
摘要由CSDN通过智能技术生成

1、多路IO转接服务器

服务器和客户端建立连接示意图在这里插入图片描述

2、select函数

Server服务器利用select()进行监听需要建立连接的Client,Server事先创建好lfd并交给select()函数进行监听,若有客户端需要建立连接,就反馈给服务器 ,服务器调用accpet()函数返回cfd文件描述符给select()函数。
在这里插入图片描述

select多路IO转换,原理:借助内核, select 来监听,客户端连接、数据通信事件。
①fd_set rset:创建一个名为rset的文件描述符集合

②void FD_ZERO(fd_set *set):清空一个文件描述符集合 。FD_ZERO(&rset);

③void FD_SET(int fd, fd_set *set):将待监听的文件描述符,添加到监听集合中。FD_SET(3, &rset);FD_SET(5, &rset);FD_SET(6, &rset); 将3、5、6号文件描述符添加到监听集合中。

④void FD_CLR(int fd, fd_set *set):将一个文件描述符从监听集合中移除。FD_CLR(4, &rest);

⑤int FD_ISSET(int fd, fd_set *set):判断一个文件描述符是否在监听集合中。FD_ISSET(4, &rest);返回值,在返回1,不在返回0

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
select()参数及返回值
nfds:监听的所有文件描述符中,最大文件描述符+1
readfds:读 文件描述符监听集合传入传出参数
writefds :写 文件描述符监听集合传入传出参数通常NULL
exceptfds:异常 文件描述符监听集合 传入传出参数 通常NULL
timeout:>0,设置监听超时时长;NULL: 阻塞监听;0: 非阻塞监听,轮询
返回值:>0 所有监听集合中,满足对应事件的总数;0 无满足监听条件的文件描述符;-1 errno

select的优缺点
缺点:
①监听上限受文件描述符限制,最大1024。
②检测满足条件的fd. 自己添加业务逻辑提高小。 提高了编码难度

优点:跨平台,win、linux、macOS、Unix。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>

#include "wrap.h"   //错误函数处理 查博文里有

#define SERV_PORT 6666

int main(int argc, char *argv[])
{
    int i, j, n, nready;
    int maxfd = 0;
    int listenfd, connfd;

    char buf[BUFSIZ];         /* #define INET_ADDRSTRLEN 16 */

    struct sockaddr_in clie_addr, serv_addr;
    socklen_t clie_addr_len;

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);  
    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    bzero(&serv_addr, sizeof(serv_addr));
    
    serv_addr.sin_family= AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port= htons(SERV_PORT);
    
    Bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    Listen(listenfd, 128);

    fd_set rset, allset;                            /* rset 读事件文件描述符集合 allset用来暂存 */
    maxfd = listenfd;

    FD_ZERO(&allset);
    FD_SET(listenfd, &allset);                                  /* 构造select监控文件描述符集 */

    while (1) {   
        rset = allset;                                          /* 每次循环时都从新设置select监控信号集 */
        nready = select(maxfd+1, &rset, NULL, NULL, NULL);
        if (nready < 0)
            perr_exit("select error");

        if (FD_ISSET(listenfd, &rset)) {                        /* 说明有新的客户端链接请求 */

            clie_addr_len = sizeof(clie_addr);
            connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len);       /* Accept 不会阻塞 */

            FD_SET(connfd, &allset);                            /* 向监控文件描述符集合allset添加新的文件描述符connfd */

            if (maxfd < connfd)
                maxfd = connfd;

            if (0 == --nready)                                  /* 只有listenfd有事件, 后续的 for 不需执行 */
                continue;
        } 

        for (i = listenfd+1; i <= maxfd; i++) {                 /* 检测哪个clients 有数据就绪 */

            if (FD_ISSET(i, &rset)) {

                if ((n = Read(i, buf, sizeof(buf))) == 0) {    /* 当client关闭链接时,服务器端也关闭对应链接 */
                    Close(i);
                    FD_CLR(i, &allset);                        /* 解除select对此文件描述符的监控 */

                } else if (n > 0) {

                    for (j = 0; j < n; j++)
                        buf[j] = toupper(buf[j]);
                    Write(i, buf, n);
                }
            }
        }
    }
    
    Close(listenfd);
    return 0;
}

3、poll函数

poll 是一种 I/O 多路复用机制,允许程序监控多个文件描述符的状态,以便在它们变得可读、可写或有异常时做出反应。
poll 函数通过监视一个文件描述符数组中的所有文件描述符,等待它们的状态发生变化。当指定的事件发生时,poll 函数返回,程序可以对这些事件做出响应。

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

poll()参数及返回值:
fds: 监听的文件描述符【数组】,传入传出
struct pollfd结构体成员变量
int fd:待监听的文件描述符
short events:待监听的文件描述符对应的监听事件,取值:POLLIN、POLLOUT、POLLERR
short revents传入时,给0。 如果满足对应事件的话,返回非0;非0-----POLLIN、POLLOUT、POLLERR
nfds: 监听数组的,实际有效监听个数
timeout>0: 超时时长。 单位:毫秒 ;-1: 阻塞等待 ;0:不阻塞
返回值:返回满足对应监听事件的文件描述符总个数
poll优缺点
优点:自带数组结构,可以将监听事件集合 和返回事件集合 分离;拓展 监听上限 超出1024限制
缺点:不能跨平台。适用于Linux;无法直接定位满足监听事件的文件描述符

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>

int main() {
    struct pollfd fds[2];
    int timeout_msecs = 5000; // 5 seconds timeout

    // 初始化第一个文件描述符(例如 stdin)
    fds[0].fd = STDIN_FILENO;
    fds[0].events = POLLIN; // 监听可读事件

    // 初始化第二个文件描述符(例如某个套接字)
    fds[1].fd = /* 某个套接字的文件描述符 */;
    fds[1].events = POLLIN; // 监听可读事件

    // 调用 poll 进行事件监测
    int retval = poll(fds, 2, timeout_msecs);
    if (retval == -1) {
        perror("poll()");
        exit(EXIT_FAILURE);
    }

    if (retval == 0) {
        printf("Timeout occurred! No data available.\n");
    } else {
        // 处理发生的事件
        if (fds[0].revents & POLLIN) {
            printf("Data available on stdin.\n");
        }

        if (fds[1].revents & POLLIN) {
            printf("Data available on socket.\n");
        }
    }

    return 0;
}

4、epoll函数

int epoll_create(int size):创建一个监听红黑树
size:创建的红黑树的监听节点数量。(仅供内核参考)
返回值:指向新创建的红黑树的根节点的fd;失败: -1 errno

int epoll_ctl(int epfd, int op, int fd, struct epoll_event * event):操作监听红黑树
epoll_ctl()参数及返回值
①epfd : epoll_create 函数的返回值 epfd文件描述符
② op: 对该监听红黑树所做的操作
EPOLL_CTL_ADD添加fd到 监听红黑树上
EPOLL_CTL_MOD修改fd在 监听红黑树上的监听事件
EPOLL_CTL_DEL将一个fd 从监听红黑树上摘下(取消监听)
③fd: 待监听的fd
④ event: 本质是struct epoll_event 结构体地址。
成员
events:EPOLLIN,EPOLLOUT,EPOLLERR
data联合体:int fd(对应监听事件的fd),void *ptr,uint32_t u32,uint64_t u64
⑤ 返回值成功0;失败 -1 errno

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout): 阻塞监听
epoll_wait()参数及返回值
①epfd : epoll_create 函数的返回值 epfd文件描述符
②events: 传出参数,看作数组, 满足监听条件的那些 fd结构体
③maxevents:数组 元素的总个数。 直接写1024 struct epoll_event evnets[1024]
④timeout >0: 超时时长。 单位:毫秒
-1: 阻塞等待
0: 不阻塞
⑤返回值 >0: 满足监听的 总个数。 可以用作循环上限
0:没有fd满足监听事件
-1:失败 errno
在这里插入图片描述

5、epoll事件模型

ET模式(边沿触发)
缓冲区剩余未读尽的数据不会导致epoll_wait 返回,新的事件才会触发。
struct eopll_event event,event.events = EPOLLIN | EPOLLE。
只有数据到来才触发,不管缓存区是否还有数据。

LT模式(水平触发)
默认采用模式,缓冲区剩余未读尽的数据会导致epoll_wait 返回。
水平触发只要有数据都会触发。

结论:
epoll 的 ET模式, 高效模式。但是只支持 非阻塞模式。
struct epoll_event event,event.events = EPOLLIN | EPOLLET。
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &event)。
int flg = fcntl(cfd, F_GETFL),flg | = O_NONBLOCK,fcntl(cfd, F_SETEL, flg)。

6、线程池模块分析

7、TCP和UDP通信的区别

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值