IO多路复用之select

IO多路复用之select

select函数原型

int select(int maxfdp,
           fd_set *readfds, fd_set *writefds, fd_set *errorfds,
           struct timeval *timeout);

在上述的参变量中,结构体fd_set可以理解为一个存放文件描述符(file descriptor)的集合,并且可以人为的去操作它:

fd_set set;
FD_ZERO(&set)      // 将set清零
FD_SET(fd, &set)   // 将fd加入set
FD_CLR(fd, &set)   // 将fd从set中清除
FD_ISSET(fd, &set) // 如果fd在set中则为真 

而结构体timeval是一个常用的结构体,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。

select各个参数的含义

maxfdp: 该参数代表一个整数值,是指集合中所有文件描述符的范围,表述的是所有文件描述符的最大值+1

readfds: 该参数是指向fd_set结构体的指针,这个参数是用来监视fd_set这个池子里所有文件描述符"可读"这一属性的。如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读。如果没有文件可读,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若有异常则返回负值。该参数可以传NULL表示不关心读事件。

writefds: 该参数和上述参数同理,负责监视所有写的描述符,若有可写的文件描述符,则返回大于0的数,传入NULL表示不关心写事件。

errorfds: 用来监视文件错误异常。

timeout:select的超时时间,这个参数至关重要,它可以使select处于3种状态:

① 若是传入NULL作为参数,select则会变成一个阻塞函数,它会一直等待,直到监听文件描述符集合中某个文件描述符发生变化为止。

② 若是传入参数为0,则select会变成一个完全的非阻塞函数,不管文件描述符是否有变化,都立刻返回,它就是看一眼有变化返回正值,无变化返回0。

③ 若是传入一个非零数,该非零数就是设置的超时时间,该函数会一直阻塞到超出该时间,超出该时间时,有文件描述符变化就返回大于0的数,没有就返回0,不会继续等待。相当于①和②的综合。

select返回值是准备就绪的描述符数,若超时则返回0,出错返回-1。

select 简单示例

以下代码定义了一个keyboard描述符从键盘中获取字符,以及一个readfdwhile循环中把该描述符加入到readfd中去,然后用select监听是否在timeout内有读事件发生,然后根据规则返回值到ret中,并进行打印输出。若是在timeout内没有读入事件,那么就超时退出。

#include<sys/stat.h>
#include<fcntl.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<errno.h>
#include<sys/select.h>

int main() {
    int keyboard, ret;
    char c;
    fd_set readfd;
    struct timeval timeout;
    keyboard = open("/dev/tty", O_RDONLY | O_NONBLOCK);
    assert(keyboard > 0);
    while (1) {
        timeout.tv_sec = 5, timeout.tv_usec = 0;
        FD_ZERO(&readfd);
        FD_SET(keyboard, &readfd);
        ret = select(keyboard + 1, &readfd, NULL, NULL, &timeout);
        if (ret == -1) {
            printf("select error!");
        } else if (ret) {
            if (FD_ISSET(keyboard, &readfd)) {
                read(keyboard, &c, 1);
                if (c == '\n') continue;
                printf("Input is %c\n", c);
                if (c == 'q') break;
            }
        } else if (ret == 0) {
            printf("time out \n");
            break;
        }
    }
    return 0;
}

服务端客户端举例

我已经把关键的地方写到了注释里面,程序是在linux上才能运行,最多可以对应五个客户端连接。

server端

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#define DEFAULT_PORT 6666

int main(int argc, char **argv) {
    int serverfd;
    struct sockaddr_in serverAddr;
   
    // 创建socket
    if ((serverfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        printf("Create socket error!\n");
        return -1;
    }
    printf("socket sucessfully!\n");

    // 绑定IP和端口号
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family      = AF_INET;
    serverAddr.sin_port        = htons(6666);
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    if (bind(serverfd, (struct sockaddr*)&serverAddr, sizeof(struct sockaddr)) == -1) {
        printf("Bind error!\n");
        return -2;
    }
    printf("bind sucessfully!\n");

    // 监听
    int num = 10;  // 这个变量这里先不用管
    if (listen(serverfd, num) == -1) {
        printf("listen error!\n");
        return -3;
    }
    printf("listen sucessfully!\n");


    // 初始化一些量
    fd_set clientfdSet;              // 监听文件描述符集合

    int clientSockFd[5];             // 把来连接的client的fd放到一个数组中, 这里设置了最多连接5个client
    memset(clientSockFd, 0, sizeof(clientSockFd));

    struct timeval timeout;          // 超时返回时间
    int clientNumber = 0;            // 用来记录描述符(连接)的数量
    int maxSockValue = serverfd;     // 描述符的最大值, 一开始只有serverfd, 所以最大的描述符为serverfd         
    char buffer[1024];               // 缓存数据用的buffer
    int ret = 0;

    while (1) {

        FD_ZERO(&clientfdSet);
        FD_SET(serverfd, &clientfdSet);
        timeout.tv_sec = 30;
        timeout.tv_usec = 0;

        // 把非零的fd加到文件描述符集合(池子)中, (我个人喜欢叫做池子)
        for (int i = 0; i < 5; i++) {
            if (clientSockFd[i] != 0) {
                FD_SET(clientSockFd[i], &clientfdSet);
            }
        }

        // select函数
        ret = select(maxSockValue + 1, &clientfdSet, NULL, NULL, &timeout);
        if (ret < 0) {
            printf("Select error!\n");
            break;
        } else if (ret == 0) {
            printf("Timeout!\n");
            continue;               // 这里只是说30s没有动静会提示超时, 但是没有break
        }

        // 若返回的是一个大于0的数, 就进行下面的操作, 说明有读事件发生, 但我们不知道具体是哪个连接需要读, 所以要进一步遍历
        // 这也是select和epoll的区别
        for (int i = 0; i < clientNumber; i++) {
            if (FD_ISSET(clientSockFd[i], &clientfdSet)) {
                printf("Start receive from client[%d]: \n", i);
                ret = recv(clientSockFd[i], buffer, 1024, 0);
                if (ret <= 0) {
                    printf("client[%d] close\n", i);
                    close(clientSockFd[i]);
                    FD_CLR(clientSockFd[i], &clientfdSet);
                    clientSockFd[i] = 0;
                } else {
                    printf("Receive from client[%d]: %s\n", i, buffer);
                }
            }
        }

        // 此时我们也要看serverfd是否发生了新连接, 若有新连接则把它加入到池子里
        if (FD_ISSET(serverfd, &clientfdSet)) {
            struct sockaddr_in client_addr;
            size_t size = sizeof(struct sockaddr_in);
            int clientSock = accept(serverfd, (struct sockaddr*)(&client_addr), (unsigned int*)(&size));
            if (clientSock < 0) {
                printf("Accept error\n");
                continue;
            }
            if (clientNumber < 5) {

                clientSockFd[clientNumber++] = clientSock;
                memset(buffer, 0, sizeof(1024));
                strcpy(buffer, "Welcome!\n");
                send(clientSock, buffer, 1024, 0);
                
                printf("New connection client[%d] %s: %d\n", clientNumber, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
                
                memset(buffer, 0, sizeof(1024));
                ret = recv(clientSock, buffer, 1024, 0);

                if (ret < 0) {
                    printf("Recvive error\n");
                    close(serverfd);
                    return -1;
                }
                printf("Receive: %s\n", buffer);

                if (clientSock > maxSockValue) {
                    maxSockValue = clientSock;
                } else {
                    printf("Max connection!\n");
                    break;
                }
            }
        }
    }
    for (int i = 0; i < 5; i++) {
        if (clientSockFd[i] != 0) {
            close(clientSockFd[i]);
        }
    }
    close(serverfd);
    return 0;
}

client端

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#include <string>
#include <iostream>
#define DEFAULT_PORT 6666
using namespace std;

int main(int argc, char **argv) {
    int connectFd = 0, cLen = 0;
    if(argc < 2) {
        printf("Use: clientent [serverIP address]\n");
        return -1;
    }

    struct sockaddr_in client;
    client.sin_family      = AF_INET;
    client.sin_port        = htons(DEFAULT_PORT);
    client.sin_addr.s_addr = inet_addr(argv[1]);
    connectFd = socket(AF_INET, SOCK_STREAM, 0);
    if (connectFd < 0) {
        printf("Socket error!\n");
        return -1;
    }
    if (connect(connectFd, (struct sockaddr*)&client, sizeof(client)) < 0) {
        printf("Connect error!\n");
        return -1;
    }
    char buffer[1024];
    memset(buffer, 0, sizeof(buffer));
    recv(connectFd, buffer, 1024, 0);
    printf("Recvive from server: %s\n", buffer);

    memset(buffer, 0, sizeof(buffer));
    strcpy(buffer, "This is client!\n");
    send(connectFd, buffer, 1024, 0);

    while (1) {
        string buffer;  // 为了获取带有空格的字符串
        getline(cin, buffer);
        // memset(buffer, 0, sizeof(buffer));
        // scanf("%s", buffer);
        send(connectFd, buffer.c_str(), 1024, 0);
        printf("Send buff successfully!\n");
    }
    close(connectFd);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值