Linux网络编程——select函数

select函数

select函数用于检测一组socket中是否有时间就绪,主要包含以下三类事件:

  1. 读事件就绪
  2. 写事件就绪
  3. 异常事件就绪
typedef struct{
	long int __fds_bits[16];//可以看做128bit的数组
}fd_set;

void FD_SET(int fd, fd_set *set);将一个fd添加到fd_set集合中,决定这个fd在__fds_bits数组的位置的实现使用的是位图法。
FD_SET宏在本质上是在一个有1024个连续bit(共计64字节)的内存的某个bit上设置一个标志。
如果要在fd_set中删除一个fd,即将对应的bit置0,则可以使用FD_CLR:void FD_CLR(int fd, fd_set *set);
如果要将fd_set中所有的fd都清掉,即将所有的bit都置0,则可以使用:void FD_ZERO(fd_set *set);
当select函数返回时,可以使用int FD_ISSET(int fd, fd_set *set);来判断在某个fd中是否有我们关心的事件,本质上就是检测对应的bit是否置位。

使用select函数的服务器代码示例

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <vector>

#define INVALID_FD -1

int main(int argc, const char * argv[]) {
    // insert code here...
    //std::cout << "Hello, World!\n";
    //创建一个监听socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if(listenfd == INVALID_FD){
        std::cout<<"create listen socket error"<<std::endl;
        return -1;
    }
    //初始化服务器地址
    struct sockaddr_in bindaddr;
    bindaddr.sin_family = AF_INET;
    bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    bindaddr.sin_port = htons(3000);
    if(bind(listenfd, (struct sockaddr*) &bindaddr, sizeof(bindaddr)) == -1){
        std::cout<<"bind listen socket error"<<std::endl;
        return -1;
    }
    //启动监听
    if(listen(listenfd, SOMAXCONN) == -1){
        std::cout<<"listen error"<<std::endl;
        close(listenfd);
        return -1;
    }
    //记录所有客户端的连接
    std::vector<int> clientfds;
    int maxfd;
    while(true){
        fd_set readset;
        FD_ZERO(&readset);
        //将监听socket加入到可检测的可读事件中
        FD_SET(listenfd, &readset);
        
        maxfd = listenfd;
        //将客户端fd加入待检测的可读事件中
        int clientfdslength = clientfds.size();
        std::cout<<"the size of clientfds:"<<clientfdslength<<std::endl;
        for(int i = 0; i < clientfdslength; i++){
            if(clientfds[i] != INVALID_FD){
                FD_SET(clientfds[i], &readset);
                
                if(maxfd < clientfds[i])
                    maxfd = clientfds[i];
            }
        }
        
        timeval tm;
        tm.tv_sec = 1;
        tm.tv_usec = 0;
        
        //暂且只检测可读事件,不检测可写事件和异常事件
        int ret = select(maxfd + 1, &readset, NULL, NULL, &tm);
        if(ret == -1){
            if(errno != EINTR)
                break;
        }else if(ret == 0){
            continue;//select超时,下次继续
        }else{
            //监测到某个socket事件
            if(FD_ISSET(listenfd, &readset)){
                //监听socket的可读事件,表明有新的连接到来
                struct sockaddr_in clientaddr;
                socklen_t clientaddrlen = sizeof(clientaddr);
                //接收客户端连接
                int clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientaddrlen);
                if(clientfd == INVALID_FD){
                    //接收连接出错
                    break;
                }
                //只接收连接,不调取recv收取任何数据
                std::cout<<"accept a client connection, fd :"<<clientfd<<std::endl;
                clientfds.push_back(clientfd);
            }else{
                //假设对端发来的数据长度不超过63个字符
                char recvbuf[64];
                int clientfdslength = clientfds.size();
                for(int i = 0; i < clientfdslength; ++i){
                    if(clientfds[i] != INVALID_FD && FD_ISSET(clientfds[i], &readset)){
                        memset(recvbuf, 0, sizeof(recvbuf));
                        //非监听socket,接收数据
                        int length = recv(clientfds[i], recvbuf, 64, 0);
                        if(length <= 0){
                            //收取数据出错
                            std::cout<<"recv data error, clientfd:"<<clientfds[i]<<std::endl;
                            close(clientfds[i]);
                            //不直接删除该元素,将该位置元素标记为INVALID_FD
                            clientfds[i] = INVALID_FD;
                            continue;
                        }
                        std::cout<<"clientfd:"<<clientfds[i]<<", recv data:"<<recvbuf<<std::endl;
                    }
                }
            }
        }
    }
        
    //关闭所有客户端socket
    int clientfdslength = clientfds.size();
    for(int i = 0; i < clientfdslength; i++){
        if(clientfds[i] != INVALID_FD){
            close(clientfds[i]);
        }
    }
        /*
        struct sockaddr_in clientaddr;
        socklen_t clientaddrlen = sizeof(clientaddr);
        //接收客户端连接
        int clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientaddrlen);
        if(clientfd != -1){
            char recvBuf[32] = {0};
            //从客户端接收数据
            int ret = recv(clientfd, recvBuf, 32, 0);
            if(ret > 0){
                std::cout<<"recv data from client, data:"<<std::endl;
                //将收到的数据原封不动的发给客户端
                ret = send(clientfd, recvBuf, strlen(recvBuf), 0);
                if(ret != strlen(recvBuf)){
                    std::cout<<"send data error"<<std::endl;
                }else{
                    std::cout<<"send data to client success"<<std::endl;
                }
            }else{
                std::cout<<"recv data error."<<std::endl;
            }
            //close(clientfd);
            clientfds.push_back(clientfd);
        }
    }
    */
    //关闭监听socket
    close(listenfd);
    return 0;
}

上述代码的流程图如下所示:

服务器采用select函数的基本工作流程

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值