IO多路复用-select系统调用

一、IO多路复用

  IO多路复用允许进程同时检查多个文件描述符,检查其中任意一个是否可以执行IO操作。有两个功能几乎相同的系统调用来执行IO多路复用,一是select,二是poll,历史上select的使用更为广泛。这两个系统调用可以检查普通文件、终端、伪终端、管道、FIFO、套接字等,允许进程要么一直等待文件描述符成为就绪态,要么指定一个超时时间。这里简单记录下select的使用

二、select调用
#include <sys/time.h>
#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
//成功返回就绪的文件描述符数, 返回0表示超时,-1错误

参数readfds, writefds, exceptfds都是指向文件描述符的集合,按如下方式使用:

  • readfds是用来检测输入是否就绪的文件描述符集合
  • writefds是用来检测输出是否就绪的文件描述符集合
  • exceptsfds是用来检测异常情况是否发生的文件描述集合

通常,fd_set数据类型使用四个宏来操作:FD_ZERO, FD_SET, FD_CLR, FD_ISSET

#include <sys/select.h>

//将fdset指向的集合初始化为空
void FD_ZERO(fd_set *fdset);

//将文件描述符fd添加到集合fdset中
void FD_SET(int fd, fd_set *fdset);

//将文件描述符fd从集合fdset中删除
void FD_CLR(int fd, fd_set *fdset);

//判断文件描述符fd是否在集合fdset中
int FD_ISSET(int fd, fd_set *fdset);
注意事项:
  1. 文件描述符集合有一个最大容量限制,由常量FD_SETSIZE决定,在linux上值为1024,select实现可以处理更大的文件描述符集合,但是没有简单方法可以修改FD_SETSIZE,必须修改glibc头文件中的定义。并且由于select的性能延展性不佳,超过1024个文件描述符时,应考虑使用epoll接口。
  2. 参数readfds, writefds, exceptfds所指向的结构体都是保存结果值的地方。在调用select前,必须使用FD_ZERO和FD_SET初始化,以包含感兴趣的文件描述符。select会修改这些集合,返回时,它们包含的就是已处于就绪态的文件描述符了。
  3. 由于readfds, writefds, exceptfds所指向的结构体会被select修改,所以在循环中重复调用select时,必须保证每次都要重新初始化
  4. 如果对某一类型事件不感兴趣,可将对应文件描述符集合置为NULL
  5. 参数nfds需设为比3个文件描述符集合中包含的最大的文件描述符号还要大1,这样select不会去检查大于nfds的文件描述符,使之更有效率。

关于select的返回值:

  • 返回-1时表示有错误发生,可能的错误包括EBADF和EINTR,EBADF表示参数中有非法文件描述符(例如文件描述符并没有打开)。EINTR表示被信号处理器中断
  • 返回0表示在还没有文件描述符成为就绪态前就超时了
  • 返回一个正整数表示有一个或多个文件描述符成为就绪态。如果一个文件描述符在三个参数中都指定了,可能会被统计多次。
三、示例程序

示例程序包含两部分代码,一个是服务器,一个是客户端。使用TCP协议。服务端使用select实现并发通信。
server

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

using namespace std;

#define PORT        60000
#define BUF_SIZE    5

int main()
{
    int listenfd = 0;
    int maxfd = 0;
    set<int> fd_arr;
    struct sockaddr_in sockaddr;

    memset(&sockaddr, 0, sizeof(sockaddr));

    sockaddr.sin_family = AF_INET;
    sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    sockaddr.sin_port = htons(PORT);

    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if(listenfd < 0)
    {
        printf("Create socket failed.\n");
        return -1;
    }

    if(bind(listenfd, (struct sockaddr *) &sockaddr, sizeof(sockaddr)) < 0)
    {
        printf("bind failed.\n");
        return -1;
    }

    if(listen(listenfd, 5) < 0)
    {
        printf("listen failed.\n");
        return -1;
    }

    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(listenfd, &fds);
    maxfd = listenfd;
    fd_arr.insert(listenfd);

    for(;;)
    {
        fd_set readfds = fds;
        int ret = select(maxfd + 1, &readfds, NULL, NULL, NULL);

        if(ret < 0)
        {
            printf("select failed.\n");
        }
        else if(ret == 0)
        {
            printf("select timeout.\n");
        }
        else
        {
            //listenfd就绪,说明有新客户端连接
            if(FD_ISSET(listenfd, &readfds))
            {
                int connfd = accept(listenfd, NULL, 0);
                if(connfd < 0)
                {
                    printf("accept failed.\n");
                    continue;
                }
                else
                {
                    printf("New incoming connection, fd = %d\n", connfd);
                    FD_SET(connfd, &fds);
                    if(connfd > maxfd)
                    {
                        maxfd = connfd;
                    }
                    fd_arr.insert(connfd);
                }
            }
            else
            {
                maxfd = 0;
                for(int fd : fd_arr)
                {
                    //除listenfd外的其它fd就绪,说明可以进行IO操作
                    if(FD_ISSET(fd, &readfds))
                    {
                        //READ
                        char buf[BUF_SIZE + 1];
                        int num = recv(fd, buf, BUF_SIZE, 0);
                        //读取的字节数小于等于0,说明客户端断开了
                        if(num <= 0)
                        {
                            printf("client offline.\n");
                            FD_CLR(fd, &fds);
                            fd_arr.erase(fd);
                            close(fd);
                            continue;
                        }
                        //这里设置的BUF_SIZE较小,若一次不能读取完,下次select会直接返回,因为还有未读数据,该fd仍为就绪态
                        buf[num] = '\0';
                        printf("read from %d, content = %s\n", fd, buf);
                    }
                    if(fd > maxfd)
                    {
                        maxfd = fd;
                    }
                }
            }
        }
    }
    close(listenfd);
    return 0;
}

client

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string>
#include <iostream>
#include <sstream>
#define MAXLINE 10
int main(int argc,char **argv)
{
    char *servInetAddr = "127.0.0.1";
    int socketfd;
    struct sockaddr_in sockaddr;
    char recvline[MAXLINE], sendline[MAXLINE];
    int n;

    socketfd = socket(AF_INET,SOCK_STREAM,0);
    memset(&sockaddr,0,sizeof(sockaddr));
    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = htons(60000);
    inet_pton(AF_INET,servInetAddr,&sockaddr.sin_addr);
    if((connect(socketfd,(struct sockaddr*)&sockaddr,sizeof(sockaddr))) < 0 )
    {
        printf("connect error %s errno: %d\n",strerror(errno),errno);
        exit(0);
    }
    for(;;)
    {
        printf("send message to server\n");

        std::string msg;
        std::cin>>msg;

        if(msg == "exit")
        {
            printf("Got exit, close client socket.\n");
            break;
        }
        else{
            if((send(socketfd,msg.c_str(),msg.size(),0)) < 0)
            {
                printf("send mes error: %s errno : %d",strerror(errno),errno);
                exit(0);
            }
        }

    }

    close(socketfd);
    printf("exit\n");
    exit(0);
}

结果:
这里写图片描述
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值