欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Linux多路轉(zhuǎn)接之select函數(shù)使用方式

 更新時(shí)間:2024年08月01日 09:41:49   作者:zzu_ljk  
這篇文章主要介紹了Linux多路轉(zhuǎn)接之select函數(shù)使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

首先我們要了解一下,什么是多路轉(zhuǎn)接?

多路轉(zhuǎn)接也叫多路復(fù)用,是一種用于管理多個(gè)IO通道的技術(shù)。

它能實(shí)現(xiàn)同時(shí)監(jiān)聽(tīng)和處理多個(gè)IO事件,而不是為每個(gè)IO通道創(chuàng)建單獨(dú)的線程或者進(jìn)程,多路轉(zhuǎn)接允許在單個(gè)進(jìn)程或線程中同時(shí)處理多個(gè)IO操作,從而提高程序的性能和效率。

本篇文章介紹的select函數(shù),就用于select系統(tǒng)調(diào)用的多路轉(zhuǎn)接技術(shù)。

1. 認(rèn)識(shí)select函數(shù)

select函數(shù)是系統(tǒng)提供的一個(gè)多路轉(zhuǎn)接接口。

IO = 等待就緒 + 數(shù)據(jù)拷貝,而select是只負(fù)責(zé)等。

  • select系統(tǒng)調(diào)用可以讓我們的程序同時(shí)監(jiān)聽(tīng)多個(gè)文件描述符上的事件是否就緒。
  • select的核心工作就是等,當(dāng)監(jiān)聽(tīng)的多個(gè)文件描述符中有一個(gè)或多個(gè)事件就緒時(shí),select函數(shù)才會(huì)成功返回并將對(duì)應(yīng)文件描述符的就緒事件告知調(diào)用者。

2. select函數(shù)原型

參數(shù)說(shuō)明:

  • nfds:需要監(jiān)聽(tīng)的文件描述符中,最大的文件描述符值 + 1。
  • readfs:輸入輸出型參數(shù),調(diào)用時(shí)用戶告知內(nèi)核需要監(jiān)聽(tīng)哪些文件描述符的讀事件是否就緒,返回時(shí)內(nèi)核告訴用戶哪些文件描述符的讀事件已經(jīng)就緒。
  • writefds:輸入輸出型參數(shù),調(diào)用時(shí)用戶告知內(nèi)核需要監(jiān)聽(tīng)哪些文件描述符的寫事件是否就緒,返回時(shí)內(nèi)核告知用戶哪些文件描述符的寫事件已經(jīng)就緒。
  • exceptfds:輸入輸出型參數(shù),調(diào)用時(shí)告知內(nèi)核需要監(jiān)聽(tīng)哪些文件描述符的異常事件是否就緒,返回時(shí)內(nèi)核告知用戶哪些文件描述符的異常事件已經(jīng)就緒。
  • timeout:輸入輸出參數(shù),調(diào)用時(shí)由用戶設(shè)置select的等待時(shí)間,返回時(shí)表示timeout的剩余時(shí)間。

參數(shù)timeout的取值:

  • NULL/nullptr:select調(diào)用后進(jìn)行阻塞等待,直到被監(jiān)視的某個(gè)文件描述符上的事件就緒。
  • 0:select調(diào)用后進(jìn)行非阻塞等待,無(wú)論被監(jiān)視的文件描述符的事件是否就緒,select檢測(cè)后都會(huì)立即返回。
  • 特定的時(shí)間值:select調(diào)用后在指定的時(shí)間內(nèi)進(jìn)行阻塞等待,如果被監(jiān)視的文件描述符上一直沒(méi)有事件就緒,則在該時(shí)間后select進(jìn)行超時(shí)返回。

返回值說(shuō)明:

  • 如果函數(shù)調(diào)用成功,則返回有事件就緒的文件描述符個(gè)數(shù)。
  • 如果timeout時(shí)間耗盡,則返回0。
  • 如果函數(shù)調(diào)用失敗,則返回-1,同時(shí)錯(cuò)誤碼會(huì)被設(shè)置。

select調(diào)用失敗,錯(cuò)誤碼可能被設(shè)置為:

  • EBADF:文件描述符有無(wú)效的或者該文件已關(guān)閉
  • EINTR:此調(diào)用被信號(hào)所中斷
  • EINVAL:參數(shù)nfds為負(fù)值
  • ENOMEM:核心內(nèi)存不足

fd_set 結(jié)構(gòu)

fd_set 結(jié)構(gòu)與 sigset_t 結(jié)構(gòu)類似,fd_set 本質(zhì)也是一個(gè)位圖,用位圖中對(duì)應(yīng)的位來(lái)表示要監(jiān)聽(tīng)的文件描述符。

調(diào)用select函數(shù)之前就需要用fd_set結(jié)構(gòu)定義出對(duì)應(yīng)的文件描述符集,然后將需要監(jiān)視的文件描述符添加到文件描述符集當(dāng)中,這個(gè)添加的過(guò)程本質(zhì)就是在進(jìn)行位操作,但是這個(gè)位操作不需要用戶自己進(jìn)行,系統(tǒng)專門提供了一組專門的接口,用戶對(duì)fd_set位圖進(jìn)行各種操作。

timeval 結(jié)構(gòu)

傳入select函數(shù)的最后一個(gè)參數(shù)timeout,就是一個(gè)指向timeval結(jié)構(gòu)的指針,timeval結(jié)構(gòu)用于描述一段時(shí)間長(zhǎng)度,該結(jié)構(gòu)當(dāng)中包含兩個(gè)成員,其中tv_sec表示的是秒,tv_usec表示的是微妙。

3. socket就緒條件

讀就緒

  • socket內(nèi)核中,接收緩沖區(qū)中的字節(jié)數(shù),大于等于低水位標(biāo)記物SO_RCVLOWAT,此時(shí)可以無(wú)阻塞地讀取該文件描述符,并且返回值大于0。
  • socket TCP通信中,對(duì)端連接關(guān)閉,此時(shí)對(duì)該socket讀,則返回0。
  • 監(jiān)聽(tīng)的socket上有新的連接請(qǐng)求。
  • socket上有未處理的錯(cuò)誤。

寫就緒

  • socket內(nèi)核中,發(fā)送緩沖區(qū)中的可用字節(jié)數(shù),大于等于低水位標(biāo)記SO_SNDLOWAT,此時(shí)可以無(wú)阻塞地寫,并且返回值大于0。
  • socket的寫操作被關(guān)閉(close或者shutdown),對(duì)一個(gè)寫操作被關(guān)閉的socket進(jìn)行寫操作,會(huì)觸發(fā)SIGPIPE信號(hào)。
  • socket使用非阻塞connect連接成功或失敗之后。
  • socket上有未讀取的錯(cuò)誤。

異常就緒

  • socket上收到帶外數(shù)據(jù)

4. select工作流程

這里我們只介紹select處理讀取的操作。

如果我們要實(shí)現(xiàn)一個(gè)簡(jiǎn)單的select服務(wù)器,該服務(wù)器要做的就是讀取客戶端發(fā)來(lái)的數(shù)據(jù)并進(jìn)行打印,那么這個(gè)select服務(wù)器的工作流程應(yīng)該是這樣的:

  • 先初始化服務(wù)器,完成套接字的創(chuàng)建、綁定和監(jiān)聽(tīng)。
  • 定義一個(gè)fd_array數(shù)組用于保存監(jiān)聽(tīng)套接字和已經(jīng)與客戶端建立連接的套接字,剛開始時(shí)就將監(jiān)聽(tīng)套接字添加到fd_array數(shù)組當(dāng)中。
  • 然后服務(wù)器開始循環(huán)調(diào)用select函數(shù),檢測(cè)讀事件是否就緒,如果就緒則執(zhí)行對(duì)應(yīng)的操作。
  • 每次調(diào)用select函數(shù)之前,都需要定義一個(gè)讀文件描述符集readfds,并將fd_array當(dāng)中的文件描述符依此設(shè)置進(jìn)readfds當(dāng)中,表示讓select幫我們監(jiān)視這些文件描述符的讀事件是否就緒。
  • 當(dāng)select檢測(cè)到數(shù)據(jù)就緒時(shí)會(huì)讀事件就緒的文件描述符設(shè)置進(jìn)readfds當(dāng)中,此時(shí)我們能夠得知哪些文件描述符的讀事件就緒了,并對(duì)這些文件描述符進(jìn)行對(duì)應(yīng)的操作。
  • 如果讀事件就緒的就是監(jiān)聽(tīng)套接字,則調(diào)用accept函數(shù)從底層全連接隊(duì)列獲取已經(jīng)建立好的連接,并將該連接對(duì)應(yīng)的套接字添加到fd_array數(shù)組當(dāng)中。
  • 如果讀事件就緒的是與客戶端建議連接的套接字,則調(diào)用read函數(shù)讀取客戶端發(fā)來(lái)的數(shù)據(jù)并進(jìn)行打印輸出。
  • 當(dāng)然,服務(wù)器與客戶端建立連接的套接字讀事件就緒,也可能是因?yàn)榭蛻舳藢⑦B接關(guān)閉了,此時(shí)服務(wù)器應(yīng)該調(diào)用close關(guān)閉套接字,并將該套接字從fd_array中清除,因?yàn)橄乱淮尾恍枰俦O(jiān)視該文件描述符的讀事件了。

注意:

  • 因?yàn)閭魅氲膕elect函數(shù)的readfds、writefds和exceptfds都是輸入輸出型參數(shù),當(dāng)select函數(shù)返回時(shí)這些參數(shù)當(dāng)中的值已經(jīng)被修改了,因此每次調(diào)用seletct函數(shù)時(shí)都需要對(duì)其進(jìn)行重新設(shè)置,timeout也是類似的道理。
  • 因?yàn)槊看握{(diào)用select函數(shù)之前都需要對(duì)readfds進(jìn)行重新設(shè)置,所以需要定義一個(gè)fd_array數(shù)組保存與客戶端已經(jīng)建立的若干連接和監(jiān)聽(tīng)套接字,實(shí)際fd_array數(shù)組當(dāng)中的文件描述符就是需要讓select監(jiān)視讀事件的文件描述符。
  • 我們的select服務(wù)器只是讀取客戶端發(fā)來(lái)的數(shù)據(jù),因此只需讓select幫我們監(jiān)視特定文件描述符的讀事件,如果同時(shí)讓select幫我們監(jiān)視特定文件描述符的讀事件和寫事件,則需要分別定義readfds和writefds,并定義兩個(gè)數(shù)組分別保存需要被監(jiān)視讀事件和寫事件的文件描述符,便于每次調(diào)用select函數(shù)前對(duì)readfds和writefds進(jìn)行重新設(shè)置。
  • 服務(wù)器剛開始運(yùn)行時(shí),fd_array數(shù)組當(dāng)中只有監(jiān)聽(tīng)套接字,因此select第一次調(diào)用時(shí)只需要監(jiān)視監(jiān)聽(tīng)套接字的讀事件是否就緒,但每次調(diào)用accept獲取到新連接之后,都會(huì)將連接對(duì)應(yīng)的套接字添加到fd_array當(dāng)中,因此后續(xù)select調(diào)用時(shí)就需要監(jiān)視監(jiān)聽(tīng)套接字和若干連接套接字的讀事件是否就緒。
  • 由于調(diào)用select時(shí)還需要傳入被監(jiān)視的文件描述符中最大文件描述符值+1,因此每次在遍歷fd_array對(duì)readfds進(jìn)行重新設(shè)置時(shí),還需要記錄最大文件描述符的值。

5. select服務(wù)器

Socket類

我們編寫一個(gè)Socket類,對(duì)套接字相關(guān)的接口進(jìn)行一定程序的封裝,為了讓外部能夠直接調(diào)用Socket類當(dāng)中的函數(shù),我們將這些成員函數(shù)定義成靜態(tài)成員函數(shù)。

#pragma once

#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <cstring>
#include <cstdlib>

class Socket
{
public:
    // 創(chuàng)建套接字
    static int SocketCreate()
    {
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        if (sock < 0)
        {
            std::cerr << "socket error" << std::endl;
            exit(2);
        }

        // 設(shè)置端口復(fù)用
        int opt = 1;
        setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
        return sock;
    }

    // 綁定
    static void SocketBind(int sock, int 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;

        socklen_t len = sizeof(local);

        if (bind(sock, (struct sockaddr*)&local, len) < 0)
        {
            std::cerr << "bind error" << std::endl;
            exit(3);
        }
    }

    // 監(jiān)聽(tīng)
    static void SocketListen(int sock, int backlog)
    {
        if (listen(sock, backlog) < 0)
        {
            std::cerr << "listen error" << std::endl;
            exit(4);
        }
    }
};

SelectServer類

編寫SelectServer類,因?yàn)槲耶?dāng)前使用的是云服務(wù)器,所以編寫的select服務(wù)器在綁定時(shí)只需將IP地址設(shè)置為INADDR_ANY即可,所以類中只包含監(jiān)聽(tīng)套接字和端口號(hào)兩個(gè)成員變量即可。

  • 在構(gòu)造SelectServer對(duì)象時(shí),需要指明select服務(wù)器的端口號(hào),當(dāng)然也可以在初始化select服務(wù)器的時(shí)候指明。
  • 在初始化select服務(wù)器的時(shí)候需要調(diào)用Socket類當(dāng)中的函數(shù),依此進(jìn)行套接字的創(chuàng)建、綁定和監(jiān)聽(tīng)即可。
  • 在析構(gòu)函數(shù)中可以選擇調(diào)用close函數(shù)將監(jiān)聽(tīng)套接字進(jìn)行關(guān)閉,但實(shí)際也可以不進(jìn)行該動(dòng)作,因?yàn)榉?wù)器運(yùn)行后一般是不退出的。
#pragma once

#include "Socket.hpp"
#include <sys/select.h>

#define BACK_LOG 5

class SelectServer
{
public:
    SelectServer(int port)
        : _port(port)
    {}

    void InitSelectServer()
    {
        _listen_sock = Socket::SocketCreate();
        Socket::SocketBind(_listen_sock, _port);
        Socket::SocketListen(_listen_sock, BACK_LOG);
    }

    ~SelectServer()
    {
        if (_listen_sock >= 0) close(_listen_sock);
    }

private:
    int _listen_sock;
    int _port;
};

運(yùn)行服務(wù)器

服務(wù)器初始化完畢之后就可以周期性地執(zhí)行某種動(dòng)作了,而select服務(wù)器要做的就是不斷調(diào)用select函數(shù),當(dāng)事件就緒對(duì)應(yīng)執(zhí)行某種動(dòng)作即可。

  • 首先,在select服務(wù)器開始死循環(huán)調(diào)用select函數(shù)之前,需要先定義一個(gè)fd_array數(shù)組,先把數(shù)組中所有的位置初始化為無(wú)效,并將監(jiān)聽(tīng)套接字添加到該數(shù)組當(dāng)中,fd_array數(shù)組當(dāng)中保存的就是需要被select監(jiān)視讀事件是否就緒的文件描述符。
  • 此后,select服務(wù)器就不斷調(diào)用select函數(shù)監(jiān)視讀事件是否就緒,每次調(diào)用select函數(shù)之前都需要重新設(shè)置readfds,具體設(shè)置過(guò)程就是遍歷fd_array數(shù)組,將fd_array數(shù)組當(dāng)中的文件描述符添加到readfds當(dāng)中,并同時(shí)記錄最大的文件描述符maxfd。
  • 當(dāng)select函數(shù)返回后,如果返回值為0,則說(shuō)明timeout時(shí)間耗盡,此時(shí)直接準(zhǔn)備下一次select調(diào)用即可。如果select的返回值為-1,則說(shuō)明select調(diào)用失敗,此時(shí)也讓服務(wù)器準(zhǔn)備下一次select調(diào)用,但實(shí)際應(yīng)該進(jìn)一步判斷錯(cuò)誤碼,根據(jù)錯(cuò)誤碼來(lái)判斷是否應(yīng)該繼續(xù)調(diào)用select函數(shù)。
  • 如果select函數(shù)的返回值大于0,則說(shuō)明select函數(shù)調(diào)用成功,此時(shí)已經(jīng)有文件描述符的讀事件就緒,接下來(lái)就應(yīng)該對(duì)就緒事件進(jìn)行處理。
#pragma once

#include "Socket.hpp"
#include <sys/select.h>

#define BACK_LOG 5
#define NUM 1024
#define DFL_FD - 1

class SelectServer
{
public:
    SelectServer(int port)
        : _port(port)
    {}

    void InitSelectServer()
    {
        _listen_sock = Socket::SocketCreate();
        Socket::SocketBind(_listen_sock, _port);
        Socket::SocketListen(_listen_sock, BACK_LOG);
    }

    ~SelectServer()
    {
        if (_listen_sock >= 0) close(_listen_sock);
    }

    void Run()
    {
        fd_set readfds; // 創(chuàng)建讀文件描述符集
        int fd_array[NUM]; // 保存需要被監(jiān)視讀事件是否就緒的文件描述符
        ClearFdArray(fd_array, NUM, DFL_FD); // 將數(shù)組中的所有位置設(shè)置為無(wú)效
        fd_array[0] = _listen_sock; // 將監(jiān)聽(tīng)套接字添加到fd_array數(shù)組中的第0個(gè)位置
        while (1)
        {
            FD_ZERO(&readfds); // 清空readfds
            // 將fd_array數(shù)組當(dāng)中的文件描述符添加到readfds中,并記錄最大的文件描述符
            int maxfd = DFL_FD;
            for (int i = 0; i < NUM; ++i)
            {
                if (fd_array[i] == DFL_FD) continue; // 跳過(guò)無(wú)效的位置
                FD_SET(fd_array[i], &readfds);  // 將有效位置的文件描述符添加到readfds中
                if (fd_array[i] > maxfd) maxfd = fd_array[i]; // 更新最大文件描述符
            }

            switch (select(maxfd + 1, &readfds, nullptr, nullptr, nullptr))
            {
                case 0:
                    std::cout << "timeout..." << std::endl;
                    break;
                case -1:
                    std::cerr << "select error" << std::endl;
                    break;
                default:
                    std::cout << "有事件發(fā)生..." << std::endl;
                    break; 
            }
        }

    }

private:
    void ClearFdArray(int fd_array[], int num, int default_fd)
    {
        for (int i = 0; i < num; ++i) fd_array[i] = default_fd;
    }

    int _listen_sock;
    int _port;
};

啟動(dòng)服務(wù)器

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

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << "SelectServer" << " port" << std::endl;
        exit(1);
    }
    int port = atoi(argv[1]);

    SelectServer* svr = new SelectServer(port);
    svr->InitSelectServer();
    svr->Run();

    return 0;
}

由于當(dāng)前服務(wù)器調(diào)用select函數(shù)時(shí)直接將timeout設(shè)置為了nullptr,因此select函數(shù)調(diào)用后會(huì)進(jìn)行阻塞等待。

而服務(wù)器在第一次調(diào)用select函數(shù)時(shí)只讓select函數(shù)監(jiān)視監(jiān)聽(tīng)套接字的讀事件,所以運(yùn)行服務(wù)器之后如果沒(méi)有客戶端發(fā)來(lái)連接請(qǐng)求,那么讀事件就不會(huì)就緒,而服務(wù)器會(huì)一直在第一次調(diào)用的select函數(shù)中進(jìn)行阻塞等待。

當(dāng)我們借助telnet工具向select服務(wù)器發(fā)起連接請(qǐng)求之后,select函數(shù)就會(huì)立馬檢測(cè)到監(jiān)聽(tīng)套接字的讀事件就緒,此時(shí)select函數(shù)便會(huì)返回成功,并將我們?cè)O(shè)置的提示語(yǔ)句進(jìn)行打印輸出,因?yàn)楫?dāng)前程序沒(méi)有對(duì)就緒事件進(jìn)行處理,此后每次select函數(shù)一調(diào)用就會(huì)檢測(cè)到讀事件就緒成功返回,因此屏幕不但打印輸出提示語(yǔ)句。

如果服務(wù)器在調(diào)用select函數(shù)時(shí)將timeout的值設(shè)置為0,那么select函數(shù)調(diào)用后就會(huì)進(jìn)行非阻塞等待,無(wú)論被監(jiān)視的文件描述符上的事件是否就緒,select檢測(cè)后都會(huì)立即返回。

此時(shí)如果select監(jiān)視的文件描述符上有事件就緒,那么select函數(shù)的返回值就是大于0的,如果select函數(shù)監(jiān)視的文件描述符上沒(méi)有事件就緒,那么select的返回值就是小于0的,這里也就不進(jìn)行演示了。

事件處理

當(dāng)select檢測(cè)到右文件描述符的讀事件就緒并成功返回后,接下來(lái)就應(yīng)該對(duì)就緒事件進(jìn)行處理了,這里編寫一個(gè)HandleEvent函數(shù),當(dāng)讀事件就緒之后就調(diào)用該函數(shù)進(jìn)行事件處理。

  • 在進(jìn)行事件處理時(shí)需要遍歷fd_array數(shù)組當(dāng)中的文件描述符,以此判斷各個(gè)文件描述符對(duì)應(yīng)的讀事件是否就緒,如果就緒則需要進(jìn)行事件處理。
  • 當(dāng)一個(gè)文件描述符的讀事件就緒之后,還需要進(jìn)一步判斷該文件描述符是否是監(jiān)聽(tīng)套接字,如果是監(jiān)聽(tīng)套接字的讀事件就緒,那么就應(yīng)該調(diào)用accept函數(shù)將底層的連接獲取上來(lái)。但是只調(diào)用accept函數(shù)將連接獲取上來(lái)還不夠,為了下一次調(diào)用select函數(shù)時(shí)能夠讓select幫我們監(jiān)視新連接的事件是否就緒,在連接獲取上來(lái)后還應(yīng)該將連接對(duì)應(yīng)的文件描述符添加到fd_array數(shù)組當(dāng)中,這樣在下一次調(diào)用select函數(shù)前對(duì)readfds重新設(shè)置時(shí)就能將該文件描述符添加進(jìn)去了。
  • 如果是客戶端建立的連接對(duì)應(yīng)的讀事件就緒,那么就應(yīng)該調(diào)用read函數(shù)讀取客戶端發(fā)來(lái)的連接,如果讀取成功則將讀到的數(shù)據(jù)在服務(wù)端進(jìn)行打印。如果調(diào)用read函數(shù)讀取失敗或者客戶端關(guān)閉了連接,那么select服務(wù)器也應(yīng)該調(diào)用close函數(shù)關(guān)閉對(duì)應(yīng)的連接,但此時(shí)只關(guān)閉連接也是不夠的,還應(yīng)該將該連接對(duì)應(yīng)的文件描述符從fd_array數(shù)組當(dāng)中清除,否則后續(xù)調(diào)用的select函數(shù)還會(huì)幫我們監(jiān)視該連接的讀事件是否就緒,但實(shí)際已經(jīng)不需要了。
    void HandleEvent(const fd_set& readfds, int fd_array[], int num)
    {
        for (int i = 0; i < num; ++i)
        {
            // 跳過(guò)無(wú)效位置
            if (fd_array[i] == DFL_FD) continue;
                
            // 連接事件就緒
            if (fd_array[i] == _listen_sock && FD_ISSET(fd_array[i], &readfds))
            {
                // 獲取連接
                struct sockaddr_in peer;
                memset(&peer, 0, sizeof(peer));
                socklen_t len = sizeof(peer);
                int sock = accept(_listen_sock, (struct sockaddr*)&peer, &len);
                if (sock < 0)
                {
                    std::cerr << "accept error" << std::endl;
                    continue;
                }
                std::string peer_ip = inet_ntoa(peer.sin_addr);
                int peer_port = ntohs(peer.sin_port);
                std::cout << "get a new link[" << peer_ip << ":" << peer_port << "]" << std::endl;
                // 將獲取到的文件描述符添加到fd_array中
                if (!SetFdArray(fd_array, num, sock))
                {
                    // 如果添加失敗,關(guān)閉文件描述符
                    close(sock);
                    std::cout << "select server is full, close fd: " << sock << std::endl;
                }
            }
        }
    }

private:
    bool SetFdArray(int fd_array[], int num, int fd)
    {
        for (int i = 0; i < num; ++i)
        {
            if (fd_array[i] == DFL_FD) 
            {
                fd_array[i] = fd;
                return true;
            }
        }
        return false;
    }

添加文件描述符到fd_array數(shù)組中,本質(zhì)就是遍歷fd_array數(shù)組,找到一個(gè)沒(méi)有被使用的位置將該文件描述符添加進(jìn)去即可。

但有可能fd_array數(shù)組中全部的位置都已經(jīng)被占用了,那么文件描述符就會(huì)添加失敗,此時(shí)就只能將剛剛獲取上來(lái)的連接對(duì)應(yīng)的套接字進(jìn)行關(guān)閉,因?yàn)榇藭r(shí)服務(wù)器是沒(méi)有能力處理這個(gè)連接的。

該select服務(wù)器存在的一些問(wèn)題

  • 服務(wù)器沒(méi)有對(duì)客戶端進(jìn)行響應(yīng),select服務(wù)器如果要向客戶端發(fā)送數(shù)據(jù),不能直接調(diào)用write函數(shù),因?yàn)檎{(diào)用write函數(shù)時(shí)實(shí)際也為了“等”和“拷貝”兩步,我們也應(yīng)該將“等”的這個(gè)過(guò)程交給select函數(shù),因此在每次調(diào)用select函數(shù)之前,除了需要重新設(shè)置readfds之外還需要重新設(shè)置writefds,并且還需要一個(gè)數(shù)組來(lái)保存需要被監(jiān)視事件是否就緒的文件描述符,當(dāng)某一文件描述符的寫事件就緒時(shí)我們才能夠調(diào)用write函數(shù)向客戶端發(fā)送數(shù)據(jù)。
  • 沒(méi)有定制協(xié)議,代碼中讀取數(shù)據(jù)時(shí)并沒(méi)有按照某種規(guī)則進(jìn)行讀取,此時(shí)就可能造成粘包問(wèn)題。比如HTTP協(xié)議規(guī)定在讀取底層數(shù)據(jù)時(shí)讀取到空行就表明讀完了一個(gè)HTTP報(bào)頭,此時(shí)再根據(jù)HTTP報(bào)頭當(dāng)中的Content-Length屬性得知正文的長(zhǎng)度,最終就能讀取到一個(gè)完整的HTTP報(bào)文,HTTP協(xié)議通過(guò)這種方式就避免了粘包問(wèn)題。
  • 沒(méi)有對(duì)應(yīng)的輸入輸出緩沖區(qū),代碼中直接將讀取的數(shù)據(jù)存儲(chǔ)到了字符數(shù)組buffer中,這是不嚴(yán)謹(jǐn)?shù)?,因?yàn)楸镜財(cái)?shù)據(jù)讀取可能并沒(méi)有讀取到一個(gè)完整的報(bào)文,此時(shí)服務(wù)器就不能進(jìn)行數(shù)據(jù)的分析處理,一個(gè)將讀取到的數(shù)據(jù)存儲(chǔ)到一個(gè)輸入緩沖區(qū)中,當(dāng)讀取到一個(gè)完整的報(bào)文之后再讓服務(wù)器進(jìn)行處理。此外,如果服務(wù)器能夠?qū)蛻舳诉M(jìn)行響應(yīng),那么服務(wù)器的響應(yīng)數(shù)據(jù)也不應(yīng)該直接調(diào)用write函數(shù)發(fā)送給客戶端,應(yīng)該先存儲(chǔ)到一個(gè)輸出緩沖區(qū)當(dāng)中,因?yàn)轫憫?yīng)數(shù)據(jù)可能很龐大,無(wú)法一次發(fā)送完畢,可能需要進(jìn)行分批發(fā)送。

6. select的優(yōu)缺點(diǎn)

select的優(yōu)點(diǎn)

  • 可以同時(shí)等待多個(gè)文件描述符,并且只負(fù)責(zé)等待,實(shí)際的IO操作由accept、read、write等接口完成,這些接口在進(jìn)行IO操作時(shí)不會(huì)被阻塞。
  • select同時(shí)等待多個(gè)文件描述符,因此可以將“讀”的時(shí)間重疊,提高了IO的效率。

當(dāng)然,這也是所有多路轉(zhuǎn)接接口的優(yōu)點(diǎn)。

select的缺點(diǎn)

  • 每次調(diào)用select,都需要手動(dòng)設(shè)置fd集合,從接口使用角度來(lái)看并不方便。
  • 每次調(diào)用select,都需要把fd集合從用戶態(tài)拷貝到內(nèi)核態(tài),這個(gè)開銷在fd很多時(shí)會(huì)很大。
  • 同時(shí)每次調(diào)用select時(shí)都需要在內(nèi)核遍歷傳遞進(jìn)來(lái)的所有fd,這個(gè)開銷在fd很多時(shí)也很大。
  • select可監(jiān)控的文件描述符數(shù)量太少。

select可監(jiān)控的文件描述符有1024個(gè),除去其中的一個(gè)監(jiān)聽(tīng)套接字,那么它最多只能連接1023個(gè)客戶端。

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 詳解Linux 主機(jī)網(wǎng)絡(luò)接入配置

    詳解Linux 主機(jī)網(wǎng)絡(luò)接入配置

    這篇文章主要介紹了詳解Linux 主機(jī)網(wǎng)絡(luò)接入配置的相關(guān)資料,希望通過(guò)本文能幫助到大家,讓大家實(shí)現(xiàn)網(wǎng)絡(luò)接入配置的功能,需要的朋友可以參考下
    2017-10-10
  • Linux安裝MongoDB啟動(dòng)及常見(jiàn)問(wèn)題解決

    Linux安裝MongoDB啟動(dòng)及常見(jiàn)問(wèn)題解決

    這篇文章主要介紹了Linux安裝MongoDB啟動(dòng)及問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-08-08
  • Linux 為特定的用戶或用戶組啟用或禁用 SSH的方法

    Linux 為特定的用戶或用戶組啟用或禁用 SSH的方法

    這篇文章主要介紹了如何在 Linux 上為特定的用戶或用戶組啟用或禁用 SSH,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-03-03
  • VirtualBox安裝Centos6.8出現(xiàn)E_INVALIDARG(0x80070057)的解決方法

    VirtualBox安裝Centos6.8出現(xiàn)E_INVALIDARG(0x80070057)的解決方法

    這篇文章主要為大家詳細(xì)介紹了VirtualBox安裝Centos6.8出現(xiàn)E_INVALIDARG(0x80070057)的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • linux下獲取當(dāng)前時(shí)間的相關(guān)函數(shù)

    linux下獲取當(dāng)前時(shí)間的相關(guān)函數(shù)

    這篇文章主要介紹了linux下獲取當(dāng)前時(shí)間的相關(guān)函數(shù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,
    2023-09-09
  • IO多路復(fù)用之epoll全面總結(jié)(必看篇)

    IO多路復(fù)用之epoll全面總結(jié)(必看篇)

    下面小編就為大家?guī)?lái)一篇IO多路復(fù)用之epoll全面總結(jié)(必看篇)。小編覺(jué)得挺不錯(cuò)的。現(xiàn)在就分享給大家。也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-12-12
  • linux下cat命令連接文件并打印到標(biāo)準(zhǔn)輸出設(shè)備上

    linux下cat命令連接文件并打印到標(biāo)準(zhǔn)輸出設(shè)備上

    這篇文章主要給大家介紹了關(guān)于在linux下cat命令連接文件并打印到標(biāo)準(zhǔn)輸出設(shè)備上的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。
    2017-07-07
  • ubuntu 16.04系統(tǒng)完美解決pip不能升級(jí)的問(wèn)題

    ubuntu 16.04系統(tǒng)完美解決pip不能升級(jí)的問(wèn)題

    這篇文章主要介紹了ubuntu 16.04系統(tǒng)完美解決pip不能升級(jí)的問(wèn)題 ,本文圖文并茂給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2018-04-04
  • Apache?Doris?中Compaction問(wèn)題分析和典型案例分析

    Apache?Doris?中Compaction問(wèn)題分析和典型案例分析

    這篇文章主要介紹了Apache?Doris?中Compaction問(wèn)題分析和典型案例,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2024-08-08
  • 在 Linux 上使用 Multitail命令的教程

    在 Linux 上使用 Multitail命令的教程

    MultiTail是一個(gè)開源的ncurses的實(shí)用工具,可用于在一個(gè)窗口或單一外殼,顯示實(shí)時(shí)一樣的尾巴命令,該命令拆分控制臺(tái)為更多子窗口的日志文件的最后幾行。這篇文章主要介紹了在 Linux 上使用 Multitail命令的教程,需要的朋友可以參考下
    2019-12-12

最新評(píng)論