• socket编程之常用api介绍与socket、select、poll、epoll高并发服务器模型代码实现


    前言

      本文旨在学习socket网络编程这一块的内容,epoll是重中之重,后续文章写reactor模型是建立在epoll之上的。

      本专栏知识点是通过零声教育的线上课学习,进行梳理总结写下文章,对c/c++linux课程感兴趣的读者,可以点击链接 C/C++后台高级服务器课程介绍 详细查看课程的服务。

    socket编程

    socket介绍

      传统的进程间通信借助内核提供的IPC机制进行, 但是只能限于本机通信, 若要跨机通信, 就必须使用网络通信( 本质上借助内核-内核提供了socket伪文件的机制实现通信----实际上是使用文件描述符), 这就需要用到内核提供给用户的socket API函数库。

      使用socket会建立一个socket pair,如下图, 一个文件描述符操作两个缓冲区。

    在这里插入图片描述

    使用socket的API函数编写服务端和客户端程序的步骤

    在这里插入图片描述

    预备知识

    网络字节序

      网络字节序:大端和小端的概念

    • 大端: 低位地址存放高位数据, 高位地址存放低位数据
    • 小端: 低位地址存放低位数据, 高位地址存放高位数据

      大端和小端的使用使用场合:在网络中经常需要考虑大端和小端的是IP和端口。网络传输用的是大端,计算机用的是小端, 所以需要进行大小端的转换

      下面4个函数就是进行大小端转换的函数,函数名的h表示主机host, n表示网络network, s表示short, l表示long。

    #include <arpa/inet.h>
    uint32_t htonl(uint32_t hostlong);
    uint16_t htons(uint16_t hostshort);
    uint32_t ntohl(uint32_t netlong);
    uint16_t ntohs(uint16_t netshort);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上述的几个函数, 如果本来不需要转换函数内部就不会做转换。

    IP地址转换函数

      IP地址转换函数

    int inet_pton(int af, const char *src, void *dst);
    
    • 1
    • p->表示点分十进制的字符串形式
    • to->到
    • n->表示network网络

    函数说明: 将字符串形式的点分十进制IP转换为大端模式的网络IP(整形4字节数)
    参数说明:

    • af: AF_INET
    • src: 字符串形式的点分十进制的IP地址
    • dst: 存放转换后的变量的地址
    • 例如inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);

      手工也可以计算: 如192.168.232.145, 先将4个正数分别转换为16进制数,
       192—>0xC0   168—>0xA8    232—>0xE8    145—>0x91
      最后按照大端字节序存放: 0x91E8A8C0, 这个就是4字节的整形值。
      

    const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
    
    • 1

    函数说明: 网络IP转换为字符串形式的点分十进制的IP
    参数说明:

    • af: AF_INET
    • src: 网络的整形的IP地址
    • dst: 转换后的IP地址,一般为字符串数组
    • size: dst的长度

    返回值:

    • 成功–返回执行dst的指针
    • 失败–返回NULL, 并设置errno

      例如: IP地址为010aa8c0, 转换为点分十进制的格式:
      01---->1    0a---->10   a8---->168    c0---->192
      由于从网络中的IP地址是高端模式, 所以转换为点分十进制后应该为: 192.168.10.1

    struct sockaddr

    socket编程用到的重要的结构体:struct sockaddr
    在这里插入图片描述

    //struct sockaddr结构说明:
    struct sockaddr {
         sa_family_t sa_family;
         char     sa_data[14];
    }
    
    //struct sockaddr_in结构:
    struct sockaddr_in {
         sa_family_t    sin_family; /* address family: AF_INET */
         in_port_t      sin_port;   /* port in network byte order */
         struct in_addr sin_addr;   /* internet address */
    };
    
    /* Internet address. */
    struct in_addr {
          uint32_t  s_addr;     /* address in network byte order */
    };	 //网络字节序IP--大端模式
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    通过man 7 ip可以查看相关说明

    主要API函数介绍

    socket

    int socket(int domain, int type, int protocol);
    
    • 1

    函数描述: 创建socket

    参数说明:

    • domain: 协议版本
    - - AF_INET IPV4
    - - AF_INET6 IPV6
    - - AF_UNIX AF_LOCAL本地套接字使用
    
    • 1
    • 2
    • 3
    • type:协议类型
    - - SOCK_STREAM 流式, 默认使用的协议是TCP协议
    - - SOCK_DGRAM  报式, 默认使用的是UDP协议
    
    • 1
    • 2
    • protocal:
    - - 一般填0, 表示使用对应类型的默认协议.
    
    • 1
    • 返回值:
    - - 成功: 返回一个大于0的文件描述符
    - - 失败: 返回-1, 并设置errno
    
    • 1
    • 2

      当调用socket函数以后, 返回一个文件描述符, 内核会提供与该文件描述符相对应的读和写缓冲区, 同时还有两个队列, 分别是请求连接队列和已连接队列(监听文件描述符才有,listenFd)

    在这里插入图片描述

    bind

    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    
    • 1

    函数描述: 将socket文件描述符和IP,PORT绑定

    参数说明:

    • socket: 调用socket函数返回的文件描述符
    • addr: 本地服务器的IP地址和PORT,
    struct sockaddr_in serv;
    serv.sin_family = AF_INET;
    serv.sin_port = htons(8888);
    //serv.sin_addr.s_addr = htonl(INADDR_ANY);
    //INADDR_ANY: 表示使用本机任意有效的可用IP
    inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • addrlen: addr变量的占用的内存大小

    返回值:

    • 成功: 返回0
    • 失败: 返回-1, 并设置errno

    listen

    int listen(int sockfd, int backlog);
    
    • 1

    函数描述: 将套接字由主动态变为被动态

    参数说明:

    • sockfd: 调用socket函数返回的文件描述符
    • backlog: 在linux系统中,这里代表全连接队列(已连接队列)的数量。在unix系统种,这里代表全连接队列(已连接队列)+ 半连接队列(请求连接队列)的总数

    返回值:

    • 成功: 返回0
    • 失败: 返回-1, 并设置errno

    accept

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);	
    
    • 1

    函数说明:获得一个连接, 若当前没有连接则会阻塞等待.

    函数参数:

    • sockfd: 调用socket函数返回的文件描述符
    • addr: 传出参数, 保存客户端的地址信息
    • addrlen: 传入传出参数, addr变量所占内存空间大小

    返回值:

    • 成功: 返回一个新的文件描述符,用于和客户端通信
    • 失败: 返回-1, 并设置errno值.

      accept函数是一个阻塞函数, 若没有新的连接请求, 则一直阻塞.
      从已连接队列中获取一个新的连接, 并获得一个新的文件描述符, 该文件描述符用于和客户端通信. (内核会负责将请求队列中的连接拿到已连接队列中)

    connect

    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    
    • 1

    函数说明: 连接服务器

    函数参数:

    • sockfd: 调用socket函数返回的文件描述符
    • addr: 服务端的地址信息
    • addrlen: addr变量的内存大小

    返回值:

    • 成功: 返回0
    • 失败: 返回-1, 并设置errno值

    读取和发送数据

      接下来就可以使用write和read函数进行读写操作了。除了使用read/write函数以外, 还可以使用recv和send函数。

    ssize_t read(int fd, void *buf, size_t count);
    ssize_t write(int fd, const void *buf, size_t count);
    ssize_t recv(int sockfd, void *buf, size_t len, int flags);
    ssize_t send(int sockfd, const void *buf, size_t len, int flags);	
    //对应recv和send这两个函数flags直接填0就可以了
    
    • 1
    • 2
    • 3
    • 4
    • 5

      注意: 如果写缓冲区已满, write也会阻塞, read读操作的时候, 若读缓冲区没有数据会引起阻塞。

    高并发服务器模型-select

    select介绍

      多路IO技术: select, 同时监听多个文件描述符, 将监控的操作交给内核去处理

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

    数据类型fd_set::文件描述符集合——本质是位图

    函数介绍: 委托内核监控该文件描述符对应的读,写或者错误事件的发生

    参数说明:

    • nfds: 最大的文件描述符+1
    • readfds: 读集合, 是一个传入传出参数
    		传入: 指的是告诉内核哪些文件描述符需要监控
    		传出: 指的是内核告诉应用程序哪些文件描述符发生了变化
    
    • 1
    • 2
    • writefds: 写文件描述符集合(传入传出参数,同上)
    • execptfds: 异常文件描述符集合(传入传出参数,同上)
    • timeout:
    		NULL--表示永久阻塞, 直到有事件发生
    		0   --表示不阻塞, 立刻返回, 不管是否有监控的事件发生
    		>0  --到指定事件或者有事件发生了就返回
    
    • 1
    • 2
    • 3
    • 返回值: 成功返回发生变化的文件描述符的个数。失败返回-1, 并设置errno值。

    select-api

    将fd从set集合中清除

    void FD_CLR(int fd, fd_set *set);
    
    • 1

    功能描述: 判断fd是否在集合中
    返回值: 如果fd在set集合中, 返回1, 否则返回0

    int FD_ISSET(int fd, fd_set *set);
    
    • 1

    将fd设置到set集合中

    void FD_SET(int fd, fd_set *set);
    
    • 1

    初始化set集合

    void FD_ZERO(fd_set *set);
    
    • 1

    用select函数其实就是委托内核帮我们去检测哪些文件描述符有可读数据,可写,错误发生

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

    select优缺点

    select优点:

    1. select支持跨平台

    select缺点:

    1. 代码编写困难
    2. 会涉及到用户区到内核区的来回拷贝
    3. 当客户端多个连接, 但少数活跃的情况, select效率较低(例如: 作为极端的一种情况, 3-1023文件描述符全部打开, 但是只有1023有发送数据, select就显得效率低下)
    4. 最大支持1024个客户端连接(select最大支持1024个客户端连接不是有文件描述符表最多可以支持1024个文件描述符限制的, 而是由FD_SETSIZE=1024限制的)

    FD_SETSIZE=1024 fd_set使用了该宏, 当然可以修改内核, 然后再重新编译内核, 一般不建议这么做

    select代码实现

    #include <errno.h>
    #include <netinet/in.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/poll.h>
    #include <sys/epoll.h>
    #include <pthread.h>
    
    #define MAX_LEN  4096
    
    int main(int argc, char **argv) {
        int listenfd, connfd, n;
        struct sockaddr_in svr_addr;
        char buff[MAX_LEN];
    
        if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
            printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
            return 0;
        }
    
        memset(&svr_addr, 0, sizeof(svr_addr));
        svr_addr.sin_family = AF_INET;
        svr_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        svr_addr.sin_port = htons(8081);
    
        if (bind(listenfd, (struct sockaddr *) &svr_addr, sizeof(svr_addr)) == -1) {
            printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
            return 0;
        }
    
        if (listen(listenfd, 10) == -1) {
            printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
            return 0;
        }
        //select
        fd_set rfds, rset, wfds, wset;
        FD_ZERO(&rfds);
        FD_ZERO(&wfds);
        FD_SET(listenfd, &rfds);
        int max_fd = listenfd;
    
        while (1) {
            rset = rfds;
            wset = wfds;
            int nready = select(max_fd + 1, &rset, &wset, NULL, NULL);
    
            if (FD_ISSET(listenfd, &rset)) { //
                struct sockaddr_in clt_addr;
                socklen_t len = sizeof(clt_addr);
                if ((connfd = accept(listenfd, (struct sockaddr *) &clt_addr, &len)) == -1) {
                    printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
                    return 0;
                }
                FD_SET(connfd, &rfds);
                if (connfd > max_fd) max_fd = connfd;
                if (--nready == 0) continue;
            }
            int i = 0;
            for (i = listenfd + 1; i <= max_fd; i++) {
                if (FD_ISSET(i, &rset)) { //
                    n = recv(i, buff, MAX_LEN, 0);
                    if (n > 0) {
                        buff[n] = '\0';
                        printf("recv msg from client: %s\n", buff);
                        FD_SET(i, &wfds);
                    }
                    else if (n == 0) { //
                        FD_CLR(i, &rfds);
                        close(i);
                    }
                    if (--nready == 0) break;
                }
                else if (FD_ISSET(i, &wset)) {
                    send(i, buff, n, 0);
                    FD_SET(i, &rfds);
                    FD_CLR(i, &wfds);
                }
            }
        }
        close(listenfd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    高并发服务器模型-poll

    poll介绍

      poll跟select类似, 监控多路IO, 但poll不能跨平台。其实poll就是把select三个文件描述符集合变成一个集合了。

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

    参数说明:

    • fds: 传入传出参数, 实际上是一个结构体数组
    fds.fd: 要监控的文件描述符
    fds.events: 
    	POLLIN---->读事件
    	POLLOUT---->写事件
    fds.revents: 返回的事件
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • nfds: 数组实际有效内容的个数
    • timeout: 超时时间, 单位是毫秒.
    -1:永久阻塞, 直到监控的事件发生
    0: 不管是否有事件发生, 立刻返回
    >0: 直到监控的事件发生或者超时
    
    • 1
    • 2
    • 3

    返回值:

    • 成功:返回就绪事件的个数
    • 失败: 返回-1。若timeout=0, poll函数不阻塞,且没有事件发生, 此时返回-1, 并且errno=EAGAIN, 这种情况不应视为错误。
    struct pollfd {
       int   fd;        /* file descriptor */   监控的文件描述符
       short events;     /* requested events */  要监控的事件---不会被修改
       short revents;    /* returned events */   返回发生变化的事件 ---由内核返回
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    说明:

    1. 当poll函数返回的时候, 结构体当中的fd和events没有发生变化, 究竟有没有事件发生由revents来判断, 所以poll是请求和返回分离
    2. struct pollfd结构体中的fd成员若赋值为-1, 则poll不会监控
    3. 相对于select, poll没有本质上的改变; 但是poll可以突破1024的限制.在/proc/sys/fs/file-max查看一个进程可以打开的socket描述符上限,如果需要可以修改配置文件: /etc/security/limits.conf,加入如下配置信息, 然后重启终端即可生效
    * soft nofile 1024
    * hard nofile 100000
    
    • 1
    • 2

    soft和hard分别表示ulimit命令可以修改的最小限制和最大限制

    poll代码实现

    #include <errno.h>
    #include <netinet/in.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/poll.h>
    #include <sys/epoll.h>
    #include <pthread.h>
    #define MAX_LEN  4096
    #define POLL_SIZE    1024
    
    int main(int argc, char **argv) {
        int listenfd, connfd, n;
        struct sockaddr_in svr_addr;
        char buff[MAX_LEN];
    
        if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
            printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
            return 0;
        }
    
        memset(&svr_addr, 0, sizeof(svr_addr));
        svr_addr.sin_family = AF_INET;
        svr_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        svr_addr.sin_port = htons(8081);
    
        if (bind(listenfd, (struct sockaddr *) &svr_addr, sizeof(svr_addr)) == -1) {
            printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
            return 0;
        }
    
        if (listen(listenfd, 10) == -1) {
            printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
            return 0;
        }
    
        //poll
        struct pollfd fds[POLL_SIZE] = {0};
        fds[0].fd = listenfd;
        fds[0].events = POLLIN;
    
        int max_fd = listenfd;
        int i = 0;
        for (i = 1; i < POLL_SIZE; i++) {
            fds[i].fd = -1;
        }
        while (1) {
            int nready = poll(fds, max_fd + 1, -1);
    
            if (fds[0].revents & POLLIN) {
                struct sockaddr_in client = {};
                socklen_t len = sizeof(client);
                if ((connfd = accept(listenfd, (struct sockaddr *) &client, &len)) == -1) {
                    printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
                    return 0;
                }
                printf("accept \n");
                fds[connfd].fd = connfd;
                fds[connfd].events = POLLIN;
                if (connfd > max_fd) max_fd = connfd;
                if (--nready == 0) continue;
            }
            //int i = 0;
            for (i = listenfd + 1; i <= max_fd; i++) {
                if (fds[i].revents & POLLIN) {
                    n = recv(i, buff, MAX_LEN, 0);
                    if (n > 0) {
                        buff[n] = '\0';
                        printf("recv msg from client: %s\n", buff);
                        send(i, buff, n, 0);
                    }
                    else if (n == 0) { //
                        fds[i].fd = -1;
                        close(i);
                    }
                    if (--nready == 0) break;
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    高并发服务器模型-epoll (重点)

    epoll介绍

      将检测文件描述符的变化委托给内核去处理, 然后内核将发生变化的文件描述符对应的事件返回给应用程序。

      记住,epoll是事件驱动的,其底层数据结构是红黑树,红黑树的key是fd,val是事件,返回的是事件。

    epoll有两种工作模式,ET和LT模式。

    水平触发LT:

    • 高电平代表1
    • 只要缓冲区中有数据, 就一直通知

    边缘触发ET:

    • 电平有变化就代表1
    • 缓冲区中有数据只会通知一次, 之后再有新的数据到来才会通知(若是读数据的时候没有读完, 则剩余的数据不会再通知, 直到有新的数据到来)

      epoll默认是水平触发LT,在需要高性能的场景下,可以改成边缘ET非阻塞方式来提高效率。

      一般使用LT是一次性读数据读不完,数据较多的情况。而一次性能够读完,小数据量则用边缘ET。

      ET模式由于只通知一次, 所以在读的时候要循环读, 直到读完, 但是当读完之后read就会阻塞, 所以应该将该文件描述符设置为非阻塞模式(fcntl函数)

      read函数在非阻塞模式下读的时候, 若返回-1, 且errno为EAGAIN, 则表示当前资源不可用, 也就是说缓冲区无数据(缓冲区的数据已经读完了); 或者当read返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲区中已没有数据可读了,也就可以认为此时读事件已处理完成。

    epoll反应堆

      反应堆: 一个小事件触发一系列反应

      epoll反应堆的思想: c++的封装思想(把数据和操作封装到一起)

    • 将描述符,事件,对应的处理方法封装在一起
    • 当描述符对应的事件发生了, 自动调用处理方法(其实原理就是回调函数)

      epoll反应堆的核心思想是: 在调用epoll_ctl函数的时候, 将events上树的时候,利用epoll_data_t的ptr成员, 将一个文件描述符,事件和回调函数封装成一个结构体, 然后让ptr指向这个结构体。然后调用epoll_wait函数返回的时候, 可以得到具体的events, 然后获得events结构体中的events.data.ptr指针, ptr指针指向的结构体中有回调函数, 最终可以调用这个回调函数。

    struct epoll_event {
    	uint32_t     events;      /* Epoll events */
    	epoll_data_t data;        /* User data variable */
    };
    typedef union epoll_data {
    	void        *ptr;
    	int          fd;
    	uint32_t     u32;
    	uint64_t     u64;
    } epoll_data_t;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    epoll-api

    int epoll_create(int size);
    
    • 1

    函数说明: 创建一个树根

    参数说明:

    • size: 最大节点数, 此参数在linux 2.6.8已被忽略, 但必须传递一个大于0的数,历史意义,用epoll_create1也行。
    • 返回值:
    成功: 返回一个大于0的文件描述符, 代表整个树的树根.
    失败: 返回-1, 并设置errno值.
    
    • 1
    • 2

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

    函数说明: 将要监听的节点在epoll树上添加, 删除和修改

    参数说明:

    • epfd: epoll树根

    • op:

    EPOLL_CTL_ADD: 添加事件节点到树上
    EPOLL_CTL_DEL: 从树上删除事件节点
    EPOLL_CTL_MOD: 修改树上对应的事件节点
    
    • 1
    • 2
    • 3
    • fd: 事件节点对应的文件描述符
    • event: 要操作的事件节点
    struct epoll_event {
    	uint32_t     events;      /* Epoll events */
    	epoll_data_t data;        /* User data variable */
    };
    typedef union epoll_data {
    	void        *ptr;
    	int          fd;
    	uint32_t     u32;
    	uint64_t     u64;
    } epoll_data_t;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • event.events常用的有:
    EPOLLIN: 读事件
    EPOLLOUT: 写事件
    EPOLLERR: 错误事件
    EPOLLET: 边缘触发模式
    
    • 1
    • 2
    • 3
    • 4
    • event.fd: 要监控的事件对应的文件描述符

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

    函数说明:等待内核返回事件发生

    参数说明:

    • epfd: epoll树根
    • events: 传出参数, 其实是一个事件结构体数组
    • maxevents: 数组大小
    • timeout:
    	-1: 表示永久阻塞
    	0: 立即返回
    	>0: 表示超时等待事件
    
    • 1
    • 2
    • 3

    返回值:

    • 成功: 返回发生事件的个数
    • 失败: 若timeout=0, 没有事件发生则返回; 返回-1, 设置errno值

      epoll_wait的events是一个传出参数, 调用epoll_ctl传递给内核什么值, 当epoll_wait返回的时候, 内核就传回什么值,不会对struct event的结构体变量的值做任何修改。

    epoll优缺点

    epoll优点:

    1. 性能高,百万并发不在话下,而select就不行

    epoll缺点:

    1. 不能跨平台,linux下的

    epoll代码实现

    #include <errno.h>
    #include <netinet/in.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/poll.h>
    #include <sys/epoll.h>
    #include <pthread.h>
    
    #define POLL_SIZE 1024
    #define MAX_LEN  4096
    
    int main(int argc, char **argv) {
        int listenfd, connfd, n;
        char buff[MAX_LEN];
        struct sockaddr_in svr_addr;
        memset(&svr_addr, 0, sizeof(svr_addr));
        svr_addr.sin_family = AF_INET;
        svr_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        svr_addr.sin_port = htons(8081);
    
        if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
            printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
            return 0;
        }
    
        if (bind(listenfd, (struct sockaddr *) &svr_addr, sizeof(svr_addr)) == -1) {
            printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
            return 0;
        }
    
        if (listen(listenfd, 10) == -1) {
            printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
            return 0;
        }
    
        int epfd = epoll_create(1); //int size
    
        struct epoll_event events[POLL_SIZE] = {0};
        struct epoll_event ev;
    
        ev.events = EPOLLIN;
        ev.data.fd = listenfd;
    
        epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
    
        while (1) {
            int nready = epoll_wait(epfd, events, POLL_SIZE, 5);
            if (nready == -1) {
                continue;
            }
            int i = 0;
            for (i = 0; i < nready; i++) {
                int actFd = events[i].data.fd;
                if (actFd == listenfd) {
                    struct sockaddr_in cli_addr;
                    socklen_t len = sizeof(cli_addr);
                    if ((connfd = accept(listenfd, (struct sockaddr *) &cli_addr, &len)) == -1) {
                        printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
                        return 0;
                    }
                    printf("accept\n");
                    ev.events = EPOLLIN;
                    ev.data.fd = connfd;
                    epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
                }
                else if (events[i].events & EPOLLIN) {
                    n = recv(actFd, buff, MAX_LEN, 0);
                    if (n > 0) {
                        buff[n] = '\0';
                        printf("recv msg from client: %s\n", buff);
                        send(actFd, buff, n, 0);
                    }
                    else if (n == 0) { //
                        epoll_ctl(epfd, EPOLL_CTL_DEL, actFd, NULL);
                        close(actFd);
                    }
                }
            }
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
  • 相关阅读:
    计算机网络-第2章物理层
    Linux ARM平台开发系列讲解(platform平台子系统) 2.10.1 platform平台子系统介绍
    【C# Programming】值类型、良构类型
    产品经理不得不知道的电商API接口对接流程梳理
    基于Python+Django的热门旅游景点数据分析系统的设计与实现(源码+lw+部署文档+讲解等)
    Redis目录
    poi-tl实现对Word模板中复杂表格的数据填充
    Excel查找函数的高级用法
    2022金九银十 —— 招聘有感,给各位测试同学的一些建议
    VS2019无法设置Qt版本解决方案
  • 原文地址:https://blog.csdn.net/qq_42956653/article/details/125617038