Linux网络编程---I/O复用模型之select

Linux网络编程—I/O复用模型之select

1. IO复用模型

  • IO复用能够预先告知内核,一旦发现进程指定的一个或者多个IO条件就绪,它就通知进程。
  • IO复用阻塞在select或poll系统调用上,而不是阻塞在真正的IO系统调用上。

这里写图片描述

2. 函数select

select函数能够告知内核对哪些描述符(不局限于套接字)感兴趣以及等待多长事件

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
//返回值:返回就绪描述符数目,若超时则为0, 若出错则为-1
  • timeout用来指定内核等待所指定描述符的任何一个就绪花多长事件。timeval结构用于指定这段事件的秒数和微妙数
struct timeval {
    long    tv_sec;         /* seconds */
    long    tv_usec;        /* microseconds */
};
//当timeout为NULL,则永远等待。
//当timeout为timeval,等待固定时间。
//当timeout为timeval,但timeval时间设置为0,则检查描述符字立即返回,称为轮询。
  • 中间的三个参数readfds、writefds、exceptfds指定内核测试读、写、异常条件的描述符,这三个参数都是fd_set结构的指针类型,fd_set结构实现如下:
#define __FD_SETSIZE    1024

typedef struct {
        unsigned long fds_bits[__FD_SETSIZE / (8 * sizeof(long))];
} __kernel_fd_set;

select函数使用描述符集,通常是一个整数数组,其中每一个整数中的每一位对应一个描述符。如何操作这些描述符则系统提供了四个宏

void FD_CLR(int fd, fd_set *set);
//把文件描述符集合里fd清零
int  FD_ISSET(int fd, fd_set *set);
//测试文件描述符集合里fd是否置1
void FD_SET(int fd, fd_set *set);
//把文件描述符集合里fd位置1
void FD_ZERO(fd_set *set);
//把文件描述符集合里所有位清0
  • 如果对应哪一个条件不感兴趣,则可以将它设置为空指针。
  • nfds参数指定待测试的描述符个数,它的值是待测试的最大描述符加1,描述符0到nfds-1都会被测试。
  • select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数。解决1024以下客户端时使用select是很合适的,但如果链接客户端过多,select采用的是轮询模型,会大大降低服务器响应效率。
  • select函数修改由指针readfds、writefds、exceptfds指向的描述符集,调用函数时,我们指定所关心的描述符的值,函数返回时,结果将指示哪些描述符已就绪,函数返回后,使用FD_ISSET宏来测试fd_set数据类型中的描述符,描述符集内任何与未就绪描述符对应的位均清成0.为此,每次重新调用select函数时都要将描述符集内所关心的位置为1。

3. select模型实现

3.1 服务器端

#include "wrap.h"

#define MAXLINE         1024

int start_ser(char *ipaddr, char *port)
{
        int sock = Socket(AF_INET, SOCK_STREAM, 0);

        struct sockaddr_in serveraddr;
        bzero(&serveraddr, sizeof(serveraddr));
        serveraddr.sin_family = AF_INET;
        serveraddr.sin_port = htons(atoi(port));
        inet_pton(AF_INET, ipaddr, &serveraddr.sin_addr);

        Bind(sock, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

        Listen(sock, 128);

        return sock;
}

int main(int argc, char *argv[])
{

        int i, maxi, maxfd, listenfd, connfd, sockfd;
        int nready, client[FD_SETSIZE];
        ssize_t n;
        fd_set readset, allset;
        char buf[MAXLINE];
        socklen_t clilen;
        struct sockaddr_in clientaddr;

        listenfd = start_ser(argv[1], argv[2]); //监听文件描述符

        maxfd = listenfd;//最大的文件描述符
        maxi = -1;//数组中最大文件描述符下标
        for(i = 0; i < FD_SETSIZE; i++){
                client[i] = -1;
        }
        FD_ZERO(&allset);//初始化allset集合
        FD_SET(listenfd, &allset);//添加监听文件描述符到集合allset中

        while(1){
                readset = allset;//每次select都要初始化集合,结构体可以直接赋值
                nready = select(maxfd+1, &readset, NULL, NULL, NULL);//组摄等待连接或请求

                if(FD_ISSET(listenfd, &readset)){//当监听文件描述符相应有新的客户端连接时
                        clilen = sizeof(clientaddr);
                        connfd = Accept(listenfd, (struct sockaddr *)&clientaddr, &clilen);//接收该客户端
                        if(connfd < 0){
                                perr_exit("accept err");
                                break;
                        }

                        for(i = 0; i < FD_SETSIZE; i++){
                                if(client[i] < 0){
                                        client[i] = connfd;     //保存文件描述符到数组
                                        break;
                                }
                        }
                        if(i == FD_SETSIZE){            //连接个数不能大于内核规定的FD_SETSIZE
                                perr_exit("too many clients");
                        }
                        FD_SET(connfd, &allset);//添加新描述符到allset集合中
                        if(connfd > maxfd){
                                maxfd = connfd; //更新最大文件描述符
                        }
                        if(i > maxi){
                                maxi = i;       //更新最大文件描述符下标
                        }
                        if(--nready == 0){      //只有一个listenfd响应则直接跳过下面的语句
                                continue;
                        }
                }

                for(i = 0; i <= maxi; i++){     //处理已连接客户端的请求
                        if((sockfd = client[i]) < 0){
                                continue;
                        }
                        if(FD_ISSET(sockfd, &readset)){ //sockfd是否在readset集合中
                                memset(buf, '\0', MAXLINE);
                                if((n = Read(sockfd, buf, MAXLINE-1)) == 0){
                                        Close(sockfd);
                                        FD_CLR(sockfd, &allset);
                                        client[i] = -1;
                                }else{
                                        printf("client:%s\n", buf);
                                }
                                if(--nready == 0){//判断是否查找完
                                        break;
                                }
                        }
                }
        }

        return 0;
}

3.2 客户端

#include "wrap.h"

int main(int argc, char *argv[])
{
        int connfd;
        struct sockaddr_in serveraddr;
        char buf[1024];

        connfd = Socket(AF_INET, SOCK_STREAM, 0);

        bzero(&serveraddr, sizeof(serveraddr));
        serveraddr.sin_family = AF_INET;
        serveraddr.sin_port = htons(atoi(argv[2]));
        inet_pton(AF_INET, argv[1], &serveraddr.sin_addr);

        Connect(connfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

        while(fgets(buf, 1024, stdin) != NULL){
                Write(connfd, buf, strlen(buf));
        }

        Close(connfd);
        return 0;
}

3.3运行结果

这里写图片描述

这里写图片描述

这里写图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值