多路复用I/O-select

简介: 多路复用I/O-select

多路复用IO


第一章 高性能服务器技术栈 (select)

第二章 高性能服务器技术栈 (epool/poll)


前言


现在网络技术越来月普及,网络充斥了我们的生活当中,网络场景越来越复杂。在网络开发中,高并发的情景是在后端开发中司空见惯的场景。掌握并发技术是后端开发中的基本能力。目前实现并发的技术,主要有借助于协程,进程实现;多路复用I/O(select, poll, epool)等。

多路复用(IO multiplexing) 在网络层通常是指select/epoll。 但是有些地方

也称这种IO 方式为事件驱动IO(event driven IO)。


一、并发基础


实现并发的技术主要有select,poll ,epoll 等技术,它们各有优缺点(它们在内核中的实现其它章节讲解)。select 技术是通过轮询遍历的方式处理网路就绪IO。epool 是通过网络事件的方式处理网络就绪IO 。具体比较其它章节讲解


1.1 select 技术

select 引入了集合(fd_set)概念Select采用一个bit表,每个fd对应表中的一个bit位,采用此方式存储放入的fd.通过轮询检测集合中的句柄。等待检测的fd 准备就绪了(fd 已经是非阻塞状态)。然后返回就绪的文件描述符的个数。在进行进一步处理。IO有三种状态,可读,可写,异常。

接口函数

/* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
/**常用的select 函数*/
int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

函数接口分别分析*

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

参数解析

maxfd:文件描述符的范围,比监听的最大文件描述符加1。select最多同时监听描述符 数量有个上限,FD_SETSIZE(1024);
readfds:可读:它是指向fd_set结构的指针,fd_set是一个描述符集合。这个集合中是要监控的读类型的文件的描述符。
writefds:可写:它也是指向fd_set结构的指针,这个集合是要监控的写类型的文件的描述符。
errorfds:异常:它是用来监视文件错误异常的文件描述符的集合。
timeout:它是select函数的超时时间,这个参数至关重要,它可以使select处于三种状态。
    1,timeout=NULL 阻塞: 若将NULL以形参传入,即不传入时间结构,则select一直置于阻塞状态,直到监控到文件描述符集合中某个文件描述符发生变化为止;
    2,timeout所指向的结构,时间设为0 非阻塞 :若将时间值设为0秒0毫秒,就变成一个纯粹的 非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;  
    3,timeout所指向的结构设为非零时间: timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后返回0。

函数的返回值:

1.在正常情况下返回就绪的文件描述符个数;

2.时间超时返回0;

3.出错或者select被某个信号中断返回-1;


NODE


  1. 每次调用select后,都需要重新清空描述符集并重新添加感兴趣的文件描述符。

2.select返回时会将剩余时间填充到timeout参数中,因此重新调用select的时候也要重新初始化该时间参数;

  1. 添加到fd_set中的fd的数目必须小于FD_SETSIZE,否则就会越界。


检测越界工具:

使用valgrind和purify等内存检测工具。

API 接口

void FD_CLR(inr fd, fd_set *fdset);用来清除描述符集合fdset中的描述符fd


int FD_ISSET(int fd,f d_set *fdset);用来检测描述符集合fdset中的描述符fd是否存在, 返回值: 存在返回1,不存在返回0;


void FD_SET(int fd, fd_set *fdset);用来将描述符fd添加到描述符集合fdset中


void FD_ZERO(fd_set *fdset);用来清除描述符集合fdset,把集合置0;


详解:

fd_set 定义的位表中,有两类fd, listenfd 和 rwfd;


1.2 select 结构体分析

fdset 是一个 fd 的位图,采用数组的形式来存储。每个 bit 位代表一个 fd,0表示该 fd 的事件未就绪,1表示该 fd 事件的已就绪。

// fd_set 里面文件描述符的数量,可以使用 ulimit -n 进行查看,默认1024
#define FD_SETSIZE __FD_SETSIZE
// fd_set 的成员是一个长整型的结构体
typedef long int __fd_mask;
// fd_set 记录要监听的 fd 集合,及其对应状态
typedef struct {
    // fds_bits是long类型数组,用来实现位图,长度: 1024/32=32
    // 共1024个bit位,每个bit位代表一个fd,0表示未就绪,1表示就绪
    __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
} fd_set;

1.3 select 使用流程

创建集合(集合的元素:文件描述符),把要等待的fd放入集合(监听)


  1. 创建fd_set集合
  2. 用FD_ZERO初始化集合(将位图所有位置0)
  3. 用FD_SET把要监听文件描述符fd加入集合(将位图所对应的位置为1)
  4. 把listenfd加入集合,监听到listenfd 获取connfd ,然后加入集合;

用 select 系统调用,进程阻塞,当集合中任意一个FD准备就绪,解除阻塞

用 FD_ISSET 检查 fd 是否就绪,就绪就解除阻塞


1.4 select 缺点

  1. 监听的 fd 的数量有限制,默认是1024, 此值可以修改
  2. 每次 select 都需要把监听的 fd 从用户态拷贝到内核态,返回后从内核态拷贝数据到用户态
  3. 轮询遍历所有的 fd 来判断就绪状态,效率低


二、实例


1.多线程多进程实例

代码如下(示例):借助于多线程实现,react模型。

void * routine(void *arg) {
    pthread_detach(pthread_self());
    int clientfd = *(int *)arg;
    unsigned char buff[BUFF_LENGTH];
    while (1) {
        int ret = recv(clientfd, buff, BUFF_LENGTH,0);
        printf("buffer:%s  ret:%dn",buff, ret);
        if (0 == ret) {
            close(clientfd);
            break;
        }
        send(clientfd, buff, ret,0);
      printf("clientfd : %d",clientfd);
    }
}
    printf("threads method\n");
    while(1) {
      struct sockaddr_in client;
      socklen_t  len = sizeof(client);
      int clientfd =  accept(listen_fd, (struct sockaddr *)&client, &len);
        pthread_t threadid;
        pthread_create(&threadid,NULL,routine,&clientfd);
    }

2.select 实例

代码如下(示例): 实现多个客户端连接服务端,通过requese-ack 模式实现。服务端借助于select 实现。


#if defined(PSELECT)
    printf("Slect method\n");
    fd_set rfds,wfds, rset, wset;  /** < fd_set 就是bit位,就是一个位表; rfds 是rset 的备份。
                                    定义两个变量主要是一个变量用来执行查询操作,一个用来置位操作 */
    int max_fd = listen_fd;
    int nready = 0;
    int conn_fd = 0;
    int nbytes = 0;
    int nsends = 0;
    char buff[BUFF_LENGTH] = {0};
    int n = 0;
  int i = 0;
    FD_ZERO(&rfds);
    FD_ZERO(&wfds);
    FD_SET(listen_fd, &rfds);
    while(1) {
        rset = rfds;
        wset = wfds;
        nready = select(max_fd+1, &rset, &wset, NULL, NULL);
        if (0 > nready ) {
            printf("select error\n");
            break;
        }else if(0 == nready) {
            continue;
        }
        if(FD_ISSET(listen_fd, &rset)) {
            struct sockaddr_in client_addr;
            socklen_t length = sizeof(client_addr);
            conn_fd = accept(listen_fd,(struct sockaddr *)&client_addr, &length);
            if (0 > conn_fd ) {
                printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
                return 0;
            }
            FD_SET(conn_fd, &rfds);
            //max_fd = max_fd > conn_fd ? max_fd : conn_fd;
            if (conn_fd > max_fd) max_fd = conn_fd;
            if (--nready == 0) {
                continue;
            }
        }
        for(i = listen_fd+1; i < max_fd +1; i++) {
            if (FD_ISSET(i, &rset)){
                nbytes = recv(i, buff, BUFF_LENGTH, 0);
                if (nbytes > 0) {
                    buff[nbytes] = '\0';
                    printf("recv:%s \n",buff);
                    FD_SET(i, &wfds);
                }else if (0 == nbytes) {
                    FD_CLR(i,&rfds);
                    close(i);
                }
                if (--nready == 0) {
                    break;
                }
            }else if(FD_ISSET(i, &wset)) {
                    FD_SET(i, &rfds);
                n = send(i,buff,nbytes,0);
                if (n == nbytes) {
                    FD_CLR(i,&wfds);
                }
                if (--nready == 0) {
                    break;
                }
                printf("i: %d  maxid:%d listen_fd:%d\n",i,max_fd, listen_fd);
                //FD_CLR(i,&wfds);
            }
        }
    }

总结


例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

本文主要讲解的是可以实现服务器并发方法,借助多线程实现并发,此种方法消耗资源比较大,相当于每一条会话都要借助于一个线程。linxu 系统的线程资源是有限的,此方法不太适合大量的并发。第二种方法借助于select 实现服务器并发,此种方法消耗资源较少。


推荐


[荐一个零声学院免费教程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,DockerTCP/IP,协程,DPDK等技术内容,点击立即学习:

目录
相关文章
|
7月前
|
存储 网络协议
TCP服务器 IO多路复用的实现:select、poll、epoll
TCP服务器 IO多路复用的实现:select、poll、epoll
95 0
|
7月前
|
存储 Linux
图解IO多路复用模型之select、poll、epoll
图解IO多路复用模型之select、poll、epoll
126 0
浅谈select,poll和epoll的区别
云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! select,poll和epoll其实都是操作系统中IO多路复用实现的方法。 select select方法本质其实就是维护了一个文件描述符(fd)数组,以此为基础,实现IO多路复用的功能。
浅谈select,poll和epoll的区别
|
7月前
|
网络协议 Unix C语言
I/O多路复用-select函数
I/O多路复用-select函数
|
7月前
|
监控 Linux
IO多路复用,epoll和select的区别
IO多路复用,epoll和select的区别
48 0
|
Linux Windows
【Linux网络编程】select多路复用
【Linux网络编程】select多路复用
|
Unix
select 机制
SELECT 机制、POLL 机制是完全一样的,只是 APP 接口函数不一样。 在调用 poll , select 函数时可以 传入“超时时间” ,相当于“定个闹钟”。在这段时间内,如果又数据可读,有空间可写等,就会立即返回,否则等到“超时时间”结束时就会返回错误。 poll, select 函数可以检测 多个文件,多种事件。
98 0
|
网络协议 Linux C语言
关于select,epoll的优缺点说明
关于select,epoll的优缺点说明
175 0