TCP IP网络编程:第12章 I/O复用(select的简单使用)

上一章介绍了使用多进程构建并发服务器的方法。该方法虽然优秀,但并非适用于所有情况,因为创建大量的新进程需要大量的

运算和内存空间。而且由于每个进程具有独立的内存空间。所以相互之间的数据交换也较为复杂。

本章将采用I/O复用技术,在不创建进程的同时向多个客户端提供服务。

12. 理解select函数并实现服务器端

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

12.1 select函数的功能和调用顺序

使用select函数时可以将多个文件描述符集中到一起统一监视,可以监视的内容包括:

  • 套接字接收数据事件
  • 无需阻塞传输数据的套接字有哪些
  • 套接字异常事件

12.2 select函数的使用方法

下图展示了select函数的调用方法和顺序

步骤一:设置文件描述符

在fd_set数组中注册要监视的文件描述符

有四个宏可以帮助我们完成这项操作

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

上述函数中国FD_ISSET用于验证select函数的调用结果。下图以图示方式简洁易懂地解释了这些函数的功能

注意:

过去,描述符集被作为一个整数位屏蔽码得到实现,但是这种实现对于多于32个的文件描述符将无法工作。描述符集现在通常用整数数组中的位域表示,数组元素的每一位对应一个文件描述符。例如,一个整数占32位,那么整数数组的第一个元素代表文件描述符0到31,数组的第二个元素代表文件描述符32到63,以此类推。

 

步骤二:调用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   // 防止select进入无线阻塞状态设置的超时事件
);

// 超时返回0,失败返回-1,有事件发生返回大于0的值,该值是发生事件的文件描述符的数量

使用select函数,需要根据监视项声明3个fd_set型变量,分别向其注册文件描述符信息,

并把变量的地址值传递到select函数的对应参数中。

除此之外还要确定监视的范围超时时间

由于文件描述符的值从0开始,所以只需要得到注册在fd_set变量中的文件描述符的值+1即为监视的范围。

超时时间则通过timeval结构体设置

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

如果不想设置超时,则想select函数传入NULL参数

 

步骤三:查看调用结果

select函数调用完成后,向其传递的fd_set变量中原来为1的没有发生变化的文件描述符对应位变为0,

而发生变化的文件描述符对应位仍为1。因此,我们通过FD_ISSET()函数检查该位是否为1,即可知道哪个文件描述符变化了。

下面是一个简单的使用select监视标准输入的例子

// select.c
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/select.h>

#define BUF_SIZE 100
void err_sys(char* buf);

int main(int argc, char* argv[])
{
    fd_set reads, temps;
    int result, str_len;
    char buf[BUF_SIZE];
    struct timeval timeout;

    FD_ZERO(&reads);
    FD_SET(0, &reads);  // 0是标准输入的文件描述符的值

    while(1)
    {
        temps = reads;
        timeout.tv_sec = 5;
        timeout.tv_usec = 0;
        result = select(0+1, &temps, 0, 0, &timeout);
        if(result == -1)
        {
            puts("select() error!");
            break;
        }
        else if(result == 0)
        {
            puts("Time-out!");
        }
        else
        {
            if(FD_ISSET(0, &temps))
            {
                str_len = read(0, buf, BUF_SIZE);
                buf[str_len] = 0;
                printf("message from console: %s", buf);
            }
        }
    }

    return 0;
}

 

下面是select I/O复用服务器端的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/select.h>

#define BUF_SIZE 100
void err_sys(char* buf);

int main(int argc, char* argv[])
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    struct timeval timeout;
    fd_set reads, cpy_reads;

    socklen_t adr_sz;
    int fd_max, str_len, fd_num, i;
    char buf[BUF_SIZE];
    if(argc != 2)
    {
        printf("Usage : %s <port> \n", argv[0]);
        exit(1);
    }

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr)) == -1)
    {
        err_sys("bind() error");
    }
    if(listen(serv_sock, 5) == -1)
    {
        err_sys("listen() error");
    }

    FD_ZERO(&reads);
    FD_SET(serv_sock, &reads);
    fd_max = serv_sock;

    while(1)
    {
        cpy_reads = reads;
        timeout.tv_sec = 5;
        timeout.tv_usec = 5000;

        if((fd_num = select(fd_max+1, &cpy_reads, 0, 0, &timeout)) == -1)
            break;
        if(fd_num == 0)
            continue;

        for(i=0; i<fd_max+1; ++i)
        {
            if(FD_ISSET(i, &cpy_reads))
            {
                if(i == serv_sock)      //connect request!
                {
                    adr_sz = sizeof(clnt_adr);
                    clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
                    FD_SET(clnt_sock, &reads);
                    if(fd_max < clnt_sock)
                        fd_max = clnt_sock;
                    printf("connected client: %d \n", clnt_sock);
                }
                else        // read message!
                {
                    str_len = read(i, buf, BUF_SIZE);
                    if(str_len == 0)        // close request!
                    {
                        FD_CLR(i, &reads);
                        close(i);
                        printf("closed client: %d \n", i);
                    }
                    else
                    {
                        write(i, buf, str_len); // echo!
                    }
                    
                }
                
            }
        }
    }
    close(serv_sock);

    return 0;
}

void err_sys(char *buf)
{
    fputs(buf, stderr);
    fputc('\n', stderr);
    exit(1);
}


 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值