UNIX网络编程:I/O复用技术(select、poll、epoll)

本文详细介绍了UNIX系统中的I/O复用技术,包括select、poll和epoll三种方法。I/O复用适用于处理多个描述符的情况,如服务器处理监听和已连接套接口、多服务或多协议服务器等。select工作原理是利用等待队列让进程在无资源时睡眠,资源就绪时唤醒。poll与select类似,但没有最大文件描述符数量限制。epoll提供了LT和ET两种模式,更高效,使用epoll_create和epoll_wait进行事件监听。总结指出,epoll的回调机制和减少拷贝次数提高了性能。
摘要由CSDN通过智能技术生成

Unix下可用的I/O模型一共有五种:阻塞I/O 、非阻塞I/O 、I/O复用 、信号驱动I/O 、异步I/O。此处我们主要介绍第三种I/O符复用。
I/O复用的功能:如果一个或多个I/O条件满足(输入已准备好读,或者描述字可以承接更多输出)时,我们就被通知到。这就是有select、poll、epoll实现。

I/O复用应用场合:
1、当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。在这前一段中已做描述。
2、一个客户同时处理多个套接口是可能的,但很少出现。
3、如果一个TCP服务器机要处理监听套接口,有要处理已连接套接口,一般也要用到I/O复用。
4、如果一个服务器机要处理TCP,有要处理UDP,一般也要使用I/O复用。
5、如果一个服务器要处理多个服务或者多个协议,一般要使用I/O复用。

I/O复用原理图:
这里写图片描述

select:
使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相 同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情 况——读写或是异常。

所要用到的结构体:
struct timeval{
long tv_sec; //等待的秒数
long tv_usec; //等待的微秒数
}

select()函数:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
作用:用来检测描述符中是否有准备好读、写、或异常的描述符
参数1(nfds):被测试的描述字个数;它的值为要被测试的最大描述符个 数+1,而描述字0,1,2,……..,nfds-1;
参数2—4 (readfds)、(writefds)、(exceptfds):这三个参数指定我们要让内核测试读、写、异常条件所需的描述字。当我们在调用该函数,指定好我们所要检测的描述字集后,如果检测三种情况下任何一中情况准备好,则将相应的状态变为可用状态。如果到达函数返回时没有可读可写则返回失败。如果我们不关心其中哪个状态,可将其设为NULL。
参数5(timeout):指定等待时间,有三种情况:
(1)、永远等待下去(参数timeout设置为空指针):仅在有一个描述字准备好I/O时才返回。
(2)、等待固定时间(指定timeval中的秒数和微秒数):在不超过timeval结构体中所指定的秒数和微秒数内检测到有一个描述字准备好I/O时返回
(3)、根本不等待(timeval中秒数和微秒数均设置为0):检查描述字后立即返回。

select工作原理:
select就是巧妙的利用等待队列机制让用户进程适当在没有资源可读/写时睡眠,有资源可读/写时唤醒。下面我们看看select睡眠的详细过程。

select会循环遍历它所监测的fd_set(一组文件描述符(fd)的集合)内的所有文件描述符对应的驱动程序的poll函数。驱动程序提供的poll函数首先会将调用select的用户进程插入到该设备驱动对应资源的等待队列(如读/写等待队列),然后返回一个bitmask告诉select当前资源哪些可用。当select循环遍历完所有fd_set内指定的文件描述符对应的poll函数后,如果没有一个资源可用(即没有一个文件可供操作),则select让该进程睡眠,一直等到有资源可用为止,进程被唤醒(或者timeout)继续往下执行。

select调用过程:
这里写图片描述

头文件:下面poll、epoll的头文件与该文件相同

#include<unistd.h>
#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/select.h>
#include<poll.h>
#include<sys/epoll.h>
#include<sys/types.h>

#define IPADDR  "192.168.3.169"
#define PORT    8787
#define MAXLINE 1024
#define LISTENQ 5

//select
#define SIZE 10
//poll
#define OPEN_SIZE 10
//epoll
#define FDSIZE 100

服务器端:

#include"../unp.h"
#include<malloc.h>

typedef struct server_context_st   //服务器描述表
{
    int cli_cnt;                   //客户端连接个数
    int clifds[SIZE];              //描述字集合
    fd_set allfds;                 //设置所有的描述字
    int maxfd;                     //最大描述字个数
}server_context_st;

static server_context_st *s_srv_ctx = NULL;         

int server_init()                          //服务器初始化函数
{
    s_srv_ctx = (server_context_st*)malloc(sizeof(server_context_st));          //申请一个服务器描述表
    if(s_srv_ctx == NULL)
        return -1;
    memset(s_srv_ctx, 0, sizeof(server_context_st));                            //将该描述表清0
    for(int i=0; i<SIZE; ++i)                                                   //将该表中的每一位设为-1
    {
        s_srv_ctx->clifds[i] = -1;
    }
    return 0;
}
void server_uninit()                      //服务器去初始化函数
{
    if(s_srv_ctx)                         //如果服务器描述表不为0,即该表申请成功存在
    {
        free(s_srv_ctx);                  //释放该表的内存
        s_srv_ctx = NULL;                 //将指针值为NULL。
    }
}

int create_server_proc(const char *ip, short port)     //创建服务器进程
{
    int fd;                                          
    fd = socket(AF_INET, SOCK_STREAM, 0);              //建立一个套接字,记录返回的描述字,
    if(fd == -1)                                       //检测是否创建成功
    {
        perror("socket");
        return -1;
    }
    //初始化服务器信息结构体
    struct sockaddr_in addrSer;
    addrSer.sin_family = AF_INET;                         //指定用到的协议族
    addrSer.sin_port = htons(port);                       //指定服务器端口号
    addrSer.sin_addr.s_addr = inet_addr(ip);              //指定服务器ip地址

    socklen_t addrlen = sizeof(struct sockaddr);
    int res = bind(fd, (struct sockaddr*)&addrSer, addrlen);     //将创建的描述字与刚才所设置的服务器信息绑定
    if(res == -1)                                                //检测是否绑定成功嗯
    {
        perror("bind");
        return -1;
    }
    listen(fd, LISTENQ);       //监听是否有客户端请求连接,如果有则将该套接字设为可用
    return fd;
}

int accept_client_proc(int srvfd)             //结束客户端连接请求
{
    struct sockaddr_in addrCli;
    socklen_t addrlen = sizeof(struct sockaddr);

    int clifd;
ACCEPT:
    clifd = accept(srvfd, (struct sockaddr*)&addrCli, &addrlen);      //结束客户端的连接请求
    if(clifd == -1)                    //判断是否连接成功
    {
        goto ACCEPT;                   //如果没有连接成功,则跳转至ACCEPT处继续连接
    }
    printf("accept a new client: %s:%d\n",inet_ntoa(addrCli.sin_addr),addrCli.sin_port);

    int i;
    for(i=0; i<SIZE; ++i)                     //循环遍历描述字
    {
        if(s_srv_ctx->clifds[i] == -1)        //如果描述字为-1,表明只连接了i个客户端(0 —— i-1)
        {
            s_srv_ctx->clifds[i] = clifd;     //则将连接描述字赋给服务器描述表中第i个描述字
            s_srv_ctx->cli_cnt++;             //已连接的客户端数量加一
            break;
        }
    }
    if(i == SIZE)                             //如果i等于SIZE,说明描述字集合已满
    {
        printf("Server Over Load.\n");        
        return -1;
    }
}

void handle_client_msg(
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值