TCP/IP网络编程笔记Chapter I -11 I/O复用 & 实现I/O复用服务器端


我们接下来延伸并发服务器的实现。首先我们需要知道多进程服务器端的缺点,那就是创建进程时需要付出很大的代价,需要大量的运算和内存空间,由于每个进程都具有独立的内存空间,所以相互间的数据交换也需要使用管道这样相对复杂的方法。
那么能否不创建进程也能同时向多个客户端提供服务?当然是可以的!I/O复用技术就是一种。

1.I/O复用技术

I/O复用技术能使程序同时监听多个文件描述符,这对提高程序性能至关重要。Linux下实现I/O复用的系统调用主要有select、poll、epoll。本节的内容是select。
对I/O复用最直观的理解是小时候使用的纸杯电话。
在这里插入图片描述
在这里插入图片描述
它的优点是减少了连线长度与纸杯个数。多进程服务器端模型与I/O复用服务器端模型的对比如下图所示,
在这里插入图片描述
在这里插入图片描述
可以看出复用技术可以减少进程数,提供服务的进程只有一个。

2.select函数

select函数是最具有代表性的实现复用技术服务器端的方法

(1)select函数的功能及调用顺序

使用select函数时可以将多个文件描述符集中到一起统一监视,监视的项目如下:

  • 是否存在套接字接收数据?
  • 无需阻塞传输数据的套接字有哪些?
  • 哪些套接字发生了异常?

我们把监视项称为事件,发生监视项对应的情况时,称“事件发生”。下图介绍select函数的调用方法和顺序,在这里插入图片描述
select函数使用和普通函数区别较大,需要一些准备工作,而且在还需要查看调用结果,接下来我们按照步骤逐一讲解。

(2)设置文件描述符

select函数可以同时监视多个文件描述符。当然,监视文件描述符可以视为监视套接字。首先需要将要监视的文件描述符集中到一起,集中时也要按照监视项(接收、传输、异常)进行区分,即按照上述3种监视项分成3类。
使用fd_set位数组执行此操作,最左端的位表示文件描述符0的位置,该位为1表示该文件描述符是监视对象。
在这里插入图片描述
使用如下的宏来对fd_set进行注册和更改

  • FD_ZERO(fd_set *fdset):将fd_set变量的所有位都初始化为0
  • FD_SET(int fd, fd_set *fdset):在参数fdset指向的变量中注册文件描述符fd的信息
  • FD_CLR(int fd, fd_set *fdset):从参数fdset指向的变量中清除文件描述符fd的信息
  • FD_ISSET(int fd, fd_set *fdset):若参数fdset指向的变量中包含文件描述符fd的信息,则返回“真”

eg
在这里插入图片描述

(3)设置监视范围及超时

我们先介绍下select函数

#include<sys/select.h>
#include<sys/time.h>
int select(int maxfd,fd_set * readset,fd_set * writeset,fd_set * exceptset,const struct timeval *timeout);
  • 成功返回大于0的值(该值是发生事件的文件描述符数),失败返回-1
  • maxfd:监视对象的文件描述符
  • readset:将所有关注“是否存在待读取数据”的文件描述符注册到fd_set型变量,并传递其地址值
  • writeset:将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set型变量,并传递其地址值
  • exceptset:将所有关注“是否发生异常”的文件描述符注册到fd_set型变量,并传递其地址值
  • timeout:调用select函数后,为防止陷入无限阻塞的状态,传递超时信息

select函数用来验证三种监视项的变化情况,根据监视项声明三个fd_set型变量,分别向其注册文件描述符信息,并把变量的地址值传递到上述函数的第二到第四个参数。但在调用select函数之前,需要决定下面两件事:

  • 文件描述符的监视范围
  • 如何设定select函数的超时时间

文件描述符的监视范围与select函数的第一个参数有关。实际上,select函数要求通过第一个参数传递监视对象文件描述符的数量。因此,需要得到注册在fd_set变量中的文件描述符数。但每次新建文件描述符时,其值都会加一,故只需将最大的文件描述符值加一再传递到select函数即可,加一是因为文件描述符的值从0开始。

select函数的超时时间与select函数的最后一个参数相关,其中timeval结构体定义如下。

struct timeval
{
   
    long tv_sec;     /* seconds */
    long tv_usec;    /* microseconds */
};

select函数在监视的文件描述符发生变化时才返回,如果未发生变化,就会进入阻塞状态,指定超时时间就是为了防止这种情况发生。通过声明上述结构体变量,将秒数填入tv_sec成员,将毫秒数填入tv_usec成员,然后将结构体的地址值传递到select函数的最后一个参数。超时select函数返回0,不设置超时,可以传递NULL。

(4)调用select函数后查看结果

在这里插入图片描述
select函数调用完成后,向其传递的fd_set变量中将发生变化。原来为1的所有位均变为0,但发生变化的文件描述符对应位除外。因此,可以认为值仍然为1的位置上的文件描述符发生了变化
select函数使用示例伪代码如下


    fd_set reads, temps;
    int result, str_len;
    struct timeval timeout;
    //①设置文件描述符
 	FD_ZERO(&reads);//初始化fd_set变量都为0 
 	//②设置监视范围
    FD_SET(0, &reads);  //监视文件描述符0位置  
    while (1)
    
    {
   	temps = reads;//每次调用select剩余位会被初始化为零,所以需要保存初始值 
        //③设置超时
        timeout.tv_sec = 5;
        timeout.tv_usec = 0;
        //④调用select函数
        result = select(1, &temps, 0, 0, &timeout);
        //⑤查看结果
        if (result == 0)//超时返回0 
        {
   
            //超时
        }
        else if(FD_ISSET(0, &temps)//验证变化的文件描述符是否是标准输入 
        {
   
            //有输入则输出
        }
    }


完整示例如下

#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/select.h>
#define BUF_SIZE 30
 
int main(int argc, char *argv[])
{
   
    fd_set reads, temps;
    int result, str_len
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值