Linux多路复用--结合网络Socket编程

Linux多路复用--结合网络Socket编程

  • 前言
  • 一、多路复用介绍
    • 1.1 用户空间与内核空间
    • 1.2 多路复用简介
    • 1.3 API函数介绍
  • 二、多路复用API函数详解
    • 2.1 select
    • 2.2 poll
    • 2.3 epoll
  • 三、源码效果与展示
    • 3.1 select服务端源码
    • 3.2 select源码效果展示
    • 3.3 poll服务端源码
    • 3.4 poll源码效果展示
    • 3.5 epoll服务器端源码
    • 3.6 epoll源码效果展示
  • 四、总结与反思

注:本文系湛江市岭南师范学院物联网俱乐部原创部分训练计划,转载请保留声明。

前言

 今天是闭关的第28天,Linux APUE的学习到目前为止已经经历了一大半,后面或许还会有一部分Linux APUE的其它知识,如Linux 动态库和Linux静态库、Makefile等等。(在本文正式开始讲解之前,先吐槽一下,很多博客的一些博主,不尊重其它博主的原创博客知识产权,抄来抄去,请问意义何在?另外,我还发现很多博主发表博客都是以赚钱为主,这是不太地道的,虽然知识付费无可厚非,但是应该多提倡开源,大家一起学习才能进步)。现在将来带大家一起学习Linux的多路复用,在这里我们将用到三个函数:select()、poll()和epoll()。

一、多路复用介绍

1.1 用户空间与内核空间

  现在的操作系统都是采用虚拟存储器,对于32位操作系统而言,它的寻址空间位4G,为了保证用户进程不能直接操作内核,保证内核的安全,操作系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间,针对linux来说,将最高的1G供内核使用,称为内核空间(从虚拟地址 0xC0000000 到 0xFFFFFFFF),较低的 3G 字节(从虚拟地址 0x00000000 到 0xBFFFFFFF),供各个进程使用,称为用户空间。

1.2 多路复用简介

  I/O多路复用指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。操作系统上的I/O用户空间和内核空间的数据交互,因此I/O操作通常包含以下两个步骤:

  ①等待网路数据到达网卡 -》读取/写入内核缓冲区
  ②从内核缓冲区复制数据 –> 用户空间(读)/从用户空间复制数据 -> 内核缓冲区(写)

  而判断一个I/O模型是同步还是异步,主要看第二步,数据在用户和内核空间之间复制的时候是不是会阻塞当前进程,如果会则为同步,否则为异步,所谓 I/O 多路复用指的就是 select/poll/epoll 这一系列的多路选择器:支持单一线程同时监听多个文件描述符(I/O 事件),阻塞等待,并在其中某个文件描述符可读写时收到通知。 I/O 复用其实复用的不是 I/O 连接,而是复用线程,让一个 thread of control 能够处理多个连接(I/O 事件)

1.3 API函数介绍

(1)select多路复用
  select()函数允许进程指示内核等待多个事件(文件描述符)中的任何一个发生,并只在有一个或多个事件发生或经历一段指定时间后才唤醒它,然后接下来判断究竟是哪个文件描述符发生了事件并进行相应的处理。

  
(2)poll多路复用
  select()和poll()系统调用的本质一样,前者在BSD UNIX中引入的,后者在System V中引入的。poll()的机制与 select() 类似,与 select() 在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是 poll() 没有最大文件描述符数量的限制(但是数量过大后性能也是会下降)。poll() 和 select() 同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

  
(3)epoll多路复用
  poll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

二、多路复用API函数详解

2.1 select

#include 
#include 
struct timeval
{
 long tv_sec; //seconds
 long tv_usec; //microseconds
};
FD_ZERO(fd_set* fds) //清空集合
FD_SET(int fd, fd_set* fds) //将给定的描述符加入集合
FD_ISSET(int fd, fd_set* fds) //判断指定描述符是否在集合中
FD_CLR(int fd, fd_set* fds) //将给定的描述符从文件中删除 
int select(int max_fd, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout);
说明: select监视并等待多个文件描述符的属性发生变化,它监视的属性分3类,分别是readfds(文件描述符有数据到来可读)writefds(文件描述符可写)、和exceptfds(文件描述符异常)。调用后select函数会阻塞,直到有描述符就绪(有数据可读、可写、
或者有错误异常),或者超时( timeout 指定等待时间)发生函数才返回。当select()函数返回后,可以通过遍历 fdset,来找到
究竟是哪些文件描述符就绪。
 
1. select函数的返回值是就绪描述符的数目,超时时返回0,出错返回-12. 第一个参数max_fd指待测试的fd的总个数,它的值是待测试的最大文件描述符加1。Linux内核从0开始到max_fd-1扫描文件描述
符,如果有数据出现事件(读、写、异常)将会返回;假设需要监测的文件描述符是8,9,10,那么Linux内核实际也要监测0~7,此时真
正带测试的文件描述符是0~10总共11个,即max(8,9,10)+1,所以第一个参数是所有要监听的文件描述符中最大的+13. 中间三个参数readset、writeset和exceptset指定要让内核测试读、写和异常条件的fd集合,如果不需要测试的可以设置为
NULL4. 最后一个参数是设置select的超时时间,如果设置为NULL则永不超时;
 需要注意的是待测试的描述集总是从012...开始的。 所以, 假如你要检测的描述符为8910, 那么系统实际也要
监测0123456, 7, 此时真正待测试的描述符的个数为11个, 也就是max(8910+ 1
 在Linux内核有个参数__FD_SETSIZE定义了每个FD_SET的句柄个数中,这也意味着select所用到的FD_SET是有限的,也正是这个
原因select()默认只能同时处理1024个客户端的连接请求:
 /linux/posix_types.h:
 #define __FD_SETSIZE 1024

  基于select的I/O复用模型的是单进程执行可以为多个客户端服务,这样可以减少创建线程或进程所需要的CPU时间片或内存
资源的开销;此外几乎所有的平台上都支持select(),其良好跨平台支持是它的另一个优点。当然它也有两个主要的缺点:

  1. 每次调用 select()都需要把fd集合从用户态拷贝到内核态,之后内核需要遍历所有传递进来的fd,这时如果客户端fd很多
    时会导致系统开销很大;
  2. 单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过setrlimit()、修改宏定义甚至重
    新编译内核等方式来提升这一限制,但是这样也会造成效率的降低;

2.2 poll

 poll函数的原型说明如下:

#include 
struct pollfd
{
 int fd; /* 文件描述符 */
 short events; /* 等待的事件 */
 short revents; /* 实际发生了的事件 */
} ;
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

 第一个参数用来指向一个struct pollfd类型的数组,每一个pollfd结构体指定了一个被监视的文件描述符,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域,events域中请求的任何事件都可能在revents域中返回。下表列出指定 events 标志以及测试 revents 标志的一些常值:
Linux多路复用--结合网络Socket编程_第1张图片

POLLIN | POLLPRI等价于select()的读事件,POLLOUT |POLLWRBAND等价于select()的写事件。POLLIN等价于POLLRDNORM |POLLRDBAND,而POLLOUT则等价于POLLWRNORM。例如,要同时监视一个文件描述符是否可读和可写.
我们可以设置 events为POLLIN |POLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的events结构体。
如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而不导致阻塞。
这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。

 第二个参数 nfds 指定数组中监听的元素个数;
 第三个参数 timeout指定等待的毫秒数,无论I/O是否准备好,poll都会返回。timeout指定为负数值表示无限超时,使poll()
一直挂起直到一个指定事件发生;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。
这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。
该函数成功调用时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;
失败时,poll()返回-1,并设置errno为下列值之一:
  EBADF   一个或多个结构体中指定的文件描述符无效。
  EFAULTfds   指针指向的地址超出进程的地址空间。
  EINTR     请求的事件之前产生一个信号,调用可以重新发起。
  EINVALnfds  参数超出PLIMIT_NOFILE值。
  ENOMEM   可用内存不足,无法完成请求。

2.3 epoll

  在linux 没有实现epoll事件驱动机制之前,我们一般选择用select或者poll等IO多路复用的方法来实现并发服务程序。自
  Linux 2.6内核正式引入epoll以来,epoll已经成为了目前实现高性能网络服务器的必备技术,在大数据、高并发、集群等一些名
词唱得火热之年代,select和poll的用武之地越来越有限,风头已经被epoll占尽。

select的缺点:

  1. 单个进程能够监视的文件描述符的数量存在最大限制,通常是1024,当然可以更改数量,但由于select采用轮询的方式扫
    描文件描述符,文件描述符数量越多,性能越差;
  2. 内核 / 用户空间内存拷贝问题,select需要复制大量的句柄数据结构,产生巨大的开销;
  3. select返回的是含有整个句柄的数组,应用程序需要遍历整个数组才能发现哪些句柄发生了事件;
  4. select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么之后每次select调用还是会将这些文件描述符通知进程。

  相比select模型,poll使用链表保存文件描述符,因此没有了监视文件数量的限制,但其他三个缺点依然存在。拿select模型为例,假设我们的服务器需要支持100万的并发连接,则在__FD_SETSIZE 为1024的情况下,则我们至少需要开辟1k个进程才能实现100万的并发连接。除了进程间上下文切换的时间消耗外,从内核/用户空间大量的无脑内存拷贝、数组轮询等,是系统难以承受的。因此,基于select模型的服务器程序,要达到10万级别的并发访问,是一个很难完成的任务。
  epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

  LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。
   ET (edge-triggered)是高速工作方式,只支持non-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。

  ET和LT的区别就在这里体现,LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读,则不断的通知你。而ET则只在事件发生之时通知。可以简单理解为LT是水平触发,而ET则为边缘触发。LT模式只要有事件未处理就会触发,而ET则只在高低电平变换时(即状态从1到0或者0到1)触发。

  由于epoll的实现机制与select/poll机制完全不同,上面所说的 select的缺点在epoll上不复存在。设想一下如下场景:有100万个客户端同时与一个服务器进程保持着TCP连接。而每一时刻,通常只有几百上千个TCP连接是活跃的(事实上大部分场景都是这种情况)。如何实现这样的高并发?在select/poll时代,服务器进程每次都把这100万个连接告诉操作系统(从用户态复制句柄数据结构到内核态),让操作系统内核去查询这些套接字上是否有事件发生,轮询完后,再将句柄数据复制到用户态,让服务器应用程序轮询处理已发生的网络事件,这一过程资源消耗较大,因此,select/poll一般只能处理几千的并发连接。
  epoll的设计和实现与select完全不同。epoll通过在Linux内核中申请一个简易的文件系统,把原先的select/poll调用分成了3
个部分:

  1. 调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)
  2. 调用epoll_ctl向epoll对象中添加这100万个连接的套接字
  3. 调用epoll_wait收集发生的事件的连接

  如此一来,要实现上面说是的场景,只需要在进程启动时建立一个epoll对象,然后在需要的时候向这个epoll对象中添加或者
删除连接。同时,epoll_wait的效率也非常高,因为调用epoll_wait时,并没有一股脑的向操作系统复制这100万个连接的句柄数
据,内核也不需要去遍历全部的连接。
Linux多路复用--结合网络Socket编程_第2张图片
Linux多路复用--结合网络Socket编程_第3张图片
Linux多路复用--结合网络Socket编程_第4张图片
Linux多路复用--结合网络Socket编程_第5张图片
Linux多路复用--结合网络Socket编程_第6张图片

三、源码效果与展示

3.1 select服务端源码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BACKLOG 13

#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))

static inline void printf_usage(char *progname);
int socket_server_init(char *listen_ip, int listen_port);

int main(int argc, char **argv)
{
    int listenfd = -1;
    int client_fd = -1;
    int rv = -1;
    int listen_port;
    struct sockaddr_in cli_addr;
    socklen_t cli_len = sizeof(cli_addr);
    char buff[1024];
    fd_set rdset;
    int i, j;
    int found;
    int maxfd = 0;
    int fds_array[1024];

    if (argc < 2)
    {
        printf("Program usage:%s [Port]\n", argv[0]);
        return -1;
    }
    listen_port = atoi(argv[1]);
    if ((listenfd = socket_server_init(NULL, listen_port)) < 0)
    {
        printf("ERROR: %s server listen on port %d failure\n", argv[0], listen_port);
        return -2;
    }

    printf("%s server start to listen on port %d\n", argv[0], listen_port);

    for (i = 0; i < ARRAY_SIZE(fds_array); i++)
    {
        fds_array[i] = -1;
    }
    fds_array[0] = listenfd;

    for (;;)
    {
        FD_ZERO(&rdset); //清空集合
        for (i = 0; i < ARRAY_SIZE(fds_array); i++)
        {
            if (fds_array[i] < 0)
                continue;
            maxfd = fds_array[i] > maxfd ? fds_array[i] : maxfd;
            FD_SET(fds_array[i], &rdset);
        }
        rv = select(maxfd + 1, &rdset, NULL, NULL, NULL);
        if (rv < 0)
        {
            printf("select failure:%s\n", strerror(errno));
            break;
        }
        else if (rv == 0)
        {
            printf("select failure:%s\n", strerror(errno));
            continue;
        }
        /*判断是否有新的客户端加入进来,listen_fd相当于门卫*/
        if (FD_ISSET(listenfd, &rdset))
        {
            if ((client_fd = accept(listenfd, (struct sockaddr *)&cli_addr, &cli_len)) < 0)
            {
                printf("accepet new client failure:%s\n", strerror(errno));
                continue;
            }
            found = 0;
            /*扫描(遍历)判断是否还有-1(空位置),有的话就把clent_fd加入进去,并且found为1,直接break,跳出for循环,如果未满,最后当i>ARRAY_SIZE(fds_array)就跳出for循环*/
            for (i = 0; i < ARRAY_SIZE(fds_array); i++)
            {
                if (fds_array[i] < 0)
                {
                    printf("accept new client [%d] and add it into array\n", client_fd);
                    fds_array[i] = client_fd;
                    found = 1;
                    break;
                }
            }
            if (!found)
            {
                printf("accept new client[%d] but full so refuse it\n", client_fd);
                close(client_fd);
            }
        }
        else /*在上面的if中就是判断有没有新的客户端连接进来,那么这个else就是已经连接的客户端了*/
        {
            for (i = 0; i < ARRAY_SIZE(fds_array); i++)
            {
                if (fds_array[i] < 0 || !FD_ISSET(fds_array[i], &rdset))
                    continue;
                memset(buff, 0, sizeof(buff));
                if ((rv = read(fds_array[i], buff, sizeof(buff))) < 0)
                {
                    printf("socket[%d] read failure or get disconnect.\n", fds_array[i]);
                    close(fds_array[i]);
                    fds_array[i] = -1;
                }
                else
                {
                    printf("socket[%d] read get %d bytes data,buff:%s\n", fds_array[i], rv, buff);
                    for (j = 0; j < rv; j++)
                    {
                        buff[j] = toupper(buff[j]);
                    }
                    if (write(fds_array[i], buff, rv < 0))
                    {
                        printf("socket[%d] write failure: %s\n", fds_array[i], strerror(errno));
                        close(fds_array[i]);
                        fds_array[i] = -1;
                    }
                }
            }
        }
    }
CleanUp:
    close(listenfd);
    return 0;
}

int socket_server_init(char *listen_ip, int listen_port)
{
    int listen_fd = -1;
    int rv = 0;
    int on = 1;
    struct sockaddr_in serv_addr;
    socklen_t serv_len = sizeof(serv_addr);

    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd < 0)
    {
        printf("socket create failure : %s, listen_fd:[%d]\n", strerror(errno), listen_fd);
        return -1;
    }
    printf("socket create success its listen_fd:[%d]\n", listen_fd);

    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(listen_port);

    if (!listen_ip) /* Listen all the local IP address */
    {
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    }
    else /* listen the specified IP address */
    {
        if (inet_pton(AF_INET, listen_ip, &serv_addr.sin_addr) <= 0)
        {
            printf("inet_pton() set listen IP address failure.\n");
            rv = -2;
            goto CleanUp;
        }
    }

    if (bind(listen_fd, (struct sockaddr *)&serv_addr, serv_len) < 0)
    {
        printf("socket create failure : %s\n", strerror(errno));
        return -2;
        goto CleanUp;
    }
    printf("socket:[%d],bind on port[%d]\n", listen_fd, listen_port);
    listen(listen_fd, BACKLOG);
CleanUp:
    if (rv < 0)
        close(listen_fd);
    else
        rv = listen_fd;
    return rv;
}

3.2 select源码效果展示

①客户端连接图
Linux多路复用--结合网络Socket编程_第7张图片
②服务器运行图:
Linux多路复用--结合网络Socket编程_第8张图片
③客户端发送消息与接收消息图:
Linux多路复用--结合网络Socket编程_第9张图片
④服务器接收消息图:
Linux多路复用--结合网络Socket编程_第10张图片

3.3 poll服务端源码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BACKLOG 13

#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))

static inline void printf_usage(char *progname);
int socket_server_init(char *listen_ip, int listen_port);

int main(int argc, char **argv)
{
    int listenfd = -1;
    int client_fd = -1;
    int rv = -1;
    int listen_port;
    struct sockaddr_in cli_addr;
    socklen_t cli_len = sizeof(cli_addr);
    char buff[1024];
    fd_set rdset;
    int i, j;
    int found;
    int max;
    struct pollfd fds_array[1024];

    if (argc < 2)
    {
        printf("Program usage:%s [Port]\n", argv[0]);
        return -1;
    }
    listen_port = atoi(argv[1]);
    if ((listenfd = socket_server_init(NULL, listen_port)) < 0)
    {
        printf("ERROR: %s server listen on port %d failure\n", argv[0], listen_port);
        return -2;
    }

    printf("%s server start to listen on port %d\n", argv[0], listen_port);

    for (i = 0; i < ARRAY_SIZE(fds_array); i++)
    {
        fds_array[i].fd = -1;
    }
    fds_array[0].fd = listenfd;
    fds_array[0].events = POLLIN;
    max = 0;

    for (;;)
    {

        rv = poll(fds_array, max + 1, -1);
        if (rv < 0)
        {
            printf("poll failure:%s\n", strerror(errno));
            break;
        }
        else if (rv == 0)
        {
            printf("poll failure:%s\n", strerror(errno));
            continue;
        }

        if (fds_array[0].revents & POLLIN)
        {
            if ((client_fd = accept(listenfd, (struct sockaddr *)&cli_addr, &cli_len)) < 0)
            {
                printf("accepet new client failure:%s\n", strerror(errno));
                continue;
            }
            found = 0;
            /*扫描(遍历)判断是否还有-1(空位置),有的话就把clent_fd加入进去,并且found为1,直接break,跳出for循环,如果未满,最后当i>ARRAY_SIZE(fds_array)就跳出for循环*/
            for (i = 0; i < ARRAY_SIZE(fds_array); i++)
            {
                if (fds_array[i].fd < 0)
                {
                    printf("accept new client [%d] and add it into array\n", client_fd);
                    fds_array[i].fd = client_fd;
                    fds_array[i].events = POLLIN;
                    found = 1;
                    break;
                }
            }
            if (!found)
            {
                printf("accept new client[%d] but full so refuse it\n", client_fd);
                close(client_fd);
                continue;
            }
            max = i > max ? i : max;
            if (--rv < 0) //这句感觉作用不大
                continue;
        }
        else /*在上面的if中就是判断有没有新的客户端连接进来,那么这个else就是已经连接的客户端了*/
        {
            for (i = 0; i < ARRAY_SIZE(fds_array); i++)
            {
                if (fds_array[i].fd < 0 || !fds_array[i].revents & POLLIN)
                    continue;
                memset(buff, 0, sizeof(buff));
                if ((rv = read(fds_array[i].fd, buff, sizeof(buff))) < 0)
                {
                    printf("socket[%d] read failure or get disconnect.\n", fds_array[i].fd);
                    close(fds_array[i].fd);
                    fds_array[i].fd = -1;
                    if (i == max) //让MAX进行动态变化
                        max--;
                }
                else
                {
                    printf("socket[%d] read get %d bytes data,buff:%s\n", fds_array[i].fd, rv, buff);
                    for (j = 0; j < rv; j++)
                    {
                        buff[j] = toupper(buff[j]);
                    }
                    if (write(fds_array[i].fd, buff, rv < 0))
                    {
                        printf("socket[%d] write failure: %s\n", fds_array[i].fd, strerror(errno));
                        close(fds_array[i].fd);
                        fds_array[i].fd = -1;
                    }
                }
            }
        }
    }
CleanUp:
    close(listenfd);
    return 0;
}

int socket_server_init(char *listen_ip, int listen_port)
{
    int listen_fd = -1;
    int rv = 0;
    int on = 1;
    struct sockaddr_in serv_addr;
    socklen_t serv_len = sizeof(serv_addr);

    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd < 0)
    {
        printf("socket create failure : %s, listen_fd:[%d]\n", strerror(errno), listen_fd);
        return -1;
    }
    printf("socket create success its listen_fd:[%d]\n", listen_fd);

    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(listen_port);

    if (!listen_ip) /* Listen all the local IP address */
    {
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    }
    else /* listen the specified IP address */
    {
        if (inet_pton(AF_INET, listen_ip, &serv_addr.sin_addr) <= 0)
        {
            printf("inet_pton() set listen IP address failure.\n");
            rv = -2;
            goto CleanUp;
        }
    }

    if (bind(listen_fd, (struct sockaddr *)&serv_addr, serv_len) < 0)
    {
        printf("socket create failure : %s\n", strerror(errno));
        return -2;
        goto CleanUp;
    }
    printf("socket:[%d],bind on port[%d]\n", listen_fd, listen_port);
    listen(listen_fd, BACKLOG);
CleanUp:
    if (rv < 0)
        close(listen_fd);
    else
        rv = listen_fd;
    return rv;
}

3.4 poll源码效果展示

①客户端连接图:
Linux多路复用--结合网络Socket编程_第11张图片
②服务器运行图:
在这里插入图片描述

③客户端发送消息与接收消息图:
Linux多路复用--结合网络Socket编程_第12张图片

④服务器接收消息图:
Linux多路复用--结合网络Socket编程_第13张图片

3.5 epoll服务器端源码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BACKLOG 13
#define MAX_EVENTS 512
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))

static inline void printf_usage(char *progname);
int socket_server_init(char *listen_ip, int listen_port);
void set_socket_rlimit(void);

int main(int argc, char **argv)
{
    int listenfd = -1;
    int client_fd = -1;
    int rv = -1;
    int listen_port;
    struct sockaddr_in cli_addr;
    socklen_t cli_len = sizeof(cli_addr);
    char buff[1024];
    fd_set rdset;
    int i, j;
    int found;
    int epollfd;
    struct epoll_event event;
    struct epoll_event event_array[MAX_EVENTS];
    int events;

    if (argc < 2)
    {
        printf("Program usage:%s [Port]\n", argv[0]);
        return -1;
    }
    listen_port = atoi(argv[1]);
    set_socket_rlimit();
    if ((listenfd = socket_server_init(NULL, listen_port)) < 0)
    {
        printf("ERROR: %s server listen on port %d failure\n", argv[0], listen_port);
        return -2;
    }

    printf("%s server start to listen on port %d\n", argv[0], listen_port);

    if ((epollfd = epoll_create(MAX_EVENTS)) < 0)
    {
        printf("epoll_create() failure: %s\n", strerror(errno));
        return -3;
    }
    //event.events = EPOLLIN|EPOLLET;
    event.events = EPOLLIN;
    event.data.fd = listenfd;
    /**/
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &event) < 0)
    {
        printf("epoll add listen socket failure: %s\n", strerror(errno));
        return -4;
    }

    for (;;)
    {
        events = epoll_wait(epollfd, event_array, MAX_EVENTS, -1);
        if (events < 0)
        {
            printf("epoll failure: %s\n", strerror(errno));
            break;
        }
        else if (events == 0)
        {
            printf("epoll get timeout\n");
            continue;
        }
        for (i = 0; i < events; i++)
        {
            if ((event_array[i].events & EPOLLERR) || (event_array[i].events & EPOLLHUP))
            {
                printf("epoll_wait get error on fd[%d]: %s\n", event_array[i].data.fd, strerror(errno));
                epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
                close(event_array[i].data.fd);
            }
            /* listen socket get event means new client start connect now */
            if (event_array[i].data.fd == listenfd)
            {
                if ((client_fd = accept(listenfd, (struct sockaddr *)NULL, NULL)) < 0)
                {
                    printf("accept new client failure: %s\n", strerror(errno));
                    continue;
                }
                event.data.fd = client_fd;
                //event.events = EPOLLIN|EPOLLET;
                event.events = EPOLLIN;
                if (epoll_ctl(epollfd, EPOLL_CTL_ADD, client_fd, &event) < 0)
                {
                    printf("epoll add client socket failure: %s\n", strerror(errno));
                    close(event_array[i].data.fd);
                    continue;
                }
                printf("epoll add new client socket[%d] ok.\n", client_fd);
            }
            else /* already connected client socket get data incoming */
            {
                memset(buff, 0, sizeof(buff));
                if ((rv = read(event_array[i].data.fd, buff, sizeof(buff))) <= 0)
                {
                    printf("socket[%d] read failure or get disconncet and will be removed.\n",
                           event_array[i].data.fd);
                    epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
                    close(event_array[i].data.fd);
                    continue;
                }
                else
                {
                    printf("socket[%d] read get %d bytes data\n", event_array[i].data.fd, rv);
                    /* convert letter from lowercase to uppercase */
                    for (j = 0; j < rv; j++)
                        buff[j] = toupper(buff[j]);
                    if (write(event_array[i].data.fd, buff, rv) < 0)
                    {
                        printf("socket[%d] write failure: %s\n", event_array[i].data.fd, strerror(errno));
                        epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
                        close(event_array[i].data.fd);
                    }
                }
            }
        }
    }
CleanUp:
    close(listenfd);
    return 0;
}

int socket_server_init(char *listen_ip, int listen_port)
{
    int listen_fd = -1;
    int rv = 0;
    int on = 1;
    struct sockaddr_in serv_addr;
    socklen_t serv_len = sizeof(serv_addr);

    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd < 0)
    {
        printf("socket create failure : %s, listen_fd:[%d]\n", strerror(errno), listen_fd);
        return -1;
    }
    printf("socket create success its listen_fd:[%d]\n", listen_fd);

    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(listen_port);

    if (!listen_ip) /* Listen all the local IP address */
    {
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    }
    else /* listen the specified IP address */
    {
        if (inet_pton(AF_INET, listen_ip, &serv_addr.sin_addr) <= 0)
        {
            printf("inet_pton() set listen IP address failure.\n");
            rv = -2;
            goto CleanUp;
        }
    }

    if (bind(listen_fd, (struct sockaddr *)&serv_addr, serv_len) < 0)
    {
        printf("socket create failure : %s\n", strerror(errno));
        return -2;
        goto CleanUp;
    }
    printf("socket:[%d],bind on port[%d]\n", listen_fd, listen_port);
    listen(listen_fd, BACKLOG);
CleanUp:
    if (rv < 0)
        close(listen_fd);
    else
        rv = listen_fd;
    return rv;
}
void set_socket_rlimit(void)
{
    struct rlimit limit = {0};
    getrlimit(RLIMIT_NOFILE, &limit);
    limit.rlim_cur = limit.rlim_max;
    setrlimit(RLIMIT_NOFILE, &limit);
    printf("set socket open fd max count to %ld\n", limit.rlim_max);
}

3.6 epoll源码效果展示

①客户端连接图
Linux多路复用--结合网络Socket编程_第14张图片

②服务器运行图:
Linux多路复用--结合网络Socket编程_第15张图片

③客户端发送消息与接收消息图:
Linux多路复用--结合网络Socket编程_第16张图片

④服务器接收消息图:
Linux多路复用--结合网络Socket编程_第17张图片

四、总结与反思

Linux的多路复用确实比较难,要把这几套代码理解掉可是真的饿有点难度,大家好好努力吧。

你可能感兴趣的:(物联网,嵌入式硬件)