您好,欢迎访问代理记账网站
  • 价格透明
  • 信息保密
  • 进度掌控
  • 售后无忧

select使用实例

select函数是多路复用的一种,本文我们给出一个select的通信实例,看下select的代码如何组织,先上代码:

#include <unistd.h>
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <errno.h>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <fcntl.h>
#include <signal.h>

//设置文件描述符为非阻塞,避免读文件时阻塞
int setNonblock(int fd){
    int flags = fcntl(fd, F_GETFL, 0);
    if(flags < 0){
        std::cout << "F_GETFL failed:" << strerror(errno) << std::endl;
        return -1;
    }
    flags = flags | O_NONBLOCK;
    if(fcntl(fd, F_SETFL, flags)<0){
        std::cout << "set nonblock failed:" << strerror(errno) << std::endl;
        return -1;
    }
    return 0;
}


int main(){
    //初始化socket
    const std::string ip = "127.0.0.1";
    const int port = 9007;

    int server_sock = socket(AF_INET, SOCK_STREAM, 0);
    if(server_sock <0){
        std::cout << "create socket failed:" << strerror(errno) << std::endl;
        return -1;
    }

    int opt = 1;
    if(setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0){
        std::cout << "set socket option reuse address falied:" << strerror(errno) << std::endl;
        close(server_sock);
        return 0;
    }

    struct sockaddr_in server_addr;
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(ip.c_str());
    server_addr.sin_port = htons(port);

    if(bind(server_sock, (struct sockaddr*)&server_addr, (socklen_t)sizeof(server_addr)) < 0){
        std::cout << "address bind failed:" << strerror(errno) << std::endl;
        close(server_sock);
        return -1;
    }

    if(listen(server_sock, 5) < 0){
        std::cout << "listen ip:" << ip << ",port:" << port << "failed!" << std::endl;
        close(server_sock);
        return -1;
    }

    setNonblock(server_sock);
//初始化监听集合
    fd_set listen_fds;
    FD_ZERO(&listen_fds);
    FD_SET(server_sock, &listen_fds);
    //server_sock = 3
    int maxfd = 4;

    char buf[512];
    while(1){

        fd_set cycle_fds = listen_fds;
        std::cout << "============waiting for IO================" << std::endl;
        //阻塞等待io事件,select最后一个参数可以传入等待时间,如果传入时间t,将每隔t时间返回一次
        if(select(maxfd+1, &cycle_fds, nullptr, nullptr, nullptr) < 0){
            std::cout << "select error:" << errno <<std::endl;
            close(server_sock);
            return -1;
        }

        int fds_size = maxfd;
        for(int fd = 3; fd < fds_size; fd++){
            //FD_ISSET用于判断文件描述符上是否有IO事件发生
            if(FD_ISSET(fd, &cycle_fds)){
                //如果server_sock上有IO事件,说明有新的client请求连接
                if(fd == server_sock){
                    int client_sock = accept(server_sock, nullptr, nullptr);
                    if(setNonblock(client_sock) < 0){
                        std::cout << client_sock <<" set non_block falied: " << std::endl; 
                        close(client_sock);
                        continue;
                    }
                    FD_SET(client_sock, &listen_fds);
                    ++maxfd;
                    std::cout << "accept client" << std::endl;
                }
                //如果不是server_sock上的IO事件,说明发生了读事件。
                else{
                    int nread;
            
                    if(nread = read(fd, buf, sizeof(buf)-1) > 0){
                        std::cout << "receive message:" << buf << std::endl;
                    }
                    //nread < 0时,说明对端已经关闭了
                    else if(nread < 0){
                        close(fd);
                        FD_CLR(fd, &listen_fds);
                        --maxfd;
                        std::cout << "remote closed" << std::endl;
                    }
                }
            }
        }
    }
    close(server_sock);
}

select有阻塞和非阻塞两种方式,上面的代码中采用的是阻塞的方式,如果没有IO事件发生,进程将一直在select函数处阻塞,非阻塞的select可以按如下方式代替select部分的代码:

struct timeval tv;
tv.tv_sec = 30; //设置时间间隔为30s
tv.tv_msec = 0;
//select每隔30s返回一次
if(select(maxfd+1, &cycle_fds, nullptr, nullptr, &tv) < 0){
	std::cout << "select error:" << errno <<std::endl;
	return -1;
}

此外,需要注意在读写非阻塞的文件描述符时,如果读到正数,则这个数是读到的数据的大小,如果读到0,说明已经读取完毕,如果读到-1,说明对端已经关闭了,此时应该关闭对应的文件描述符。
client的代码与TCP(三)中使用的相同,下面展示的是测试的过程,首先我们开启select,将四个client与server连接。
在这里插入图片描述
这里四个client同时连接了同一个server,实际上就已经实现了多路复用,接着从四个client发送数据给server,看server是否能接收四个client的数据。
在这里插入图片描述
显然也是可以的,这就证明了select函数可以实现多路复用IO模型,下次我们来看看epoll的使用实例和epoll的两种触发方式。


分享:

低价透明

统一报价,无隐形消费

金牌服务

一对一专属顾问7*24小时金牌服务

信息保密

个人信息安全有保障

售后无忧

服务出问题客服经理全程跟进