select之多路IO复用

文章介绍了IO多路复用的概念,以解决网络服务器处理多个客户端连接时的并发问题。它通过select函数实现,允许一个线程管理多个I/O流,减少线程和内存开销。示例代码展示了如何使用select进行服务器和客户端的通信,包括服务器监听、客户端连接及数据交换的过程。
摘要由CSDN通过智能技术生成

什么是IO多路复用?

举一个简单地网络服务器的例子,如果你的服务器需要和多个客户端保持连接,处理客户端的请求,属于多进程的并发问题,如果创建很多个进程来处理这些IO流,会导致CPU占有率很高。所以人们提出了I/O多路复用模型:一个线程,通过记录I/O流的状态来同时管理多个I/O,需要的线程数大大减少,减少了内存开销和上下文切换的CPU开销。

对select的理解

  • select 可以实现多路IO复用,是需要的线程数大大减少,减少了内存的开销和上下文切换CPU的开销

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

  • select 只能得到有事件发生,但是不会得到具体事件对应的文件描述符

  • select 支持的文件描述符数量默认是1024。

int select(int nfds, fd_set *readfds, fd_set *writefds,
             fd_set *exceptfds, struct timeval *timeout);
             
nfds :   最大的文件描述符加1。
readfds: 用于检查可读的。
writefds:用于检查可写性。
exceptfds:用于检查异常的数据。一般情况下为NULL

timeout:一个指向timeval结构的指针,用于决定select等待I/o的最长时间。如果为空将一直等待。
timeval结构的定义:
struct timeval{
	long tv_sec; // seconds
	long tv_usec; // microseconds
}

返回值:  >0  是已就绪的文件句柄的总数, =0 超时, <0 表示出错,错误: errno 
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);  测试某个位是否被置位 

经典案例:

这里只是对select的一个简单的运用

服务器端 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);// 把文件描述符清0
    FD_SET(server_sockfd, &readfds); // 将服务器端socket加入到集合中



        /* 输出
        server waiting
        adding client on fd 4
        server waiting
        serving client on fd 4
        server waiting
        serving client on fd 4
        server waiting
        removing client on fd 4
        server waiting
        ^C 此时这里select又再阻塞等待事件的发生
        */

    while (1){
        char ch;
        int fd;
        int nread;
        testfds = readfds; // 将需要监视的描述符集copy到select查询队列中,select会对其修改,所以一定要分开使用变量
        printf("server waiting\n");


        // 无限期阻塞,并测试文件描述符变动
        // result 是发生事件的文件描述符
        // 参数:最大文件描述符+1 , 监听读事件,写事件,异常事件, FD_SETSIZE:系统默认的最大文件描述符
        result = select(FD_SETSIZE, &testfds, (fd_set *)0, (fd_set *)0, (struct timeval *)0); 
        if (result < 1){
            perror("server5");
            exit(1);
        }


        /*select和poll都只会告诉你,有事件发生,但是不会告诉你具体的文件描述符,所以需要扫描所有的文件描述符*/
        for (fd = 0; fd < FD_SETSIZE; fd++){
            // 判断是不是读套接字里面的
            if (FD_ISSET(fd, &testfds))
            {
                /*判断是否为服务器套接字,若是则表示为客户请求连接。*/
                if (fd == server_sockfd)
                {
                    client_len = sizeof(client_address);
                    client_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);
                }
                else
                {
                    // 客户端socket中有数据请求时
                    // 这里是fd 表示的是 client_sockfd
                    ioctl(fd, FIONREAD, &nread); // 取得数据量交给nread

                    /*客户数据请求完毕,关闭套接字,从集合中清除相应描述符 */
                    if (nread == 0)
                    {
                        // 客户端关闭也是一个读事件
                        close(fd);
                        FD_CLR(fd, &readfds); // 去掉关闭的fd
                        printf("removing client on fd %d\n", fd);
                    }
                    /*处理客户数据请求*/
                    else
                    {
                        read(fd, &ch, 1); // 读取字符
                        sleep(5);
                        printf("serving client on fd %d\n", fd);
                        ch++;
                        write(fd, &ch, 1);
                    }
                }
            }
        }
    }

    return 0;
}

客户端
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);
    result = connect(client_sockfd, (struct sockaddr*)&address, sizeof(address));


    if (result == -1){
        perror("oops: client2");
        exit(1);
    }

    /* 输出
    the first time: char from server = B
    the second time: char from server = C
    */

    //第一次读写
    write(client_sockfd, &ch, 1);// A
    read(client_sockfd, &ch, 1); // B
    printf("the first time: char from server = %c\n", ch);
    sleep(5);

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

    close(client_sockfd);

    return 0;
}

![在这里插入图片描述](https://img-blog.csdnimg.cn/161099b728954cf085ed32dd4f8be5ba.pn

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值