【高并发网络通信架构】引入IO多路复用(select,poll,epoll)实现高并发tcp服务端

news2024/7/4 4:39:52

目录

一,往期文章

二,基本概念

IO多路复用

select 模型

poll 模型

epoll 模型

三,函数清单

1.select 方法

2.poll 方法

3.epoll_create 方法

4.epoll_ctl 方法

5.epoll_wait 方法

6.struct epoll_event 结构体

四,代码实现

select 操作流程

select 完整代码

poll 操作流程

poll 完整代码

epoll 操作流程

epoll 完整代码


一,往期文章

【高并发网络通信架构】1.Linux下实现单客户连接的tcp服务端

【高并发网络通信架构】2.引入多线程实现多客户端连接的tcp服务端

二,基本概念

IO多路复用

介绍

  • I/O多路复用(IO Multiplexing)是一种并发编程技术,用于同时监视多个I/O事件并选择就绪的事件进行处理。它可以通过一个线程或进程同时处理多个I/O操作,而不需要为每个I/O操作创建一个独立的线程或进程。I/O多路复用可以提高系统的并发性能,减少资源的消耗。
  • 在传统的编程模型中,每个I/O操作通常都需要一个独立的线程或进程来处理。这种方式在面对大量的并发连接时,会导致系统资源的浪费和性能下降。而I/O多路复用通过使用一种事件驱动的方式,可以同时监视多个I/O事件,只有当有事件就绪时才进行相应的处理,从而提高了系统的并发性能。
  • 常见的I/O多路复用函数包括"select"、"poll"、"epoll"等。这些函数可以同时监视多个文件描述符的状态,并确定哪些文件描述符已经准备好可进行相应的读取、写入或异常处理。通过使用这些函数,可以避免为每个I/O操作创建一个独立的线程或进程,从而减少了系统资源的消耗。
  • I/O多路复用的工作原理是通过一个事件循环来监视多个I/O事件。当有一个或多个事件就绪时,事件循环会通知程序进行相应的处理。这种方式可以大大提高系统的并发性能,减少了线程或进程的切换开销。

总结

  • I/O多路复用是一种并发编程技术,用于同时监视多个I/O事件并选择就绪的事件进行处理。它可以提高系统的并发性能,减少资源的消耗。通过使用I/O多路复用函数,可以避免为每个I/O操作创建一个独立的线程或进程,从而提高系统的效率。

select 模型

前言

  • io多路复用select模型是一种用于实现高效的事件驱动I/O的技术。它的基本思想是在一个线程中同时监视多个文件描述符(包括套接字、管道等),并在有事件到达时进行处理。
  • 在传统的阻塞式I/O中,一个线程只能处理一个请求,并且需要等待I/O操作完成后才能处理下一个请求。而在使用select模型时,它可以同时监视多个文件描述符的状态,当有一个或多个文件描述符准备好进行读写时,就可以通过select函数得知,并进行相应的处理操作。
  • 通过使用select模型,可以在一个线程中同时处理多个文件描述符的I/O操作,从而提高程序的并发性和响应性能。它适用于需要同时处理多个客户端请求的情况,比如服务器程序中的多连接处理、多用户聊天程序等。在实际应用中,select模型已被广泛使用,并且也衍生出其他更高效的模型,如poll、epoll、kqueue等。

定义

  • "select函数"是一种用于在一组文件描述符(文件、套接字、管道等)中选择就绪的文件描述符的函数。它在不同的操作系统中有不同的实现。

功能

  • "select函数"的主要功能是监视文件描述符的状态并确定哪些文件描述符已经准备好可进行相应的读取、写入或异常处理。

工作原理

  • 当调用"select函数"时,在指定的文件描述符集合上进行监视。当有一个或多个文件描述符准备好进行读取、写入或出现异常时,"select函数"将返回,告诉程序哪些文件描述符已经就绪。程序可以利用这些就绪的文件描述符进行相应的I/O操作。

优点

  • 多路复用:"select函数"可以同时监视多个文件描述符的状态,这些文件描述符可以是与网络连接相关的套接字、标准输入/输出等。通过使用"select函数",可以避免单独为每个文件描述符创建线程或进程来处理事件,从而实现更高效的并发编程。
  • 非阻塞:使用"select函数"可以将文件描述符设置为非阻塞模式,因此在等待事件就绪的同时,可以执行其他任务,充分利用CPU资源。
  • 兼容性:"select函数"是一种跨平台的解决方案,在大多数操作系统上都得到支持。

缺点

  • 所有文件描述符集合的线性遍历:在调用"select函数"时,需要将待监视的文件描述符集合传递给函数。然而,在文件描述符数量较大时,需要线性遍历集合以找到就绪的文件描述符,这可能导致性能下降。
  • 限制文件描述符数量:不同操作系统对最大支持的文件描述符数量有限制,如果需要监视的文件描述符数量超出此限制,"select函数"可能无法满足需求。此外,当文件描述符数量增加时,调用"select函数"的开销也会增加。

总结

  • "select函数"是一种常用的函数,用于实现多路复用I/O操作,监视多个文件描述符的状态,并确定哪些文件描述符已准备好进行相应的I/O操作。
  • 它在网络编程中特别有用,可以实现高效的事件驱动编程。

poll 模型

前言

  • "poll函数"提供了一种替代"select函数"的方法,用于监视一组文件描述符的状态。

定义

  • "poll函数"是一个系统调用,用于监视一组文件描述符的状态并确定哪些文件描述符已经准备好可进行相应的读取、写入或异常处理。

功能

  • "poll函数"的主要功能是监视文件描述符的状态,类似于"select函数"。它可以同时监视多个文件描述符,以确定它们是否处于可读、可写或异常状态。

工作原理

  • 当调用"poll函数"时,它会监视指定的文件描述符并等待事件的发生。如果有一个或多个文件描述符准备好进行读取、写入或出现异常,"poll函数"将返回,并标记就绪的文件描述符的状态。程序可以根据文件描述符的状态进行相应的I/O操作。

优点

  • "poll函数"解决了"select函数"中线性遍历文件描述符集合的性能问题,它使用轮询算法,避免了遍历集合的开销。
  • "poll函数"支持监视大量文件描述符,没有文件描述符数量的限制。
  • "poll函数"比"select函数"更加可读,并且在处理异常情况时更加灵活。

缺点

  • "poll函数"的可移植性可能略低于"select函数",因为不同的操作系统和编程语言对"poll函数"的实现可能有所不同。
  • 当处理大量文件描述符时,"poll函数"的性能可能会受到影响。

总结

  • "poll函数"是一种常用的函数,用于实现多路复用I/O操作,它可以同时监视多个文件描述符的状态并确定哪些文件描述符已准备好进行相应的I/O操作。
  • 相对于"select函数","poll函数"提供了更好的性能和可读性。但仍需要考虑平台兼容性和大量文件描述符的性能问题。

epoll 模型

前言

  • epoll(事件轮询)是Linux操作系统提供的一种高性能I/O多路复用机制,在处理大规模的并发连接时具有优越的效率和可扩展性。它通过使用内核的事件通知机制来同时监视和处理多个文件描述符的事件,大大减少了轮询的开销,提高了系统的并发性能。
  • 与传统的select和poll函数相比,epoll在处理大规模并发连接时具有更高的效率。

基本流程

  1. 调用epoll_create函数创建一个epoll实例,并获得一个文件描述符(epfd)。
  2. 使用epoll_ctl函数向epoll实例中加入需要监视的文件描述符和对应的事件类型,包括读、写、错误等。
  3. 使用epoll_wait函数阻塞等待事件的发生,当有文件描述符就绪时,epoll_wait将返回就绪的文件描述符集合。
  4. 遍历就绪的文件描述符集合,进行相应的I/O操作。

工作原理

  1. 调用epoll_create函数创建一个epoll实例,获得一个epoll文件描述符(epfd)。
  2. 使用epoll_ctl函数向epoll实例注册需要监视的文件描述符和相应的事件类型,并将其加入到内核维护的事件表中。
  3. 调用epoll_wait函数阻塞等待事件的发生,此时线程会进入休眠状态,不再需要轮询文件描述符。
  4. 当被监视的文件描述符上的事件发生时,内核会将事件加入到就绪列表中,同时唤醒处于阻塞状态的线程。
  5. epoll_wait函数返回时,通过遍历就绪列表,获取到就绪的文件描述符集合。
  6. 应用程序根据文件描述符的就绪事件类型进行相应的I/O操作,如读取、写入数据等。

相关函数

  • epoll_create(int size):创建一个epoll实例。
  • epoll_ctl(int epfd, int op, int fd, struct epoll_event* event):向epoll实例注册/修改/删除一个文件描述符及其对应的事件。
  • epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout):等待事件的发生,并获取就绪的文件描述符。

事件类型(events参数)

  • EPOLLIN:文件描述符上有数据可读。
  • EPOLLOUT:文件描述符上可以写入数据。
  • EPOLLERR:文件描述符发生错误。
  • EPOLLHUP:文件描述符挂起。
  • EPOLLET:边缘触发模式。
  • EPOLLONESHOT:一次性事件,事件发生后会被自动从epoll实例中删除。

优点

  1. 高性能:epoll使用红黑树(内核使用的数据结构),就绪列表和事件通知机制,避免了遍历整个文件描述符集合的开销,在大规模并发连接时性能比传统的select和poll函数更好。
  2. 可扩展性:epoll的设计支持超大规模的并发连接,能够更好地适应高并发的网络环境。
  3. 边缘触发模式:epoll提供了边缘触发(ET)模式,只有在文件描述符状态发生变化时才触发事件通知,相比于水平触发(LT)模式可以减少事件通知次数,提高效率。
  4. 持久化的事件注册:注册事件时,可以选择EPOLLONESHOT标志,使得内核在将事件通知给应用程序后,自动将该事件从epoll实例中删除,避免重复通知。
  5. 支持多种数据结构:epoll支持红黑树和链表两种数据结构,用于快速查找文件描述符和就绪列表的操作,提高了处理效率。
  6. 大规模并发:epoll可以高效地管理大量的文件描述符,支持高并发的网络编程。

总结

  • epoll是Linux操作系统提供的一种高性能I/O多路复用机制,它通过内核的事件通知和就绪列表,能同时监视和处理大量的文件描述符。它具有高性能、可扩展性和边缘触发模式等特点,是开发高并发网络应用的重要工具

三,函数清单

1.select 方法

#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:超时时间,可以设置为NULL(无限等待),或者指定一个时间段。

返回值

  • 修改给定的文件描述符集合,并返回就绪的文件描述符的数量,若超时则返回0,若出错则返回-1。

2.poll 方法

#include <poll.h>

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

功能

  • 监视文件描述符的状态并确定哪些文件描述符已经准备好可进行相应的读取、写入或异常处理。

参数

  • fds:指向一个结构体数组的指针,每个结构体中包含待监视的文件描述符和监视事件类型。
  • nfds:结构体数组的大小,即待监视的文件描述符的最大值加1。
  • timeout:超时时间,可以设置为-1(无限等待),或者指定一个时间段。

返回值

  • 修改给定的结构体数组,并返回就绪的文件描述符的数量,若超时则返回0,若出错则返回-1。

3.epoll_create 方法

#include <sys/epoll.h>

int epoll_create(int size);

功能

  • 创建一个epoll实例,并返回一个文件描述符用于操作该实例。

参数

  • size:表示实例能够监视的文件描述符的最大数量(在Linux 2.6.8之后被忽略)。

返回值

  • 如果成功,这些系统调用返回一个文件描述符(一个非负整数)。如果出现错误,则返回-1,并设置errno来指示错误。

4.epoll_ctl 方法

#include <sys/epoll.h>

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

功能

  • 向epoll实例添加/修改/删除一个文件描述符及其对应的事件。

参数

  • epfd:为epoll实例的文件描述符。
  • op:指定操作类型,可以是EPOLL_CTL_ADD(添加)、EPOLL_CTL_MOD(修改)或EPOLL_CTL_DEL(删除)。
  • fd:待操作的文件描述符。
  • event:指向一个epoll_event结构体,描述了待添加/修改/删除的事件类型。

返回值

  • 当成功时,epoll_ctl()返回零。当发生错误时,epoll_ctl()返回-1,并设置errno来指示错误。

5.epoll_wait 方法

#include <sys/epoll.h>

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

功能

  • 等待事件的发生并获取就绪的文件描述符。

参数

  • epfd:epoll实例的文件描述符。
  • events:存储就绪事件的数组。
  • maxevents:表示events数组的最大大小。
  • timeout:指定超时时间(以毫秒为单位),可以设置为-1(无限等待)或0(立即返回)。

返回值

  • 若成功则返回就绪的文件描述符的数量,若超时则返回0,若出错则返回-1,并设置errno来指示错误。

6.struct epoll_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 */
};

功能

  • 描述一个文件描述符及其对应的事件类型。

成员

  • events:表示关注的事件类型,包括EPOLLIN(可读)、EPOLLOUT(可写)、EPOLLERR(错误)、EPOLLHUP(挂起)等。
  • data:可以是一个32或64位的整数,或者一个指向void的指针,用于存储与文件描述符关联的用户数据。

四,代码实现

select 操作流程

  1. 创建并初始化fd_set集合:fd_set是一个用于存储文件描述符的数据结构。通过定义一个fd_set变量,并使用FD_ZERO和FD_SET宏来初始化。
  2. 设置超时时间:通过设置timeval结构体来指定select函数的超时时间。如果不需要设置超时时间,可以将该参数设置为NULL。
  3. 调用select函数:使用select函数来等待文件描述符的就绪事件。
  4. 检查select函数的返回值:根据select函数的返回值,确定是否有文件描述符就绪。
  5. 遍历就绪文件描述符集合:通过遍历fd_set集合来判断哪些文件描述符已经就绪。使用FD_ISSET宏来判断文件描述符是否在集合中。

注意事项

  • 在使用select函数之前,需要手动设置文件描述符的阻塞或非阻塞状态。
  • 每次调用select函数之后,需要重新初始化fd_set集合,并将需要监听的文件描述符重新设置进去。
  • select函数每次只能监听一部分文件描述符,因此在大规模并发连接时,性能可能会受到限制。
  • select函数支持的文件描述符数量有限,通常取决于操作系统的限制。

总结

  • 通过创建并初始化fd_set集合(准备需要监听的文件描述符集合),设置超时时间,并使用select函数进行监听文件描述符的就绪事件。然后根据select函数的返回值和遍历就绪文件描述符集合来执行相应的I/O操作。

select 完整代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include <sys/select.h>


#define BUFFER_LENGTH   1024

int init_server(int port){
    //获取服务端fd,通常为3,前面0,1,2用于表示标准输入,输出,错误值
    int sfd = socket(AF_INET,SOCK_STREAM,0);

    if(-1 == sfd){
        printf("socket error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    struct sockaddr_in servAddr;
    memset(&servAddr,0,sizeof(struct sockaddr_in));
    servAddr.sin_family = AF_INET;  //ipv4
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);   //0.0.0.0
    servAddr.sin_port = htons(port);
    
    //绑定IP和端口号
    if(-1 == bind(sfd,(struct sockaddr*)&servAddr,sizeof(struct sockaddr_in)))
    {
        printf("bind error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    //监听该套接字上的连接
    if(-1 == listen(sfd,10))
    {
        printf("listen error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    return sfd;
}

int main(int argc,char *argv[]){

    if(argc < 2)return -1;

    int port = atoi(argv[1]);       //字符串转整型
    int sfd = init_server(port);
    printf("server fd: %d\n",sfd);
    if(sfd == -1)return -1;

    fd_set rfds,rset;    //fd位数组
    FD_ZERO(&rfds);
    FD_SET(sfd,&rfds);

    int maxfd = sfd;
    int cfd = 0;
    struct sockaddr_in clientAddr;
    socklen_t len = sizeof(struct sockaddr_in);
    
    while (1)   //表示每周都去东莞      主线程
    {
        rset = rfds;
        int nready = select(maxfd+1,&rset,NULL,NULL,NULL);    //系统调用

        if(FD_ISSET(sfd,&rset)){
            cfd = accept(sfd,(struct sockaddr*)&clientAddr,&len);   //阻塞等待客户端连接
            printf("client fd: %d\n",cfd);
            FD_SET(cfd,&rfds);
            if(cfd > maxfd)maxfd = cfd;     //将需要监听的文件描述符重新设置进去

            if(--nready == 0)continue;      //超时时间到,没有就绪的文件描述符
            
        }

        int i = 0;
        //服务端sfd=3
        for(i = sfd+1;i<=maxfd;i++){
            if(FD_ISSET(i,&rset)){
                //有bug,下一个客户端发送数据后,上一个客户端发送数据不能接收
                char data[BUFFER_LENGTH] = {0};
                int recvLen = recv(cfd,data,BUFFER_LENGTH,0);
                if(recvLen == 0){
                    printf("cfd: %d disconnect\n",cfd);
                    close(cfd);
                    break;  //客户端断开连接
                }
                printf("recv cfd: %d data: %s\n",cfd,data);

                send(cfd,data,recvLen,0);
            }
        }
    }
    
    return 0;
}

poll 操作流程

  1. 准备需要监听的文件描述符集合。
  2. 设置超时时间(可选)。
  3. 调用 poll 函数等待就绪事件。
  4. 检查 poll 函数的返回值。

注意事项

  • 在使用 poll 函数之前,需要设置文件描述符的阻塞或非阻塞状态。
  • 每次调用 poll 函数之前,需要重新设置需要监听的文件描述符集合。
  • poll 函数一次可以监听多个文件描述符,相比于 select 函数能更好地适应高并发环境。
  • poll 函数将就绪的文件描述符信息填充到 revents 字段中,可以通过按位与操作来判断文件描述符的就绪事件类型。

总结

  • 通过准备需要监听的文件描述符集合,调用 poll 函数等待就绪事件,然后根据返回值和遍历就绪文件描述符集合来执行相应的 I/O 操作。

poll 完整代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include <poll.h>


#define BUFFER_LENGTH   1024
#define POLL_LENGTH   1024

int init_server(int port){
    //获取服务端fd,通常为3,前面0,1,2用于指定输入,输出,错误值
    int sfd = socket(AF_INET,SOCK_STREAM,0);

    if(-1 == sfd){
        printf("socket error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    struct sockaddr_in servAddr;
    memset(&servAddr,0,sizeof(struct sockaddr_in));
    servAddr.sin_family = AF_INET;  //ipv4
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);   //0.0.0.0
    servAddr.sin_port = htons(port);
    
    //绑定IP和端口号
    if(-1 == bind(sfd,(struct sockaddr*)&servAddr,sizeof(struct sockaddr_in)))
    {
        printf("bind error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    //监听该套接字上的连接
    if(-1 == listen(sfd,10))
    {
        printf("listen error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    return sfd;
}

int main(int argc,char *argv[]){

    if(argc < 2)return -1;

    int port = atoi(argv[1]);
    int sfd = init_server(port);
    printf("server fd: %d\n",sfd);
    if(sfd == -1)return -1;

    struct pollfd fds[POLL_LENGTH] = {0};   // 定义一个 pollfd 数组,大小根据需要监听的文件描述符个数来设置
    fds[sfd].fd = sfd;  // 设置第一个需要监听的文件描述符
    fds[sfd].events = POLLIN;   // 设置需要监听的事件类型
    fds[sfd].revents = 0; // 清空返回的就绪事件

    int maxfd = sfd;
    int cfd;
    struct sockaddr_in clientAddr;
    socklen_t len = sizeof(struct sockaddr_in);
    while (1)
    {
        int nready = poll(fds,maxfd+1,-1);
        if(fds[sfd].revents & POLLIN){
            cfd = accept(sfd,(struct sockaddr*)&clientAddr,&len);   //阻塞等待客户端连接
            printf("client fd: %d\n",cfd);

            fds[cfd].fd = cfd;
            fds[cfd].events = POLLIN;

            if(cfd > maxfd)maxfd = cfd;

            if(--nready == 0)continue;
        }

        int i = 0;
        for(i=0;i<=maxfd;i++){
            if(fds[i].revents & POLLIN){
                char data[BUFFER_LENGTH] = {0};
                int recvLen = recv(cfd,data,BUFFER_LENGTH,0);
                if(recvLen == 0){
                    //有点问题
                    fds[cfd].fd = -1;
                    fds[cfd].events = 0;
                    printf("client fd: %d disconnect\n",cfd);
                    close(cfd);
                    break;  //客户端断开连接
                }

                printf("recv cfd: %d data: %s\n",cfd,data);

                send(cfd,data,recvLen,0);
            }
        }
    }
    
    return 0;
}

epoll 操作流程

  1. 创建 epoll 实例。
  2. 准备需要监听的文件描述符集合并添加到 epoll 实例中。
  3. 设置超时时间(可选)。
  4. 调用 epoll_wait 函数等待就绪事件。
  5. 检查 epoll_wait 函数的返回值。
  6. 移除文件描述符或关闭 epoll 实例(可选)。

注意事项

  • 在使用 epoll 函数之前,需要设置文件描述符的阻塞或非阻塞状态。
  • 每次调用 epoll_wait 函数之前,需要重新设置需要监听的文件描述符集合,或是通过 epoll_ctl 函数动态改变监听的文件描述符集合。
  • epoll 提供了更高效的事件通知机制,可以同时监听大量的文件描述符。
  • epoll_wait 函数将就绪的文件描述符信息填充到 events 数组中,可以通过遍历数组来处理就绪事件。

总结

  • 通过调用 epoll_create 创建 epoll 实例,准备需要监听的文件描述符集合并添加到 epoll 实例中,调用 epoll_wait 函数等待就绪事件,然后根据返回值和遍历就绪事件集合来执行相应的 I/O 操作。最后,根据需要,可以移除文件描述符或关闭 epoll 实例。

epoll 完整代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include <sys/epoll.h>


#define BUFFER_LENGTH   1024
#define EPOLL_LENGTH    1024

int init_server(int port){
    //获取服务端fd,通常为3,前面0,1,2用于指定输入,输出,错误值
    int sfd = socket(AF_INET,SOCK_STREAM,0);

    if(-1 == sfd){
        printf("socket error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    struct sockaddr_in servAddr;
    memset(&servAddr,0,sizeof(struct sockaddr_in));
    servAddr.sin_family = AF_INET;  //ipv4
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);   //0.0.0.0
    servAddr.sin_port = htons(port);
    
    //绑定IP和端口号
    if(-1 == bind(sfd,(struct sockaddr*)&servAddr,sizeof(struct sockaddr_in)))
    {
        printf("bind error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    //监听该套接字上的连接
    if(-1 == listen(sfd,10))
    {
        printf("listen error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    return sfd;
}

int main(int argc,char *argv[]){

    if(argc < 2)return -1;

    int port = atoi(argv[1]);
    int sfd = init_server(port);
    printf("server fd: %d\n",sfd);
    if(sfd == -1)return -1;

    int epfd = epoll_create(1);
    printf("epoll fd: %d\n",epfd);
    if (epfd == -1) {
        // 创建 epoll 实例失败
        return -1;
    }

    struct epoll_event ev;  // 定义存储就绪事件的数组
    ev.events = EPOLLIN;    // 设置需要监听的事件类型,例如 EPOLLIN 表示可读事件,EPOLLOUT 表示可写事件。
    ev.data.fd = sfd;   // 设置第一个需要监听的文件描述符

    if (epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&ev) == -1) {
        // 添加第一个文件描述符到 epoll 实例失败
        return -1;
    }

    struct epoll_event events[EPOLL_LENGTH] = {0};
    struct sockaddr_in clientAddr;
    socklen_t len = sizeof(struct sockaddr_in);
    while (1)   //mainloop 7*24小时工作
    {
        // 第一个参数是 epoll 实例的文件描述符。
        // 第二个参数是一个用于存储就绪事件的结构体数组。
        // 第三个参数是 events 数组的大小,表示最多能够处理的就绪事件数量。
        // 第四个参数是超时时间,可以设置为-1表示无限等待。
        int nready = epoll_wait(epfd,events,EPOLL_LENGTH,-1);
        if(nready < 0){
            //epoll_wait 调用出错
            continue;
        }

        int i = 0;
        for (i = 0; i < nready; i++)
        {
            int connfd = events[i].data.fd;

            if(sfd == connfd)
            {  //accept
                int cfd = accept(sfd,(struct sockaddr*)&clientAddr,&len);
                if(cfd < 0)continue;

                printf("client fd: %d\n",cfd);
                ev.events = EPOLLIN;
                ev.data.fd = cfd;
                epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
            }
            else if(events[i].events & EPOLLIN)
            {
                //通过按位与操作判断就绪事件的类型,例如 events[i].events & EPOLLIN 判断是否是可读事件。
                char buffer[BUFFER_LENGTH] = {0};
                int recvLen = recv(connfd,buffer,BUFFER_LENGTH,0);
                if(recvLen > 0){
                    printf("recv client fd %d data: %s\n",connfd,buffer);

                    send(connfd,buffer,recvLen,0);
                }else if(recvLen == 0)
                {
                    printf("client fd %d close\n",connfd);

                    epoll_ctl(epfd,EPOLL_CTL_DEL,connfd,&ev);   // 移除文件描述符
                    close(connfd);
                }
            }
        }
    }
    
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/730139.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uniapp踩坑之项目:uniapp修改弹窗组件样式

在components文件夹里创建zz-prompt文件夹&#xff0c;再在下面创建index.vue <!--通知弹窗index.vue--> <template><view class"prompt-box" v-if"visible" touchmove"true"><view class"prompt"><view c…

1.8 用户注册_和使用的工具类

步骤1&#xff1a;在common模块下,创建对应的工具类 /** 创建性别枚举(Sex)*/ /** md5加密类(MD5Utils)*/ /** 时间转换格式化类(DateUtil)*/ /** 生成全局唯一主机id (Sid)*/步骤2&#xff1a;在pojo模块下&#xff0c;创建表单封装bo对象 /** 注册表单bo对象 UserBO*/步骤3…

怎么将桌面笔记软件中记录的内容折叠起来或展开显示?

记笔记是一种良好的习惯&#xff0c;不仅可以帮助我们整理思绪&#xff0c;还能有效地记录重要的信息。在现代科技的支持下&#xff0c;一款优秀的笔记软件已经成为我们记录和管理事项的主要工具。特别是一款能够折叠的桌面笔记软件&#xff0c;将会给用户带来更多的便利和效率…

2023.07.07 homework

孩子们有些基础不好&#xff0c;鼓励为主&#xff0c;教他们慢慢搬公式推算&#xff0c;提高准确率就好啦。每一次比上一次好一点点&#xff0c;慢慢找回自信心。 图形结合&#xff0c;话说话&#xff0c;其实数学这玩意&#xff0c;画得好也比较直观 第八题找规律的题目&#…

【如何成功加载 HuggingFace 数据集】不使用Colab,以ChnSentiCorp数据集为例

【如何成功加载 HuggingFace 数据集】不使用Colab&#xff0c;以ChnSentiCorp数据集为例 前置加载数据集尝试一&#xff1a;标准加载数据库代码尝试二&#xff1a;科学上网尝试三&#xff1a;把 Huggingface 的数据库下载到本地尝试3.5 创建 state.json彩蛋 前置 Huggingface …

DAY45——动态规划part7

爬楼梯问题 爬楼梯阶数为1...m class Solution {public int climbStairs(int n) {int[] dp new int[n 1];int m 2;dp[0] 1;for (int i 1; i < n; i) { // 遍历背包for (int j 1; j < m; j) { //遍历物品if (i > j) dp[i] dp[i - j];}}return dp[n];} } class…

基于matlab处理来自立体相机图像数据构建室外环境地图并估计相机的轨迹(附源码)

一、前言 视觉同步定位和映射 &#xff08;vSLAM&#xff09; 是指计算摄像机相对于周围环境的位置和方向&#xff0c;同时映射环境的过程。该过程仅使用来自相机的视觉输入。vSLAM 的应用包括增强现实、机器人和自动驾驶。vSLAM 只需使用单眼摄像头即可执行。但是&#xff0c…

Spring系列3 -- 更简单的读取和存储对象

前言 上一篇章总结了,Spring的创建与使用,通过创建Maven项目配置Spring的环境依赖,创建Spring框架的项目,然后通过在Resource目录下创建Spring-config.xml配置文件,添加<bean></bean>标签将我们需要的bean对象注入到容器中,然后通过ApplicationContext获取Spring上…

Mybatis-puls——条件查询的三种格式+条件查询null判定+查询投影

前言 在mybatis_plus的封装中的Wrapper<T>接口参数就是用于封装查询条件 在测试类中启动如上一个简单的查询&#xff0c;然后控制台运行会输出一大堆无关日志&#xff0c;这里先把这些日志关闭 去除无关日志文件 先新建一个XML配置文件 然后变成如下&#xff0c;这里…

[神经网络]YoloV7

Yolo系列是一类很经典的目标检测网络&#xff0c;属于anchor-base型网络&#xff0c;即需要先产生先验框然后筛选先验框得到预测框。同时它也是One-Stage网络&#xff0c;即不需要额外的网络结构来筛选先验框。这两种特点使得它具备了运算速度快和计算精度相对较高的特点。 比较…

Win10怎么用U盘重装系统?Win10用U盘重装系统步骤图解教程

Win10怎么用U盘重装系统&#xff1f;使用U盘重装系统是一种快速而有效的方法&#xff0c;可以帮助我们在电脑中进行Win10系统的重新安装。首先用户需要准备一个容量适当的U盘&#xff0c;并确保其中没有重要数据&#xff0c;然后按照小编分享的Win10用U盘重装系统步骤图解教程操…

Redis 简单入门

✏️作者&#xff1a;银河罐头 &#x1f4cb;系列专栏&#xff1a;JavaEE &#x1f332;“种一棵树最好的时间是十年前&#xff0c;其次是现在” 目录 安装 Redisredis 的数据类型和使用字符串类型字典类型列表类型集合类型有序集合类型 SpringBoot 集成 Redis添加 redis 依赖配…

el-checkbox / el-checkbox-group中绑定对象无法回显的问题处理

在使用el-checkbox / el-checkbox-group时&#xff0c;发现若label绑定的是对象&#xff0c;则是无法回显的&#xff0c;参考了很多方法&#xff0c;但大多都无法解决&#xff0c;最终参考下面的方法解决&#xff0c;记录一下。 <el-checkbox :label"JSON.stringify(i…

每天一点Python——day51

#第五十一天列表和字典为可变序列&#xff0c;元组是不可变序列 为什么要将元组设计成不可变序列呢&#xff1f; 一旦创建了不可变类型的对象&#xff0c;对象内部的所有数据将不能被修改 这样就避免了由于修改数据而导致的错误 对于不可变对象&#xff0c;在多任务环境下&…

最小覆盖串双指针解题思路及Java实现

最小覆盖串双指针解题思路及Java实现 题目双指针思路Java实现 题目 题目来自牛客NC28 最小覆盖子串 给出两个字符串 s 和 t&#xff0c;要求在 s 中找出最短的包含 t 中所有字符的连续子串。 例如&#xff1a; 输入&#xff1a;“XDOYEZODEYXNZ”,“XYZ” 返回值&#xff1a;“…

使用LiteSpeed缓存插件将WordPress优化到100%的得分

页面速度优化应该是每个网站所有者的首要任务&#xff0c;因为它直接影响WordPress SEO。此外&#xff0c;网站加载的时间越长&#xff0c;其跳出率就越高。这可能会阻止您产生转化并为您的网站带来流量。 使用正确的工具和配置&#xff0c;缓存您的网站可以显着提高其性能。因…

c++——命名空间

1.什么是命名空间 1.1命名空间的定义 命名空间&#xff08;Namespace&#xff09;是C中用来避免命名冲突的一种机制。命名空间可以将一组相关的函数、类、变量等定义在同一个逻辑分组中&#xff0c;从而避免它们与其他代码中的同名实体发生冲突。 C中&#xff0c;命名空间使用…

Openlayers实战:回显点、线段、圆形、多边形

Openlayers地图中,回显数据到地图上,形成几何图形是非常重要的一部分学习内容。 回显的内容基本单元包括点、线、圆形、多边形。本实战项目中,根据给定的坐标点,用最基础最管用的方式来显示这些信息。 效果图 源代码 /* * @Author: 大剑师兰特(xiaozhuanlan),还是大剑…

Keil环境下CANopenNode移植到STM32问题记录(一)---printf重定向问题

文章目录 问题描述问题结决思考&#xff1a;相关文章 在直接将CANopenSTM32的示例工程直接移植到Keil环境下。 如果移植工程未实现printf函数重定向&#xff0c;则要注释掉log_printf下面的printf函数&#xff0c;使日志打印失效 /* Printf function of CanOpen app */ #define…

Django_环境配置(一)

目录 一、安装Django 二、创建项目 三、创建应用 四、注册应用 五、使用mysql数据库 六、视图 七、开发服务器 源码等资料获取方法 一、安装Django # 在CMD中运行 pip install django # 查看djangp版本 python manage.py version 二、创建项目 # 在CMD中运行 django-a…