[High concurrency network communication architecture] Introduce IO multiplexing (select, poll, epoll) to achieve high concurrency tcp server

Table of contents

1. Previous articles

2. Basic concepts

I/O multiplexing

select model

poll model

epoll model

Third, the function list

1. select method

2. poll method

3.struct pollfd structure

4. epoll_create method

5. epoll_ctl method

6. epoll_wait method

7.struct epoll_event structure

Fourth, code implementation

select operation process

select complete code

poll operation process

poll full code

epoll operation process

epoll complete code


1. Previous articles

[High concurrent network communication architecture] 1. The tcp server for single client connection under Linux

[High concurrency network communication architecture] 2. Introduce multi-threading to realize the tcp server of multi-client connection

2. Basic concepts

I/O multiplexing

introduce

  • I/O multiplexing (IO Multiplexing) is a concurrent programming technique for simultaneously monitoring multiple I/O events and selecting ready events for processing. It can handle multiple I/O operations at the same time through one thread or process without creating a separate thread or process for each I/O operation. I/O multiplexing can improve the concurrent performance of the system and reduce resource consumption.
  • In the traditional programming model, each I/O operation usually requires a separate thread or process to handle it. In the face of a large number of concurrent connections, this method will lead to waste of system resources and performance degradation. The I/O multiplexing can monitor multiple I/O events at the same time by using an event-driven method, and only when an event is ready can it be processed accordingly, thereby improving the concurrent performance of the system.
  • Common I/O multiplexing functions include " select ", " poll ", " epoll ", etc. These functions can monitor the state of multiple file descriptors simultaneously and determine which ones are ready for reading, writing, or exception handling accordingly. By using these functions, you can avoid creating an independent thread or process for each I/O operation, thereby reducing the consumption of system resources.
  • I/O multiplexing works by monitoring multiple I/O events through an event loop. When one or more events are ready, the event loop will notify the program to process accordingly. This method can greatly improve the concurrent performance of the system and reduce the switching overhead of threads or processes.

Summarize

  • I/O multiplexing is a concurrent programming technique used to simultaneously monitor multiple I/O events and select ready ones for processing. It can improve the concurrent performance of the system and reduce resource consumption. By using the I/O multiplexing function, you can avoid creating a separate thread or process for each I/O operation, thereby improving the efficiency of the system.

select model

foreword

  • The io multiplexed select model is a technique for implementing efficient event-driven I/O. Its basic idea is to simultaneously monitor multiple file descriptors (including sockets, pipes, etc.) in one thread, and process them when events arrive.
  • In traditional blocking I/O, a thread can only process one request, and it needs to wait for the I/O operation to complete before processing the next request. When using the select model, it can monitor the status of multiple file descriptors at the same time. When one or more file descriptors are ready for reading and writing, it can be known through the select function and perform corresponding processing operations.
  • By using the select model, I/O operations of multiple file descriptors can be processed in one thread at the same time, thereby improving the concurrency and response performance of the program. It is suitable for situations where multiple client requests need to be processed at the same time, such as multi-connection processing in server programs, multi-user chat programs, etc. In practical applications, the select model has been widely used, and other more efficient models have also been derived, such as poll, epoll, kqueue, etc.

definition

  • A "select function" is a function for selecting a ready file descriptor among a set of file descriptors (files, sockets, pipes, etc.). It has different implementations in different operating systems.

Function

  • The main function of the "select function" is to monitor the state of the file descriptors and determine which file descriptors are ready for reading, writing, or exception handling accordingly.

working principle

  • Watch on the specified set of file descriptors when the "select function" is called. When one or more file descriptors are ready for reading, writing, or an exception occurs, the "select function" will return, telling the program which file descriptors are ready. The program can use these ready file descriptors to perform corresponding I/O operations.

advantage

  • Multiplexing: The "select function" can monitor the status of multiple file descriptors at the same time, these file descriptors can be sockets, standard input/output, etc. related to network connections. By using the "select function", you can avoid creating a thread or process for each file descriptor to handle events, thereby achieving more efficient concurrent programming.
  • Non-blocking: Use the "select function" to set the file descriptor to non-blocking mode, so while waiting for the event to be ready, you can perform other tasks and make full use of CPU resources.
  • Compatibility: The "select function" is a cross-platform solution and is supported on most operating systems.

shortcoming

  • Linear traversal of all file descriptor collections: When calling the "select function", the collection of file descriptors to be monitored needs to be passed to the function. However, when the number of file descriptors is large, the collection needs to be traversed linearly to find a ready file descriptor, which may cause performance degradation.
  • Limit the number of file descriptors: Different operating systems have restrictions on the maximum number of supported file descriptors. If the number of file descriptors to be monitored exceeds this limit, the "select function" may not be able to meet the demand. Also, when the number of file descriptors increases, the overhead of calling the "select function" also increases.

Summarize

  • The "select function" is a commonly used function for implementing multiplexed I/O operations, monitoring the status of multiple file descriptors, and determining which file descriptors are ready for corresponding I/O operations.
  • It is especially useful in network programming, enabling efficient event-driven programming.

poll model

foreword

  • The "poll function" provides an alternative to the "select function" for monitoring the state of a set of file descriptors.

definition

  • The "poll function" is a system call that monitors the state of a set of file descriptors and determines which ones are ready for reading, writing, or exception handling accordingly.

Function

  • The main function of the "poll function" is to monitor the state of the file descriptor, similar to the "select function". It can monitor multiple file descriptors simultaneously to determine if they are readable, writable, or abnormal.

working principle

  • When the "poll function" is called, it monitors the specified file descriptor and waits for events to occur. If one or more file descriptors are ready for reading, writing, or an exception occurs, the "poll function" will return and mark the status of the ready file descriptors. The program can perform corresponding I/O operations according to the state of the file descriptor.

advantage

  • The "poll function" solves the performance problem of linearly traversing the file descriptor collection in the "select function". It uses the polling algorithm to avoid the overhead of traversing the collection.
  • The "poll function" supports monitoring a large number of file descriptors, there is no limit to the number of file descriptors.
  • The "poll function" is more readable than the "select function" and is more flexible in handling exceptions.

shortcoming

  • The portability of "poll function" may be slightly lower than that of "select function", because different operating systems and programming languages ​​may have different implementations of "poll function".
  • The performance of the "poll function" may suffer when dealing with a large number of file descriptors.

Summarize

  • The "poll function" is a commonly used function used to implement multiplexed I/O operations. It can monitor the status of multiple file descriptors at the same time and determine which file descriptors are ready for corresponding I/O operations. .
  • Compared with "select function", "poll function" provides better performance and readability. However, platform compatibility and performance issues with large numbers of file descriptors still need to be considered.

epoll model

foreword

  • epoll (event polling) is a high-performance I/O multiplexing mechanism provided by the Linux operating system, which has superior efficiency and scalability when dealing with large-scale concurrent connections. It uses the kernel's event notification mechanism to monitor and process events of multiple file descriptors at the same time, greatly reducing the overhead of polling and improving the concurrent performance of the system.
  • Compared with the traditional select and poll functions, epoll has higher efficiency in handling large-scale concurrent connections.

basic process

  1. Call the epoll_create function to create an epoll instance and obtain a file descriptor (epfd).
  2. Use the epoll_ctl function to add the file descriptors to be monitored and the corresponding event types, including read, write, error, etc., to the epoll instance.
  3. Use the epoll_wait function to block and wait for events to occur. When a file descriptor is ready, epoll_wait will return the set of ready file descriptors.
  4. Traverse the set of ready file descriptors and perform corresponding I/O operations.

working principle

  1. Call the epoll_create function to create an epoll instance and obtain an epoll file descriptor (epfd).
  2. Use the epoll_ctl function to register the file descriptors to be monitored and the corresponding event types with the epoll instance, and add them to the event table maintained by the kernel.
  3. Call the epoll_wait function to block and wait for the event to occur. At this time, the thread will enter the dormant state and no longer need to poll the file descriptor.
  4. When an event occurs on the monitored file descriptor, the kernel will add the event to the ready list and wake up the thread in the blocked state at the same time.
  5. When the epoll_wait function returns, the ready file descriptor set is obtained by traversing the ready list.
  6. The application performs corresponding I/O operations, such as reading and writing data, according to the ready event type of the file descriptor.

related functions

  • epoll_create(int size): Create an epoll instance.
  • epoll_ctl(int epfd, int op, int fd, struct epoll_event* event): Register/modify/delete a file descriptor and its corresponding event to the epoll instance.
  • epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout): Wait for events to occur and obtain ready file descriptors.

Event type (events parameter)

  • EPOLLIN: There is data readable on the file descriptor.
  • EPOLLOUT: Data can be written on the file descriptor.
  • EPOLLERR: An error occurred on the file descriptor.
  • EPOLLHUP: File descriptor hung up.
  • EPOLLET: Edge trigger mode.
  • EPOLLONESHOT: A one-time event, which will be automatically deleted from the epoll instance after the event occurs.

advantage

  1. High performance: epoll uses red-black tree (the data structure used by the kernel), ready list and event notification mechanism, which avoids the overhead of traversing the entire file descriptor set, and has better performance than traditional select and poll functions in large-scale concurrent connections .
  2. Scalability: The design of epoll supports ultra-large-scale concurrent connections, which can better adapt to high-concurrency network environments.
  3. Edge-triggered mode: epoll provides an edge-triggered (ET) mode, which triggers event notification only when the file descriptor state changes. Compared with the level-triggered (LT) mode, it can reduce the number of event notifications and improve efficiency.
  4. Persistent event registration: When registering an event, you can select the EPOLLONESHOT flag, so that the kernel automatically deletes the event from the epoll instance after notifying the application of the event, avoiding repeated notifications.
  5. Support multiple data structures: epoll supports two data structures, red-black tree and linked list, which are used to quickly find file descriptors and ready list operations, improving processing efficiency.
  6. Large-scale concurrency: epoll can efficiently manage a large number of file descriptors and support high-concurrency network programming.

Summarize

  • epoll is a high-performance I/O multiplexing mechanism provided by the Linux operating system. It can simultaneously monitor and process a large number of file descriptors through the kernel's event notification and ready list. It has the characteristics of high performance, scalability, and edge-triggered mode, and is an important tool for developing high-concurrency network applications .

Third, the function list

1. select method

#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

Function

  • Monitor the state of file descriptors and determine which ones are ready for reading, writing, or exception handling accordingly.

parameter

  • nfds: Add 1 to the maximum value of the set of monitored file descriptors.
  • readfds: A collection of file descriptors to monitor for read events.
  • writefds: A collection of file descriptors to monitor for writeable events.
  • exceptfds: A collection of file descriptors to monitor for exception events.
  • timeout: Timeout time, which can be set to NULL (infinite wait), or specify a time period.

return value

  • Modify the given set of file descriptors and return the number of ready file descriptors, 0 if timed out, or -1 if an error occurs.

2. poll method

#include <poll.h>

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

Function

  • Monitor the state of file descriptors and determine which ones are ready for reading, writing, or exception handling accordingly.

parameter

  • fds: Pointer to an array of structures, each structure contains the file descriptor and monitoring event type to be monitored.
  • nfds: The size of the structure array, that is, the maximum value of the file descriptor to be monitored plus 1.
  • timeout: Specify a timeout value, in milliseconds, to control  poll the blocking behavior, which can be set to one of the following situations:
    • timeout If it is greater than 0, it means that  timeout the timeout returns after blocking for milliseconds.
    • timeout Equal to 0, which means return immediately without blocking.
    • timeout If it is less than 0, it means infinite blocking until an event occurs.

return value

  • Modify the given structure array and return the number of ready file descriptors, 0 if timeout, or -1 if error occurs.

3.struct pollfd structure

struct pollfd {
    int fd;         // 关注的文件描述符
    short events;   // 等待的事件类型
    short revents;  // 实际发生的事件类型
};

Function

  • The poll function uses  struct pollfd a structure to describe the file descriptor and the type of event concerned.

member

  • fd: Indicates the file descriptor.
  • events: Indicates the type of event concerned, which can be one of the following events or a combination of them:
    • POLLIN: Readable event.
    • POLLOUT: Writable event.
    • POLLERR: Error event.
    • POLLHUP: pending event.
  • revents: On  poll return, revents the field represents the type of event that actually occurred, populated by the operating system.

4. epoll_create method

#include <sys/epoll.h>

int epoll_create(int size);

Function

  • Create an epoll instance and return a file descriptor to operate on the instance.

parameter

  • size: Indicates the maximum number of file descriptors that the instance can monitor (ignored after Linux 2.6.8).

return value

  • These system calls return a file descriptor (a non-negative integer) if successful. If an error occurs, -1 is returned and errno is set to indicate the error.

5. epoll_ctl method

#include <sys/epoll.h>

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

Function

  • Add/modify/delete a file descriptor and its corresponding events to the epoll instance.

parameter

  • epfd: the file descriptor of the epoll instance.
  • op: Specifies the operation type, which can be EPOLL_CTL_ADD (add), EPOLL_CTL_MOD (modify) or EPOLL_CTL_DEL (delete).
  • fd: The file descriptor to be operated.
  • event: points to an epoll_event structure, describing the type of event to be added/modified/deleted.

return value

  • When successful, epoll_ctl() returns zero. When an error occurs, epoll_ctl() returns -1 and sets errno to indicate the error.

6. epoll_wait method

#include <sys/epoll.h>

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

Function

  • Wait for an event to occur and get a ready file descriptor.

parameter

  • epfd: The file descriptor of the epoll instance.
  • events: Array to store ready events.
  • maxevents: Indicates the maximum size of the events array.
  • timeout: Specifies the timeout (in milliseconds), which can be set to -1 (wait infinitely) or 0 (return immediately).

return value

  • Returns the number of ready file descriptors on success, 0 on timeout, -1 on error, and sets errno to indicate the error.

7.struct epoll_event structure

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 */
};

Function

  • Describes a file descriptor and its corresponding event type.

member

  • events: indicates the type of event concerned, including EPOLLIN (readable), EPOLLOUT (writable), EPOLLERR (error), EPOLLHUP (hangup), etc.
  • data: Can be a 32- or 64-bit integer, or a pointer to void, used to store user data associated with the file descriptor.

Fourth, code implementation

select operation process

  1. Create and initialize the fd_set collection : fd_set is a data structure used to store file descriptors. By defining a fd_set variable and using the FD_ZERO and FD_SET macros to initialize.
  2. Set the timeout time : specify the timeout time of the select function by setting the timeval structure. If you do not need to set the timeout period, you can set this parameter to NULL.
  3. Call the select function : Use the select function to wait for the ready event of the file descriptor.
  4. Check the return value of the select function : According to the return value of the select function, determine whether there is a file descriptor ready.
  5. Traversing the ready file descriptor collection : determine which file descriptors are ready by traversing the fd_set collection. Use the FD_ISSET macro to determine if a file descriptor is in the set.

Precautions

  • Before using the select function, you need to manually set the blocking or non-blocking state of the file descriptor.
  • After each call to the select function, the fd_set collection needs to be reinitialized, and the file descriptors to be monitored must be reset.
  • The select function can only monitor a part of file descriptors at a time, so the performance may be limited during large-scale concurrent connections.
  • The select function supports a limited number of file descriptors, usually depending on operating system limitations.

Summarize

  • By creating and initializing the fd_set collection (preparing the collection of file descriptors that need to be monitored), setting the timeout period, and using the select function to monitor the ready event of the file descriptor. Then perform the corresponding I/O operation according to the return value of the select function and traverse the ready file descriptor set.

select complete code

#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 operation process

  1. Prepare the set of file descriptors that need to be monitored.
  2. Set a timeout (optional).
  3. The calling  poll function waits for the ready event.
  4. Check  poll the return value of the function.

Precautions

  • Before using  poll the function, the blocking or non-blocking state of the file descriptor needs to be set.
  • Before calling  poll the function each time, you need to reset the set of file descriptors that need to be monitored.
  • pollselect Functions can listen to multiple file descriptors at a time, which is better suited to high-concurrency environments  than  functions.
  • poll The function fills the ready file descriptor information into  revents the field, and the ready event type of the file descriptor can be judged by bitwise AND operation.

Summarize

  • By preparing the set of file descriptors that need to be monitored, calling  poll the function to wait for the ready event, and then performing corresponding I/O operations according to the return value and traversing the set of ready file descriptors.

poll full code

#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 operation process

  1. Create an epoll instance.
  2. Prepare the set of file descriptors that need to be monitored and add them to the epoll instance.
  3. Set a timeout (optional).
  4. The calling  epoll_wait function waits for the ready event.
  5. Check  epoll_wait the return value of the function.
  6. Remove the file descriptor or close the epoll instance (optional).

Precautions

  • Before using  epoll the function, the blocking or non-blocking state of the file descriptor needs to be set.
  • Before calling  epoll_wait the function each time, it is necessary to reset the set of file descriptors to be monitored, or  epoll_ctl dynamically change the set of file descriptors to be monitored through the function.
  • epoll Provides a more efficient event notification mechanism that can monitor a large number of file descriptors at the same time.
  • epoll_wait The function fills the ready file descriptor information into  events the array, and the ready event can be processed by traversing the array.

Summarize

  • Create an epoll instance by calling epoll_create, prepare the file descriptor set to be monitored and add it to the epoll instance, call  epoll_wait the function to wait for the ready event, and then execute the corresponding I/O operation according to the return value and traverse the ready event set. Finally, the file descriptor can be removed or the epoll instance can be closed as needed.

epoll complete code

#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;
}

Guess you like

Origin blog.csdn.net/weixin_43729127/article/details/131593011