Linux学习记录——사십일 高级IO(2)--- Select型服务器

文章目录

  • 1、思路
  • 2、select接口
  • 3、实现
    • 1、准备工作
    • 2、实现等待多个fd
    • 3、辨别连接和简单处理读事件
    • 4、简单处理写、读事件
  • 4、特点


1、思路

select就是多路转接IO。select能以某种形式,等待多个文件描述符,只要有哪个fd有数据就可以读取并全部返回。就绪的fd,要让用户知道。select等待的多个fd中,一定有少量或者全部都准备好了数据。

2、select接口

在这里插入图片描述

nfds输入型参数,表示select等待的多个fd中,fd对应的数 + 1

剩下四个参数都是输入输出型参数,意思是用户传入参数,操作系统通过这些参数把结果交给用户。

除去timeout,中间三个是同一个类型的,分别对应读、写、异常,只是用处不同,但思路相同。timeout的类型是一个结构体,表示select应该以什么方式来轮询检测,一共有3种值。NULL/nullptr表示阻塞等待,等待多个fd的时候就是把文件结构体的指针放到阻塞队列中,如果没有就绪的就不返回;{0,0}表示非阻塞等待,也就是等待多个文件描述符时,如果没有就绪的,就等待0s后出错返回;{n,m}表示n秒以内阻塞等待,超过这个时间就timeout一次,也就是非阻塞一次,也就是出错返回,如果n秒内得到了数据并返回,那么timeout此时就表示剩余时间。

返回值是一个int值,大于0表示有几个fd是就绪的,为0表示超时出错返回了,小于0表示等待失败。

readfd参数的类型是fd_set类型,是一个位图结构

在这里插入图片描述

用位图结构来表示多个fd,进行用户和内核之间的信息的互相传递。对于位图结构的操作只能用系统给定的接口。

在这里插入图片描述

FD_CLR把指定的描述符从指定的集合中清除;FD_ISSET查看指定fd是否在集合中;FD_SET添加fd到集合中;FD_ZERO将集合清空。

通过这个位图结构,用户告诉内核,用户关心哪些fd对应的文件有数据,内核要告诉用户,哪些fd的读事件就绪了。

假设用户这样设置,fd_set rfds:0110 111,表明文件描述符为4和7的不管,看fd为012356的文件;如果只有3号fd就绪了,内核传回来的rfds则是0000 1000;用户拿到rfds后,扫描位图,哪个位置为1就说明是哪个文件就绪了。

fd_set是OS提供的固定大小的数据类型,比特位数有上限,所以select能管理的fd也有上限。查看多大

#include <iostream>
#include <sys/select.h>

int main()
{
    fd_set fd;
    std::cout << sizeof(fd) << std::endl;
    return 0;
}

字节数就是sizeof(fd) * 8。

3、实现

1、准备工作

makefile

selectserver:main.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -f selectserver

main.cc

#include "SelectServer.hpp"
#include <memory>

int main()
{
    //fd_set fd;
    //std::cout << sizeof(fd) << std::endl;
    std::unique_ptr<SelectServer> svr(new SelectServer(3389));
    svr->InitServer();
    svr->Start();
    return 0;
}

SelectServer.hpp

#pragma once
#include <iostream>
#include <string>
#include <sys/select.h>

class SelectServer
{
public:
    SelectServer(uint16_t port): port_(port)
    {}

    void InitServer()
    {

    }

    void Start()
    {

    }
    
    ~SelectServer()
    {}
private:
    uint16_t port_;
};

引入之前的Sock.hpp和log.hpp和err.hpp

Sock.hpp

#pragma once
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include "err.hpp"
#include "log.hpp"

static const int gbacklog = 32;
static const int defaultfd = -1;

class Sock
{
public:
    Sock(): _sock(defaultfd)
    {}

    void Socket()
    {
        _sock= socket(AF_INET, SOCK_STREAM, 0);
        if(_sock < 0)
        {
            logMessage(Fatal, "socket error, code: %d, errstring: %s", errno, strerror(errno));
            exit(SOCKET_ERR);
        }
        //设置地址是可复用的
        int opt = 1;
        setsockopt(_sock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
    }

    void Bind(const uint16_t& port)
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr = INADDR_ANY;
        if(bind(_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
        {
            logMessage(Fatal, "bind error, code: %d, errstring: %s", errno, strerror(errno));
            exit(BIND_ERR);
        }
    }

    void Listen()
    {
        if(listen(_sock, gbacklog) < 0)//第二个参数维护了一个队列,发送了连接请求但是服务端没有处理的客户端,服务端开始accept后,就会出现另一个队列,就是服务端接受了请求但还没被accept的客户端
        {
            logMessage(Fatal, "listen error, code: %d, errstring: %s", errno, strerror(errno));
            exit(LISTEN_ERR);
        }
    }

    int Accept(std::string* clientip, uint16_t* clientport)
    {
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        int sock = accept(_sock, (struct sockaddr*)&temp, &len);
        if(sock < 0)
        {
            logMessage(Warning, "accept error, code: %d, errstring: %s", errno, strerror(errno));
        }
        else
        {
            *clientip = inet_ntoa(temp.sin_addr);//这个函数就可以从结构体中拿出ip地址,转换好后返回
            *clientport = ntohs(temp.sin_port);
        }
        return sock;
    }

    int Connect(const std::string& serverip, const uint16_t& serverport)//让别的客户端来连接服务端
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(serverport);
        server.sin_addr.s_addr = inet_addr(serverip.c_str());
        return connect(_sock, (struct sockaddr*)&server, sizeof(server));//先不打印消息
    }

    int Fd()
    {
        return _sock;
    }

    void Close()
    {
        if(_sock != defaultfd) close(_sock);
    }

    ~Sock()
    {}
private:
    int _sock;
};

err.hpp

#pragma once

enum
{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    CONNECT_ERR,
    SETSID_ERR,
    OPEN_ERR
};

log.hpp

#pragma once

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdarg>
#include <ctime>
#include <sys/types.h>
#include <unistd.h>

const std::string filename0 = "log/tcpserver.log.Debug";
const std::string filename1 = "log/tcpserver.log.Info";
const std::string filename2 = "log/tcpserver.log.Warning";
const std::string filename3 = "log/tcpserver.log.Error";
const std::string filename4 = "log/tcpserver.log.Fatal";
const std::string filename5 = "log/tcpserver.log.Unknown";


enum
{
    Debug = 0,//调试信息
    Info,//正常信息
    Warning,//告警,不影响运行
    Error,//一般错误
    Fatal,//严重错误
    Unknown
};

static std::string toLevelString(int level, std::string& filename)
{
    switch(level)
    {
    case Debug:
        filename = filename0;
        return "Debug";
    case Info:
        filename = filename1;
        return "Info";
    case Warning:
        filename = filename2;
        return "Warning";
    case Error:
        filename = filename3;
        return "Error";
    case Fatal:
        filename = filename4;
        return "Fatal";
    default:
        filename = filename5;
        return "Unknown";
    }
}

static std::string getTime()
{
    time_t curr = time(nullptr);//拿到当前时间
    struct tm *tmp = localtime(&curr);//这个结构体有对于时间单位的int变量
    char buffer[128];
    snprintf(buffer, sizeof(buffer), "%d-%d-%d %d:%d:%d", tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday, \
        tmp->tm_hour, tmp->tm_min, tmp->tm_sec);//这些tm_的变量就是结构体中自带的,tm_year是从1900年开始算的,所以+1900
    return buffer;
}

//日志格式: 日志等级 时间 pid 消息体
//logMessage(DEBUG, "hello: %d, %s", 12, s.c_str()); 12以%d形式打印, s.c_str()以%s形式打印
void logMessage(int level, const char* format, ...)//...就是可变参数,format是输出格式
{
    //写入到两个缓冲区中
    char logLeft[1024];//用来显示日志等级,时间,pid
    std::string filename;
    std::string level_string = toLevelString(level, filename);
    std::string curr_time = getTime();
    snprintf(logLeft, sizeof(logLeft), "[%s] [%s] [%d] ", level_string.c_str(), curr_time.c_str(), getpid());
    char logRight[1024];//用来显示消息体
    va_list p;
    va_start(p, format);
    //直接用这个接口来对format进行操作,提取信息
    vsnprintf(logRight, sizeof(logRight), format, p);
    va_end(p);
    //打印
    printf("%s%s\n", logLeft, logRight);
    //format是一个字符串,里面有格式,比如%d, %c,通过这个就可以用arg来提取参数
    //保存到文件中
    FILE* fp = fopen(filename.c_str(), "a");
    if(fp == nullptr) return ;
    fprintf(fp, "%s%s\n", logLeft, logRight);
    fflush(fp);
    fclose(fp);
    //va_list p;//char*
    //下面是三个宏函数
    //int a = va_arg(p, int);//根据类型提取参数
    //va_start(p, format);//让p指向可变参数部分的起始地址
    //va_end(p);//把p置为空, p = NULL
}

2、实现等待多个fd

#pragma once
#include <iostream>
#include <string>
#include <sys/select.h>
#include "Sock.hpp"
#include "log.hpp"
#include "err.hpp"

class SelectServer
{
public:
    SelectServer(uint16_t port): port_(port)
    {}

    void InitServer()
    {
        listensock_.Socket();
        listensock_.Bind(port_);
        listensock_.Listen();
    }

    void Start()
    {
        listensock_.Accept();
    }

    ~SelectServer()
    {}
private:
    uint16_t port_;
    Sock listensock_;
};

最一开始,服务器只有一个socket,就是上面这些代码创建出来的。如果没创建,服务器根本就没有socket,那么这时候Accept也没有检测到链接,只能阻塞着。所以不能直接Accept,要先有链接。在网络中,新链接当作读事件来就绪。我们先添加套接字到select中再处理。

#pragma once
#include <iostream>
#include <string>
#include <sys/select.h>
#include <cstring>
#include "Sock.hpp"
#include "log.hpp"
#include "err.hpp"

class SelectServer
{
public:
    SelectServer(uint16_t port): port_(port)
    {}

    void InitServer()
    {
        listensock_.Socket();
        listensock_.Bind(port_);
        listensock_.Listen();
    }

    void Start()
    {
        fd_set rfds;
        FD_ZERO(&rfds); // 先清空,即使是新的rfds
        FD_SET(listensock_.Fd(), &rfds);
        while (true)
        {
        	struct timeval timeout = {2, 0};//timeout是输出型参数,每一次都需要重新设置,不然后续都会变成0而直接打印第一条语句
            int n = select(listensock_.Fd() + 1, &rfds, nullptr, nullptr, &timeout);
            switch (n)
            {
            case 0:
                logMessage(Debug, "timeout, %d: %s", errno, strerror(errno));
                break;
            case -1:
                logMessage(Warning, "%d: %s", errno, strerror(errno));
                break;
            default:
                logMessage(Debug, "有一个就绪事件发生了");//这时候就说明至少有一个fd就绪了
                break;
            }
        }
    }

    ~SelectServer()
    {
        listensock_.Close();
    }
private:
    uint16_t port_;
    Sock listensock_;
};

事件现在已经可以检测到是否就绪了。

    void HandlerEvent(fd_set& rfds)
    {
        if(FD_ISSET(listensock_.Fd(), &rfds))
        {
            std::cout << "有一个新连接到了" << std::endl;
            //进行Accept
        }
    }

            default:
                logMessage(Debug, "有一个就绪事件发生了");//这时候就说明至少有一个fd就绪了
                HandlerEvent(rfds);//rfds已经设置好了
                break;

然后完善处理

    void HandlerEvent(fd_set& rfds)
    {
        if(FD_ISSET(listensock_.Fd(), &rfds))
        {
            std::cout << "有一个新连接到了" << std::endl;
            //进行Accept,这时候不会阻塞,因为走到这里就说明已经有连接了
            std::string clientip;
            uint16_t clientport;
            int sock = listensock_.Accept(&clientip, &clientport);//获取连接
            if(sock < 0) return ;
            //到这里已得到了新连接对应的fd,但不能直接读取数据
            //将sock添加到select的rfds中,让select来管理
            //打印出来的sock就是可用的fd,使用多个客户端连接时,clientport会变,说明连接成功,sock就是当前可用的fd
            logMessage(Debug, "[%s:%d], sock: %d", clientip.c_str(), clientport, sock);
            //select对fd要有持续监听能力,不能有一个就绪其它全部重置
        }
    }

获取连接代表新增了一个fd,可以用套接字来进行IO服务,但还不知道数据是否就绪,只知道有连接。客户端发送请求,和我们的服务端建立连接后,客户端可以不发数据,这时调用read接口就会阻塞在这里。

获得的sock不能直接添加到rfds,以后越来越多的sock都要处理,时间耗费长,且有个前提,应当处理用户关心的fd。无脑设置进去,当我们关心的fd就绪后,其它都会清0,只有那个fd的位置会是1,所以也无用,所以select对fd要有持续监听能力,不能有一个就绪其它全部清零。

这里的思路其实也不难,要持续监听,我们就维护一个数组保护所有获得连接的sock就行。

const static int gport = 8888;
typedef int type_t;

class SelectServer
{
    static const int N = (sizeof(fd_set) * 8);
public:
    SelectServer(uint16_t port = gport) : port_(port)
    {
    }
//...
private:
    uint16_t port_;
    Sock listensock_;
    type_t fdarray_[N];

初始化

static const int defaultfd = -1;//在之前的Sock.hpp中就已经有了这个全局变量,所以SelectServer.hpp中可以不设置这个,不过Sock.hpp中不要加static,这样两个文件都可以用

    void InitServer()
    {
        listensock_.Socket();
        listensock_.Bind(port_);
        listensock_.Listen();
        for(int i = 0; i < N; i++) fdarray_[i] = defaultfd;
    }

Start

    void Start()
    {
        fdarray_[0] = listensock_.Fd();
        while (true)
        {
            struct timeval timeout = {2, 0};
            //rfds是输入输出参数,所以rfds每次都要重置
            //服务器在运行中,套接字对应的fd的值一直在动态变化,所以select中第一个参数的值也得变化,否则无法照顾到其它fd
            //所以rfds相关操作要在循环里做
            fd_set rfds;
            FD_ZERO(&rfds);
            int maxfd = fdarray_[0];//用max_fd来表示fd中的最大值,看select接口那里的说明
            for(int i = 0; i < N; i++)
            {
                if(fdarray_[i] == defaultfd) continue;
                //合法fd
                FD_SET(fdarray_[i], &rfds);
                if(maxfd < fdarray_[i]) maxfd = fdarray_[i];
            }
            int n = select(maxfd + 1, &rfds, nullptr, nullptr, &timeout);
            switch (n)
            {
            case 0:
                logMessage(Debug, "timeout, %d: %s", errno, strerror(errno));
                break;
            case -1:
                logMessage(Warning, "%d: %s", errno, strerror(errno));
                break;
            default:
                logMessage(Debug, "有一个就绪事件发生了");//这时候就说明至少有一个fd就绪了
                HandlerEvent(rfds);//rfds已经设置好了
                break;
            }
        }
    }

接下来就把sock放到fdarray_中。

    void HandlerEvent(fd_set& rfds)
    {
        if(FD_ISSET(listensock_.Fd(), &rfds))
        {
            std::cout << "有一个新连接到了" << std::endl;
            //进行Accept,这时候不会阻塞,因为走到这里就说明已经有连接了
            std::string clientip;
            uint16_t clientport;
            int sock = listensock_.Accept(&clientip, &clientport);//获取连接
            if(sock < 0) return ;
            //到这里已得到了新连接对应的fd,但不能直接读取数据
            //将sock添加到select的rfds中,让select来管理
            //打印出来的sock就是可用的fd,使用多个客户端连接时,clientport会变,说明连接成功,sock就是当前可用的fd
            logMessage(Debug, "[%s:%d], sock: %d", clientip.c_str(), clientport, sock);
            //select对fd要有持续监听能力,不能有一个就绪其它全部重置

            //让select来管理,把sock添加到fdarray_[]中
            int pos = 1;
            for( ; pos < N; pos++)
            {
                if(fdarray_[pos] == defaultfd) break;
            }
            if(pos >= N)
            {
                close(sock);//说明不在工作范围内,数组已经满了,那就关闭这个连接
                logMessage(Warning, "sockfd array[] full");
            }
            else fdarray_[pos] = sock;
        }

//...
            default:
                logMessage(Debug, "有一个就绪事件发生了");//这时候就说明至少有一个fd就绪了
                HandlerEvent(rfds);//rfds已经设置好了
                DebugPrint();
                break;
            }
        }
    }

    void DebugPrint()
    {
        std::cout << "fdarray[]: ";
        for(int i = 0; i < N; ++i)
        {
            if(fdarray_[i] == defaultfd) continue;
            std::cout << fdarray_[i] << " ";
        }
        std::cout << "\n";
    }

以上的代码就已经能够做到等到多个fd并管理好它们了。

3、辨别连接和简单处理读事件

能够处理很多个连接,也都能把它们一次次的设置进集合里,但我们需要有条件地接收,要有目的地去接收。
先改一下形式

    void Accepter()
    {
        std::cout << "有一个新连接到了" << std::endl;
        //进行Accept,这时候不会阻塞,因为走到这里就说明已经有连接了
        std::string clientip;
        uint16_t clientport;
        int sock = listensock_.Accept(&clientip, &clientport);//获取连接
        if(sock < 0) return ;
        //到这里已得到了新连接对应的fd,但不能直接读取数据
        //将sock添加到select的rfds中,让select来管理
        //打印出来的sock就是可用的fd,使用多个客户端连接时,clientport会变,说明连接成功,sock就是当前可用的fd
        logMessage(Debug, "[%s:%d], sock: %d", clientip.c_str(), clientport, sock);
        //select对fd要有持续监听能力,不能有一个就绪其它全部重置

        // 让select来管理,把sock添加到fdarray_[]中
        int pos = 1;
        for (; pos < N; pos++)
        {
            if (fdarray_[pos] == defaultfd)
                break;
        }
        if (pos >= N)
        {
            close(sock); // 说明不在工作范围内,数组已经满了,那就关闭这个连接
            logMessage(Warning, "sockfd array[] full");
        }
        else
            fdarray_[pos] = sock;
    }

    void HandlerEvent(fd_set &rfds)
    {
        if (FD_ISSET(listensock_.Fd(), &rfds))
        {
            Accepter();
        }
    }

HandlerEvent除了放入连接,还得处理一下连接。

    void Recver(int index)
    {
        int fd = fdarray_[index];
        // 用户不关心的fd就绪了
        char buffer[1024];
        // 读取不会被阻塞,因为经过上面的筛选,select已经确定这个fd是已经有数据了的
        // 不过只有这一次不会阻塞,之后不确定,因为一次不保证数据全部读完
        ssize_t s = recv(fd, buffer, sizeof(buffer) - 1, 0);
        if (s > 0)
        {
            buffer[s - 1] = 0;
            std::cout << "client# " << buffer << std::endl;
            // 写一下读了再发回去,发回去也需要select管理,因为不知道自己发送条件是否就绪,比如自己的发送缓冲区是否满
            std::string echo = buffer;
            echo += " [select server echo]";
            send(fd, echo.c_str(), echo.size(), 0); // 先用阻塞式
        }
        else // 读完或者出错
        {
            if (s == 0)
                logMessage(Info, "client quit..., fdarray_[i] -> defaultfd: %d->%d", fdarray_[i], defaultfd);
            else
                logMessage(Warning, "recv error, client quit..., fdarray_[i] -> defaultfd: %d->%d", fdarray_[i], defaultfd);
            // 清空当前这个连接,关闭fd
            close(fdarray_[index]);
            fdarray_[index] = defaultfd;
        }
    }

    void HandlerEvent(fd_set &rfds)
    {
        //这个函数并不知道rfds的哪些fd就绪了,所以得循环检测
        for(int i = 0; i < N; i++)
        {
            if(fdarray_[i] == defaultfd) continue;
            //就绪的fd属于用户关心的,且它存在与rfds集合中
            if ((fdarray_[i] == listensock_.Fd()) && FD_ISSET(listensock_.Fd(), &rfds))
            {
                Accepter();
            }
            else if((fdarray_[i] != listensock_.Fd()) &&FD_ISSET(fdarray_[i], &rfds))
            {
                Recver(i);
            }
        }
    }

4、简单处理写、读事件

为了方便,先建立一个结构体

#define READ_EVENT (0X1)
#define WRITE_EVENT (0X1<<1)
#define EXCEPT_EVENT (0X1<<2)

typedef struct FdEvent
{
    int fd;
    uint8_t event;
    std::string clientip;
    uint16_t clientport;
}type_t;

static const int defaultevent = 0;

这样fdarray_这个数组就变成结构体类型的数组了。代码很多地方也要改一下,比如原本fdarray_[i]改成fdarray_[i].fd。

    void InitServer()
    {
        listensock_.Socket();
        listensock_.Bind(port_);
        listensock_.Listen();
        for (int i = 0; i < N; i++)//初始化一下
        {
            fdarray_[i].fd = defaultfd;
            fdarray_[i].event = defaultevent;
            fdarray_[i].clientport = 0;
        }
    }

    void Start()
    {
        fdarray_[0].fd = listensock_.Fd();
        fdarray_[0].event = READ_EVENT;
        while (true)
        {
            struct timeval timeout = {2, 0};
            // rfds是输入输出参数,所以rfds每次都要重置
            // 服务器在运行中,套接字对应的fd的值一直在动态变化,所以select中第一个参数的值也得变化,否则无法照顾到其它fd
            // 所以rfds相关操作要在循环里做
            fd_set rfds;
            fd_set wfds;
            FD_ZERO(&rfds);
            FD_ZERO(&wfds);
            int maxfd = fdarray_[0].fd; // 用max_fd来表示fd中的最大值,看select接口那里的说明
            for (int i = 0; i < N; i++)
            {
                if (fdarray_[i].fd == defaultfd)
                    continue;
                // 合法fd
                if(fdarray_[i].event & READ_EVENT) FD_SET(fdarray_[i].fd, &rfds);
                if(fdarray_[i].event & WRITE_EVENT) FD_SET(fdarray_[i].fd, &wfds);
                if (maxfd < fdarray_[i].fd)
                    maxfd = fdarray_[i].fd;
            }
            int n = select(maxfd + 1, &rfds, &wfds, nullptr, &timeout);
            switch (n)
            {
            case 0:
                logMessage(Debug, "timeout, %d: %s", errno, strerror(errno));
                break;
            case -1:
                logMessage(Warning, "%d: %s", errno, strerror(errno));
                break;
            default:
                logMessage(Debug, "有一个就绪事件发生了"); // 这时候就说明至少有一个fd就绪了
                HandlerEvent(rfds, wfds);                  
                DebugPrint();
                break;
            }
        }
    }

    void DebugPrint()
    {
        std::cout << "fdarray[]: ";
        for (int i = 0; i < N; ++i)
        {
            if (fdarray_[i].fd == defaultfd)
                continue;
            std::cout << fdarray_[i].fd << " ";
        }
        std::cout << "\n";
    }

上面加入写事件,虽然我们还是关心读事件,但是写事件也要管理。以及处理用的函数部分

    void Accepter()
    {
        std::cout << "有一个新连接到了" << std::endl;
        //进行Accept,这时候不会阻塞,因为走到这里就说明已经有连接了
        std::string clientip;
        uint16_t clientport;
        int sock = listensock_.Accept(&clientip, &clientport);//获取连接
        if(sock < 0) return ;
        //到这里已得到了新连接对应的fd,但不能直接读取数据
        //将sock添加到select的rfds中,让select来管理
        //打印出来的sock就是可用的fd,使用多个客户端连接时,clientport会变,说明连接成功,sock就是当前可用的fd
        logMessage(Debug, "[%s:%d], sock: %d", clientip.c_str(), clientport, sock);
        //select对fd要有持续监听能力,不能有一个就绪其它全部重置

        // 让select来管理,把sock添加到fdarray_[]中
        int pos = 1;
        for (; pos < N; pos++)
        {
            if (fdarray_[pos].fd == defaultfd)
                break;
        }
        if (pos >= N)
        {
            close(sock); // 说明不在工作范围内,数组已经满了,那就关闭这个连接
            logMessage(Warning, "sockfd array[] full");
        }
        else
        {
            fdarray_[pos].fd = sock;
            //fdarray_[pos].event = (READ_EVENT | WRITE_EVENT);//读写都关心,如果只关心一个,那就写一个
            fdarray_[pos].event = READ_EVENT;
            fdarray_[pos].clientip = clientip;
            fdarray_[pos].clientport = clientport ;
        }
    }

    void Recver(int index)
    {
        int fd = fdarray_[index].fd;
        // 用户不关心的fd就绪了
        char buffer[1024];
        // 读取不会被阻塞,因为经过上面的筛选,select已经确定这个fd是已经有数据了的
        // 不过只有这一次不会阻塞,之后不确定,因为一次不保证数据全部读完
        ssize_t s = recv(fd, buffer, sizeof(buffer) - 1, 0);
        if (s > 0)
        {
            buffer[s - 1] = 0;
            std::cout << fdarray_[index].clientip << ":" << fdarray_[index].clientport << "# " <<buffer << std::endl;
            // 写一下读了再发回去,发回去也需要select管理,因为不知道自己发送条件是否就绪,比如自己的发送缓冲区是否满
            std::string echo = buffer;
            echo += " [select server echo]";
            send(fd, echo.c_str(), echo.size(), 0); // 先用阻塞式
        }
        else // 读完或者出错
        {
            if (s == 0)
                logMessage(Info, "client quit..., fdarray_[i] -> defaultfd: %d->%d", fdarray_[index], defaultfd);
            else
                logMessage(Warning, "recv error, client quit..., fdarray_[i] -> defaultfd: %d->%d", fdarray_[index], defaultfd);
            // 清空当前这个连接,关闭fd
            close(fdarray_[index].fd);
            fdarray_[index].fd = defaultfd;
            fdarray_[index].event = defaultevent;
            fdarray_[index].clientip.resize(0);
            fdarray_[index].clientport = 0;
        }
    }

    void HandlerEvent(fd_set &rfds, fd_set &wfds)
    {
        //这个函数并不知道rfds的哪些fd就绪了,所以得循环检测
        for (int i = 0; i < N; i++)
        {
            if (fdarray_[i].fd == defaultfd)  continue;
            // 关心读事件且读事件已经就绪
            if ((fdarray_[i].event & READ_EVENT) && FD_ISSET(fdarray_[i].fd, &rfds))
            {
                if (fdarray_[i].fd == listensock_.Fd())
                {
                    Accepter();
                }
                else if (fdarray_[i].fd != listensock_.Fd())
                {
                    Recver(i);
                }
                else {}
            }
            // 关心写事件且读事件已经就绪
            else if ((fdarray_[i].event & WRITE_EVENT) && FD_ISSET(fdarray_[i].fd, &wfds))
            {}
            else {}
        }
    }

4、特点

可监控,能关心的fd个数有上限;将fd加入select监控集的同时,还需要使用一个数组结构array保存放到select监控集中的fd,用于在select返回后,array作为源数据和fd_set进行FD_ISSET判断,以及select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。fd_set的大小可以调整,不过涉及到内核。

select也有缺点。每次调用select,都需要手动设置fd集合来告知系统用户关心哪些fd,不方便,且要把fd集合从用户态拷贝到内核态,每次都这样,开销不算小。以及在代码中可以发现,操作系统底层不知道fd对应的文件里是否有数据,它就得遍历用户关心的全部fd,而用户层也要遍历,系统在遍历时,如果没有fd就绪且是非阻塞,就直接返回了,如果有timeout,那就过了时间还没有就绪再返回,如果是阻塞的,那么没有就绪的就挂起全部进程,直到有就绪的,就再遍历一次找到这个就绪的,除此之外,我们的代码中也遍历了好多次。select只是相对于之前的IO方式高效,但也并不是好方案。

select能等待的fd数量太少。存储文件描述符的一般是数组,也就是数组下标表示fd,数组有上限,如果做成服务器,这个数组会调最大,所以有上限是必然的。一个进程能等的fd数量有限,但select能等的更少,所以select等的少是select设计时的限制,和进程无关。select上限就不高。


下一篇写poll型服务器,会在select代码的基础上进行更改。

本篇gitee

结束。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/326023.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

服务异步通讯——springcloud

服务异步通讯——springcloud 文章目录 服务异步通讯——springcloud初始MQRabbitMQ快速入门单机部署1.1.下载镜像安装MQ SpringAMQPwork Queue 工作队列Fanout Exchange广播模式DirectExchange路由模式TopicExchange话题模式 消息转换器 初始MQ RabbitMQ快速入门 官网https:/…

手把手教你SWOT分析!建议收藏

最近&#xff0c;我一直为一件事情感到困扰。那家位于市中心的西点店生意越来越好&#xff0c;甚至已经开了两家分店&#xff0c;但是挣来的钱还不足够买房子。于是最近&#xff0c;我被这如火如荼的奶茶市场所吸引&#xff0c;想要利用已有的资源开一家奶茶店。但是我不确定这…

计算机视觉开发工程师怎么考?报考难度大吗?证书含金量高吗?

为进一步贯彻落实中共中央印发《关于深化人才发展体制机制改革的意见》和国务院印发《关于“十四五”数字经济发展规划》等有关工作的部署要求&#xff0c;深入实施人才强国战略和创新驱动发展战略&#xff0c;加强全国数字化人才队伍建设&#xff0c;持续推进人工智能专业人员…

JNPF低代码引擎到底是什么?

最近听说一款可以免费部署本地进行试用的低代码引擎&#xff0c;源码上支持100%源码&#xff0c;提供的功能和技术支持比较完善。借助这篇篇幅我们了解下JNPF到底是什么&#xff1f; JNPF开发平台是一款PaaS服务为核心的零代码开发平台&#xff0c;平台提供了多租户账号管理、主…

《TrollStore巨魔商店》TrollStore2安装使用教程支持IOS14.0-16.6.1

TrollStore(巨魔商店) 简单的说就相当于一个永久的免费证书&#xff0c;它可以给你的iPhone和iPad安装任何你想要安装的App软件&#xff0c;而且不需要越狱,不用担心证书签名过期的问题&#xff0c;不需要个人签名和企业签名。 支持的版本&#xff1a; TrollStore安装和使用教…

MIT 6s081 lab1:Xv6 and Unix utilities

Lab1: Xv6 and Unix utilities 作业网址&#xff1a;https://pdos.csail.mit.edu/6.828/2020/labs/util.html Boot xv6(easy) 下载&#xff0c;启动xv6系统 $ git clone git://g.csail.mit.edu/xv6-labs-2020 Cloning into xv6-labs-2020... ... $ cd xv6-labs-2020 $ git …

【dc-dc】世微AP5127平均电流型LED降压恒流驱动器 双色切换的LED灯驱动方案

这是一款双色切换的LED灯方案&#xff0c;12-50V 降压恒流,输出&#xff1a;6V 2.5A ​ 这是一款PWM工作模式 , 高效率、 外围简单、内置功率管&#xff0c;适用于 输入的 高 精度降压 LED 恒流驱动芯片。输出大功率可 达 25W&#xff0c;电流 2.5A。 可实现全亮/半亮功能切换…

Linux 设备树详解

1、概述 设备树&#xff08; Device Tree&#xff09;是一种描述硬件的数据结构&#xff0c;在操作系统&#xff08; OS&#xff09;引导 阶段进行设备初始化的时候&#xff0c;数据结构中的硬件信息被检测并传递给操作系统 最早诞生于 Open Firmware&#xff0c; Flattened De…

【MySQL】:DDL数据库定义与操作

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; MySQL从入门到进阶 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言一. SQL的分类二. DDL数据库操作2.1 查询所有数据库2.2 查询当前数据库2.3 创建数…

测量鼠标DPI的三种方法,总有一种适合你

DPI(dots per inch)代表每英寸点数,是一种用于各种技术设备(包括打印机)的测量方法,但对于鼠标来说,指的是鼠标在桌面上移动1英寸的距离的同时,鼠标光标能够在屏幕上移动多少“点”。 许多游戏鼠标都有按钮,可以让你在玩游戏时动态切换DPI,但如果你不知道鼠标的DPI怎…

x-cmd pkg | duf - df 命令的现代化替代品

目录 简介用户首次快速实验指南技术特点竞品和相关作品进一步探索 简介 Duf &#xff08;Disk Usage/Free Utility&#xff09;是一个磁盘分析工具。其直观的输出和多样化的自定义选项&#xff0c;帮助用户更好地管理和优化存储资源。 用户首次快速实验指南 使用 x duf 即可自…

为什么大厂暴力裁员确很少有技术人敢举报?

最近几年大厂暴力裁员的事件太多了&#xff0c;但是确鲜有当事人出来举报&#xff0c;这个又是为什么呢&#xff1f;本文从中立的角度&#xff0c;来给大家来分析一下。 大厂会包装&#xff0c;将暴力裁员包装为KPI优化 KPI这个玩意&#xff0c;其实是蛮扯淡的&#xff0c;也…

重磅!30余所985高校全面取消博士统考!

2024年博士招生&#xff0c;又有“双一流”高校取消统考。 近日&#xff0c;各大高校正在陆续发布《2024年博士研究生招生简章》&#xff0c;其中南昌大学的博士招生方式引起了广泛关注。据悉&#xff0c;南昌大学将全面实行“申请—考核”制选拔方式&#xff0c;适用于直接攻…

【Linux】自定义shell

👑作者主页:@安 度 因 🏠学习社区:安度因 📖专栏链接:Linux 文章目录 获取命令行前置字段命令行输入解析命令行普通指令的执行子进程执行命令指令类型判断 && 内建命令总结 &&a

什么是OV证书?

OV证书是一种经过严格身份验证的SSL/TLS证书&#xff0c;相较于基础的域名验证(DV)证书&#xff0c;它的验证过程更为深入和全面。在颁发OV证书前&#xff0c;证书颁发机构(CA)不仅会验证申请者对域名的所有权&#xff0c;还会对企业或组织的身份进行严格的审查&#xff0c;包括…

京东年度数据报告-2023全年度饮料十大热门品牌销量(销额)榜单

2023年度&#xff0c;饮料市场的销售相较去年呈下滑状态。根据鲸参谋电商数据分析平台的相关数据显示&#xff0c;京东平台上饮料市场的年度销量约9100万&#xff0c;同比下滑约9%&#xff1b;销售额约53亿&#xff0c;同比下滑约3%。另外&#xff0c;天猫平台上饮料市场的年度…

上门按摩系统:科技与传统融合的新体验

在快节奏的现代生活中&#xff0c;人们越来越重视身心健康。传统的按摩方式虽然深受喜爱&#xff0c;却常因时间、地点的限制而无法满足需求。此时&#xff0c;上门按摩系统应运而生&#xff0c;将科技与传统的按摩技艺完美结合&#xff0c;为用户提供更便捷、个性化的服务。 上…

基于SSM的流浪动物救助网站的设计与实现-计算机毕业设计源码82131

摘 要 随着生活水平的持续提高和家庭规模的缩小&#xff0c;宠物已经成为越来越多都市人生活的一部分&#xff0c;随着宠物的增多&#xff0c;流浪的动物的日益增多&#xff0c;中国的流浪动物领养和救助也随之形成规模&#xff0c;同时展现巨大潜力。本次系统的是基于SSM框架的…

使用Sqoop的并行处理:扩展数据传输

使用Sqoop的并行处理是在大数据环境中高效传输数据的关键。它可以显著减少数据传输的时间&#xff0c;并充分利用集群资源。本文将深入探讨Sqoop的并行处理能力&#xff0c;提供详细的示例代码&#xff0c;以帮助大家更全面地了解和应用这一技术。 Sqoop的并行处理 在开始介绍…

Java工具类——json字符串格式化处理

在我们拿到一团未经格式化的json字符串时&#xff0c;非常不方便查看&#xff0c;比如这样 {"APP_HEAD": {"TOTAL_NUM": "-1","PGUP_OR_PGDN": "0"},"SYS_HEAD": {"RET": [{"RET_CODE": &qu…