IO多路复用之SELECT(内附可运行代码)

SELECT

基本知识

在一段指定的时间内,监听用户感兴趣的文件描述符上可读、可写和异常等事件。
头文件

#include <sys/select.h>

相关函数
1、

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

nfds :最大的文件描述符加1。
readfds:用于检查可读的。
writefds:用于检查可写性。
exceptfds:用于检查异常的数据。
timeout:一个指向timeval结构的指针,用于决定select等待I/o的最长时间。如果为空将一直等待。
timeval结构的定义:
struct timeval{
	long tv_sec; // seconds
	long tv_usec; // microseconds
}
返回值:  >0  是已就绪的文件句柄的总数, =0 超时, <0 表示出错,错误: errno 

2、

int FD_ZERO(fd_set *fdset);          //一个 fd_set类型变量的所有位都设为 0 
int FD_CLR(int fd, fd_set *fdset);   //清除某个位时可以使用 
int FD_SET(int fd, fd_set *fd_set);  //设置变量的某个位置位 
int FD_ISSET(int fd, fd_set *fdset); //测试某个位是否被置位 

例子分析

本例子是客户端和服务器端进行交换信息(如果客户端发送字符‘A’,那么服务器端将回复一个‘B’),我将以此例子来说明SELECT的用法(本例子可以直接在linux下运行,有兴趣的同学可以自己玩玩)
先贴代码

代码以及运行结果

client.c

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/time.h>

int main()
{
    int client_sockfd;
    int len;
    struct sockaddr_in address;//服务器端网络地址结构体 
    int result;
    char ch = 'A';
    client_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立客户端socket 
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = inet_addr("127.0.0.1");
    address.sin_port = htons(9000);
    len = sizeof(address);
    result = connect(client_sockfd, (struct sockaddr*)&address, len);
    printf("connect server!!\n");
    if (result == -1)
    {
        perror("oops: client2");
        exit(1);
    }
    //第一次读写
    write(client_sockfd, &ch, 1);
    printf("first write %c\n",ch);
    read(client_sockfd, &ch, 1);
    printf("the first time: char from server = %c\n", ch);
    sleep(5);

    //第二次读写
    write(client_sockfd, &ch, 1);
    read(client_sockfd, &ch, 1);
    printf("the second time: char from server = %c\n", ch);

    close(client_sockfd);

    return 0;
}

server.c

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    int server_sockfd, client_sockfd;
    int server_len, client_len;
    struct sockaddr_in server_address;
    struct sockaddr_in client_address;
    int result;
    fd_set readfds, testfds;
    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立服务器端socket 
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = htonl(INADDR_ANY);
    server_address.sin_port = htons(9000);
    server_len = sizeof(server_address);
    bind(server_sockfd, (struct sockaddr*)&server_address, server_len);
    listen(server_sockfd, 5); //监听队列最多容纳5个 
    FD_ZERO(&readfds);
    FD_SET(server_sockfd, &readfds);//将服务器端socket加入到集合中
    while (1)
    {
    	char ch;
        int fd;
        int nread;
        testfds = readfds;//将需要监视的描述符集copy到select查询队列中,select会
对其修改,所以一定要分开使用变量 
        printf("server waiting\n");

        /*无限期阻塞,并测试文件描述符变动 select就是用于监视*/
        result = select(FD_SETSIZE, &testfds, (fd_set*)0, (fd_set*)0, (struct timeval*)0); //FD_SETSIZE:系统默认的最大文件描述符
        if (result < 1)
        {
            perror("server5");
            exit(1);
        }

        /*扫描所有的文件描述符*/
        for (fd = 0; fd < FD_SETSIZE; fd++)
        {
            /*找到相关文件描述符 是否是在testfds中的fd*/
            if (FD_ISSET(fd, &testfds))
            {
                /*判断是否为服务器套接字,是则表示为客户请求连接。*/
                if (fd == server_sockfd)
                {
                    printf("receive client connect!!!\n");
                    client_len = sizeof(client_address);
                    //记录客户端的fdclient_sockfd = accept(server_sockfd,
                    (struct sockaddr*)&client_address, &client_len);
                    FD_SET(client_sockfd, &readfds);//将客户端socket加入到集合中
                    printf("adding client on fd %d\n", client_sockfd);
                }
                /*客户端socket中有数据请求时*/
                else
                {
                    ioctl(fd, FIONREAD, &nread);//取得数据量交给nread

                    /*客户数据请求完毕,关闭套接字,从集合中清除相应描述符 如果>数据量为0表示已经请求完毕*/
                    if (nread == 0)
                    {
                        close(fd);//关闭套接字
                        FD_CLR(fd, &readfds); //去掉关闭的fd
                        printf("removing client on fd %d\n", fd);
                    }
                    /*处理客户数据请求*/
                    else
                    {
                        printf("deal with client require!!\n");
                        read(fd, &ch, 1);
                        sleep(5);
                        printf("serving client on fd %d\n", fd);
                        ch++;
                        write(fd, &ch, 1);
                    }
                }
            }
        }
    }

    return 0;
}

运行结果
client:
在这里插入图片描述
server:
在这里插入图片描述

例子刨析

客户端的主要操作就是:

  • connect(client_sockfd, (struct sockaddr*)&address, len);//向服务器端发送连接请求
    
  • //第一次写读,第二次读写
    write(client_sockfd, &ch, 1);
    read(client_sockfd, &ch, 1);
    
  • //关闭连接
    close(client_sockfd);
    

    当执行connect时会触发fd为server_sockfd的读事件,这样就触发如下服务器端代码

    		    /*判断是否为服务器套接字,是则表示为客户请求连接。*/
                if (fd == server_sockfd)
                {
                    printf("receive client connect!!!\n");
                    client_len = sizeof(client_address);
                    //记录客户端的fdclient_sockfd = accept(server_sockfd,
                    (struct sockaddr*)&client_address, &client_len);
                    FD_SET(client_sockfd, &readfds);//将客户端socket加入到集合中
                    printf("adding client on fd %d\n", client_sockfd);
                }
    

    当执行第一次write语句时,会触发服务器端如下代码(这是因为在connect语句以后,服务器端就将client_sockfd加入到读事件进行监听,而write语句会触发client_sockfd的读事件,这样服务器端就将字符进行+“1”回写回去让客户端的read进行读取),第二次写读效果和第一次写读效果一样

       		   else{
                       printf("deal with client require!!\n");
                       read(fd, &ch, 1);
                       sleep(5);
                       printf("serving client on fd %d\n", fd);
                       ch++;
                       write(fd, &ch, 1);
                   }
    

    当客户端断开连接时,触发服务器端如下代码,触发client_sockfd的读事件,并且因为数据量为0,发觉是关闭连接,所以将client_sockfd关闭并将client_sockfd从读事件中去除。

    			else{
                       ioctl(fd, FIONREAD, &nread);//取得数据量交给nread
    
                       /*客户数据请求完毕,关闭套接字,从集合中清除相应描述符 如果>数据量为0表示已经请求完毕*/
                       if (nread == 0)
                       {
                           close(fd);//关闭套接字
                           FD_CLR(fd, &readfds); //去掉关闭的fd
                           printf("removing client on fd %d\n", fd);
                       }
                   }
    

希望这篇文章能够帮助到您,如果有勘误也请在评论区中指出,谢谢啦!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值