国产亚洲精品福利在线无卡一,国产精久久一区二区三区,亚洲精品无码国模,精品久久久久久无码专区不卡

當(dāng)前位置: 首頁 > news >正文

海爾商務(wù)網(wǎng)站建設(shè)企業(yè)網(wǎng)站是什么

海爾商務(wù)網(wǎng)站建設(shè),企業(yè)網(wǎng)站是什么,太原做網(wǎng)站哪家公司好,遵義官網(wǎng)網(wǎng)站建設(shè)目錄 IO的基本概念 什么是高效的IO? 五種IO模型 阻塞IO 非阻塞IO 信號驅(qū)動IO IO多路轉(zhuǎn)接 異步IO 同步通信VS異步通信(synchronous communication / asynchronous communication) 同步通信VS同步與互斥 阻塞VS非阻塞 其他高級IO …


目錄

IO的基本概念

什么是高效的IO?

五種IO模型

阻塞IO

非阻塞IO

信號驅(qū)動IO

IO多路轉(zhuǎn)接

異步IO

同步通信VS異步通信(synchronous communication / asynchronous communication)

同步通信VS同步與互斥

阻塞VS非阻塞

其他高級IO

阻塞IO

非阻塞IO?

實現(xiàn)函數(shù) SetNoBlock

以非阻塞輪詢釋放讀取標(biāo)準(zhǔn)輸入

?I/O多路轉(zhuǎn)接之select

初識?select?

關(guān)于 fd_set 結(jié)構(gòu)

?編輯?關(guān)于 timeval 結(jié)構(gòu)

socket 就緒條件?

socket 基本工作流程

select 服務(wù)器

selectServer 類

timeout 時間測試

事件處理?

select 的優(yōu)缺點

select可監(jiān)控的fd個數(shù)


IO的基本概念

?I/O(Input / output)就是輸入和輸出,在馮諾依曼體系中,將數(shù)據(jù)從輸入設(shè)備拷貝到內(nèi)存叫做輸入,將數(shù)據(jù)從內(nèi)存拷貝到輸出設(shè)備叫做輸出。

馮·諾依曼體系結(jié)構(gòu) -- 理解_waves_K的博客-CSDN博客

· 對文件進(jìn)行的讀寫操作本質(zhì)就是一種IO,文件IO對應(yīng)的外設(shè)就是磁盤。
· 對網(wǎng)絡(luò)進(jìn)行的讀寫操作本質(zhì)也就是一種IO,網(wǎng)絡(luò)IO對應(yīng)的外設(shè)就是網(wǎng)卡。

什么是高效的IO?

IO主要分為兩個步驟:

· 第一步是等,就是等待IO條件的就緒。
· 第二步就是拷貝,就是IO條件就緒后將數(shù)據(jù)拷貝到內(nèi)存或者外設(shè)中。

所以 IO = 等 + 數(shù)據(jù)拷貝,但是實際中 “等” 消耗的時間往往比 “拷貝” 消耗的時間多,因此讓 IO 變得更高效,就是要減少 “等” 的時間。

五種IO模型

阻塞IO

阻塞IO就是內(nèi)核將數(shù)據(jù)準(zhǔn)備好之前,系統(tǒng)調(diào)用會一直等待,所有套接字,默認(rèn)都是阻塞方式,也是最常見的IO模型:

?· 在 recvfrom 函數(shù)等待數(shù)據(jù)就緒期間,在用戶看來該進(jìn)程或者線程被阻塞住了,本質(zhì)是操作系統(tǒng),將該進(jìn)程或者線程的狀態(tài)設(shè)置為了某種非 R 狀態(tài),然后放入等待隊列當(dāng)中,當(dāng)數(shù)據(jù)就緒后,操作系統(tǒng)再將其從等待隊列中喚醒,然后該進(jìn)程或者線程再將數(shù)據(jù)從內(nèi)核拷貝到用戶空間。

· 阻塞IO,在 “等” + “數(shù)據(jù)拷貝” 期間都不會返回,在用戶看來就像是阻塞住了。

非阻塞IO

如果內(nèi)核還未將數(shù)據(jù)準(zhǔn)備好,系統(tǒng)調(diào)用仍然會直接返回, 并且返回EWOULDBLOCK錯誤碼:

非阻塞IO往往需要程序猿自己循環(huán)反復(fù)嘗試讀寫文件描述符,這個過程稱為輪詢,這對CPU來說是較大的浪費,一般只有特定場景下使用。?

· 每次調(diào)用 recvfrom 函數(shù)讀取數(shù)據(jù)時,就算底層數(shù)據(jù)沒有就緒,recvfrom 函數(shù)也會立馬返回錯誤信息,后續(xù)還需要繼續(xù)不斷調(diào)用 recvfrom 函數(shù),直到底層有數(shù)據(jù)就緒,在用戶看來該進(jìn)程或者線程就沒有被阻塞住,稱為非阻塞IO。

阻塞IO 和 非阻塞IO 區(qū)別:

阻塞IO:當(dāng)數(shù)據(jù)沒有就緒時,后續(xù)檢測數(shù)據(jù)是否就緒工作是操作系統(tǒng)發(fā)起的。

非阻塞IO:當(dāng)數(shù)據(jù)沒有就緒時,后續(xù)檢測數(shù)據(jù)是否就緒工作是用戶發(fā)起的。

信號驅(qū)動IO

內(nèi)核將數(shù)據(jù)準(zhǔn)備好的時候,使用SIGIO信號通知應(yīng)用程序進(jìn)行IO操作。

當(dāng)?shù)讓訑?shù)據(jù)就緒的時候,會向當(dāng)前進(jìn)程或者線程發(fā)送SIGIO信號,因此可以通過signal或者sigaction函數(shù)將SIGIO的信號處理程序自定義為需要進(jìn)行的IO操作,當(dāng)?shù)讓訑?shù)據(jù)就緒時就會自動執(zhí)行對應(yīng)的IO操作。

信號的產(chǎn)生是異步的,但信號驅(qū)動IO是同步IO的一種:

· 信號的產(chǎn)生是異步的,因為信號在任何時刻都可能會產(chǎn)生。
· 信號驅(qū)動IO是同步IO的一種,當(dāng)?shù)讓訑?shù)據(jù)就緒時,當(dāng)前進(jìn)程或者線程需要停下來正在做的事情,轉(zhuǎn)而進(jìn)行數(shù)據(jù)的拷貝操作,因此當(dāng)前進(jìn)程或者線程仍然需要參加IO的過程。

· 一個IO過程是同步還是異步,看當(dāng)前進(jìn)程或者線程是否參與到IO的過程中,參與了是同步IO,沒有參與就是異步IO。

IO多路轉(zhuǎn)接

也叫做IO多路復(fù)用,最核心在與能夠同時等待多個文件描述符的就緒狀態(tài)。

?IO多路轉(zhuǎn)接思想:

· 使用 recvfrom 等接口需要進(jìn)行等,可以將所有的 “等” 的工作交給多路轉(zhuǎn)接接口(select,poll,epoll)
· 多路轉(zhuǎn)接接口一次 “等” 多個文件描述符,因此能夠?qū)?“等” 的時間進(jìn)行重疊,當(dāng)數(shù)據(jù)就緒后再調(diào)用對應(yīng)的 recvfrom 等函數(shù)進(jìn)行數(shù)據(jù)的拷貝,此時這些函數(shù)就能直接進(jìn)行拷貝,而不需要進(jìn)行 “等” 操作。

異步IO

由內(nèi)核在數(shù)據(jù)拷貝完成時,通知應(yīng)用程序(而信號驅(qū)動是告訴應(yīng)用程序何時可以開始拷貝數(shù)據(jù))。

· 進(jìn)行異步IO需要調(diào)用一些異步IO的接口,異步IO接口調(diào)用后立馬進(jìn)行返回。
· 當(dāng)IO完成后,操作系統(tǒng)會通知應(yīng)用程序,因此進(jìn)行異步IO的進(jìn)程或者線程并不參與IO的所有細(xì)節(jié)。


同步通信VS異步通信(synchronous communication / asynchronous communication)

同步和異步關(guān)注的是消息的通信機(jī)制

· 所謂同步:就是在發(fā)出一個調(diào)用時,在沒有得到結(jié)果之前,該調(diào)用就不返回,但是一旦調(diào)用返回,就得到了返回值了,換句話來說,就是由調(diào)用者主動等待這個調(diào)用的結(jié)果。

· 異步則是相反:調(diào)用在發(fā)出之后,這個調(diào)用就直接返回了,所以沒有返回結(jié)果;換句話來說,當(dāng)一個異步過程調(diào)用發(fā)出后,調(diào)用者不會立即得到結(jié)果,而是最調(diào)用發(fā)出后,被調(diào)用者通過狀態(tài),通知來通知調(diào)用者,或通過回調(diào)函數(shù)處理這個調(diào)用。

同步通信VS同步與互斥

· 進(jìn)程 / 線程同步:在保證數(shù)據(jù)安全的前提下,讓進(jìn)程 / 線程 能夠按照某種特定的順序訪問臨界資源,從而有效避免饑餓問題,談?wù)摰氖沁M(jìn)程 / 線程間的一種工作關(guān)系。
· 而同步 IO:進(jìn)程 / 線程 與操作系統(tǒng)之間的關(guān)系,談?wù)摰氖?進(jìn)程 / 線程 是否需要主動參與IO過程。

阻塞VS非阻塞

阻塞和非阻塞關(guān)注的是程序在等待調(diào)用結(jié)果(信息,返回值)的狀態(tài):

· 阻塞調(diào)用是指調(diào)用結(jié)果返回之前,當(dāng)前線程會被掛起,調(diào)用線程只有在得到結(jié)果之后才會返回。
· 非阻塞調(diào)用指在不能立即得到結(jié)果之前,該調(diào)用不會阻塞當(dāng)前線程。

其他高級IO

非阻塞IO,記錄鎖,系統(tǒng)V流機(jī)制,I/O多路轉(zhuǎn)接(I/O多路復(fù)用),readv和writev函數(shù)以及存儲映射IO(mmap)。

阻塞IO

使用read函數(shù)從標(biāo)準(zhǔn)輸入當(dāng)中讀取數(shù)據(jù):

#include <iostream>
#include <unistd.h>
int main()
{// 阻塞IOchar buffer[1024];while (true){printf("Please#: ");fflush(stdout);ssize_t n = read(0, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n - 1] = 0;std::cout << "echo#: " << buffer << std::endl;}else if (n == 0){std::cout << "me too quit!" << std::endl;break;}else{std::cout << "read error" << std::endl;break;}}
}

?將程序運行,如果不進(jìn)行輸入操作,此時該進(jìn)程就會阻塞住,根本原因是底層數(shù)據(jù)沒有進(jìn)行就緒(無數(shù)據(jù)),此時read函數(shù)在進(jìn)行阻塞等待:

?此時進(jìn)行輸入操作,read函數(shù)檢測到底層數(shù)據(jù)就緒時,立馬將數(shù)據(jù)讀取從內(nèi)核拷貝到buffer數(shù)組中,進(jìn)而將數(shù)據(jù)進(jìn)行輸出:

非阻塞IO?

打開文件時默認(rèn)都是以阻塞的方式打開的,如果要以非阻塞的方式打開某個文件,需要在使用open函數(shù)打開文件時,需要攜帶 O_NONBLOCK,或者 O_NDELAY 選項,此時就能夠以非阻塞的方式打開文件。

?如果要將已經(jīng)打開的某個文件或者套接字設(shè)置為非阻塞,需要用到 fcntl 函數(shù)。

? fd:已經(jīng)打開的文件描述符

? cmd:需要進(jìn)行的操作

? ... :可變參數(shù),傳入的cmd不同,后面追加的參數(shù)也不同。


fcntl 函數(shù)有5種功能:

? 復(fù)制一個現(xiàn)有的描述符(cmd = F_DUPFD)

? 獲得 / 設(shè)置文件描述符標(biāo)記(cmd = F_GETFD 或 F_SETFD)

? 獲得 / 設(shè)置文件狀態(tài)標(biāo)記(cmd = F_GETFL 或 F_SETFL)

? 獲得 / 設(shè)置異步 I/O 所有權(quán)(cmd = F_GETOWN 或 F_SETOWN)

? 獲得 / 設(shè)置記錄鎖(cmd = F_GETLK,F_SETLK 或 F_SETLKW)

如果函數(shù)調(diào)用成功,返回值取決于具體的進(jìn)行對操作;調(diào)用失敗,返回 -1,錯誤碼被設(shè)置。

實現(xiàn)函數(shù) SetNoBlock

基于 fcntl,實現(xiàn)一個SetNoBlock函數(shù),將文件描述符設(shè)置為非阻塞:

void SetNonBlock(int fd)
{// 將fd設(shè)置為非阻塞int fl = fcntl(fd, F_GETFL); // 獲取一下fd的狀態(tài)(GETFD 獲取值)if (fl < 0){std::cerr << "fcntl: " << strerror(errno) << std::endl;return;}fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}

? 使用 F_GETFL 將當(dāng)前的文件描述符的屬性取出來(這是一個位圖)

? 再使用 F_SETFL 將文件描述符設(shè)置回去,設(shè)置回去的同時,加上一個 O_NONBLOCK參數(shù)。

以非阻塞輪詢釋放讀取標(biāo)準(zhǔn)輸入

在調(diào)用read函數(shù)之前,先調(diào)用SetNonBlock函數(shù)將0號文件描述符設(shè)置為非阻塞:

#include "util.hpp"
#include <cstdlib>
#include <vector>
#include <functional>
using namespace std;using func_t = function<void()>;#define INIT(v)                  \do                           \{                            \v.push_back(printlog);   \v.push_back(installlog); \} while (0);#define EXITLOG(v)              \do                          \{                           \for (const auto &e : v) \{                       \e();                \}                       \} while (0);void printlog()
{std::cout << "this is a print log!" << std::endl;
}void installlog()
{std::cout << "this is a install log!" << std::endl;
}int main()
{// 非阻塞式IO  在非阻塞期間能做自己的事情SetNonBlock(0); // 將fd設(shè)置為非阻塞vector<func_t> vf;INIT(vf);// 阻塞IOchar buffer[1024];while (true){// printf("Please#: ");// fflush(stdout);ssize_t n = read(0, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n - 1] = 0;std::cout << "echo#: " << buffer << std::endl;}else if (n == 0){std::cout << "me too quit!" << std::endl;break;}else{// 讀取出現(xiàn)錯誤  顯示資源未就緒 錯誤?根據(jù)錯誤碼分析是否是真的read錯誤,還是無資源// std::cout << "strerror: " << strerror(errno) << "errno: " << errno << std::endl;// break;if(errno == EAGAIN){//非阻塞cout << "此時只是無數(shù)據(jù),沒有錯誤!" << endl;EXITLOG(vf);}else if(errno == EINTR){cout<<"接受到了阻斷信號,也不是read錯誤!"<<endl;continue;}else{cout << "strerror: " << strerror(errno) << "errno: " << errno << endl;break;}}sleep(1);}return 0;
}

?此時,read 函數(shù)以非阻塞方式讀取標(biāo)準(zhǔn)輸入時,底層數(shù)據(jù)不就緒,read 函數(shù)就會立即返回(以錯誤的形式),錯誤碼被設(shè)置為 EAGAIN 或 EWOULDBLOCK:

?在以非阻塞方式進(jìn)行讀取時,在read 函數(shù)返回值是 -1 的情況下,還需要根據(jù)錯誤碼進(jìn)一步判斷,如果錯誤碼是 EAGAIN 或者 EWOULDBLOCK,說明此時底層數(shù)據(jù)還沒有就緒,還需要進(jìn)行輪詢檢測,read 函數(shù)在讀取到數(shù)據(jù)之前可能會被其他信號中斷,也會以錯誤的形式返回,錯誤碼被設(shè)置為 EINTR,此時應(yīng)該重新執(zhí)行 read 函數(shù)進(jìn)行數(shù)據(jù)讀取:

?運行程序,此時沒有輸入數(shù)據(jù),程序就會不斷調(diào)用 read 函數(shù)檢測底層數(shù)據(jù)是否就緒:

當(dāng)進(jìn)行輸入操作后,read 函數(shù)就會輪詢檢測,read 函數(shù)立馬將數(shù)據(jù)讀取從內(nèi)核拷貝到 buffer 數(shù)組中,并進(jìn)行輸出:


?I/O多路轉(zhuǎn)接之select

初識?select?

select 的函數(shù)原型:

?系統(tǒng)提供 select 函數(shù)來實現(xiàn)多路復(fù)用輸入 / 輸出模型

? select 系統(tǒng)調(diào)用是用來讓程序監(jiān)視多個文件描述符的狀態(tài)變化的。

? 程序會停在 select 這里等待,直到被監(jiān)視在文件描述符有一個或者多個發(fā)生了狀態(tài)改變。

?參數(shù)介紹:

? nfds:需要監(jiān)視的文件描述符當(dāng)中,最大的文件描述符值 +1。

? readfds:輸入輸出型參數(shù),調(diào)用時用戶告知內(nèi)核需要檢視哪些文件描述符的讀事件是否就緒,返回時內(nèi)核告知用戶哪些文件描述符的讀事件已經(jīng)就緒。

? writefds:輸入輸出型參數(shù),調(diào)用時用戶告知內(nèi)核需要檢視哪些文件描述符的讀事件是否就緒,返回時內(nèi)核告知用戶哪些文件描述符的寫事件已經(jīng)就緒。

? exceptfds:輸入輸出型參數(shù),調(diào)用時用戶告知內(nèi)核需要檢視哪些文件描述符的讀事件是否就緒,返回時內(nèi)核告知用戶哪些文件描述符的異常事件已經(jīng)就緒。

? timeout:輸入輸出型參數(shù),調(diào)用時由用戶設(shè)置 select 的等待時間,返回時表示 timeout 的剩余時間。

參數(shù) timeout 的取值:

? NULL:則表示 select 函數(shù)沒有 timeout,select 將一直被阻塞,直到某個文件描述符上發(fā)生了事件

? 0:僅檢測描述符集合的狀態(tài),然后立即返回,并不等待外部時間的發(fā)生。

? 特定的時間值:如果指定的時間段里面沒有事件發(fā)生,select 將超時返回。

返回值說明:

? 如果函數(shù)調(diào)用成功,則返回有事件就緒的文件描述符個數(shù),調(diào)用失敗,返回 -1,錯誤碼被設(shè)置。

?? timeout時間耗盡,則返回0。

關(guān)于 fd_set 結(jié)構(gòu)

?本質(zhì)是一個位圖結(jié)構(gòu),用位圖中對應(yīng)的位置來表示要監(jiān)視的文件描述符:

調(diào)用 select 函數(shù)之前,需要定義fd_set對應(yīng)的文件描述符集,然后將需要監(jiān)視的文件描述符添加到文件描述符集合當(dāng)中,系統(tǒng)提供了一組操作 fd_set 的接口,方便操作位圖:

?關(guān)于 timeval 結(jié)構(gòu)

?select 的最后一個參數(shù) timeout,是一個指向 timeval 結(jié)構(gòu)的指針,timeval 結(jié)構(gòu)用于描述一段時間長度,該結(jié)構(gòu)中有兩個成員變量,tv_sec 表示秒,tv_usec 表示微秒。

socket 就緒條件?

讀就緒:

? socket 內(nèi)核中,接收緩沖區(qū)中的字節(jié)數(shù),大于等于低水位標(biāo)記 SO_RECVLOWAT,此時可以無阻塞的讀該文件描述符,并且返回值大于0

? socket TCP通信中,對端關(guān)閉連接,此時對該socket讀,則返回0

? 監(jiān)聽的socket上有新的連接請求

? socket 上有未處理的錯誤

寫就緒:

? socket 內(nèi)核中,發(fā)送緩沖區(qū)中的可用字節(jié)數(shù)(發(fā)送緩沖區(qū)的空閑位置大小),大于等于水位標(biāo)記 SO_SNDLOWAT,此時可以無阻塞的寫,并且返回值大于0

? socket 的寫操作被關(guān)閉(close 或者 shutdown)對一個寫操作被關(guān)閉的 socket 進(jìn)行寫操作,會觸發(fā)SIGPIPE信號

? socket 使用非阻塞 connect 連接成功或失敗之后

? socket 上有未讀取的錯誤

異常就緒:

? socket 上收到帶外數(shù)據(jù)(帶外數(shù)據(jù)和TCP的緊急模式相關(guān),TCP報頭當(dāng)中的URG標(biāo)志位和16位緊急指針搭配使用,就能發(fā)送和接收帶外數(shù)據(jù))

socket 基本工作流程

?實現(xiàn)一個簡單的 select 服務(wù)器,該服務(wù)器做的內(nèi)容就是讀取客戶端發(fā)來的數(shù)據(jù)進(jìn)行輸出打印即可:

? 初始化服務(wù)器,套接字的創(chuàng)建,綁定端口號和IP地址等,設(shè)置監(jiān)聽狀態(tài)

? 定義一個 _fdarray 數(shù)組用于保存監(jiān)聽套接字和已經(jīng)與客戶端建立好連接的套接字,一開始只有監(jiān)聽套接字就緒,先把監(jiān)聽套接字添加到 _fdarray 數(shù)組中

? 服務(wù)器開始運行,一直輪詢調(diào)用 select 函數(shù),檢測讀事件是否就緒,有事件就緒了就執(zhí)行對應(yīng)的操作

? 在每次調(diào)用 select 函數(shù)之前,需要定義一個文件描述符集 rfds,把 _fdarray 中的文件描述符依次添加到集合中,讓 select 函數(shù)檢測這些文件描述符中哪些文件描述符的讀事件是否就緒。

? 當(dāng) select 檢測到數(shù)據(jù)就緒時,就會將讀事件就緒的文件描述符設(shè)置進(jìn)入到 rfds 中,這個參數(shù)是一個輸入輸出型參數(shù),就可以知道哪些文件描述符就緒了,并對這些文件描述符進(jìn)行對應(yīng)的操作

? 如果就緒的是監(jiān)聽套接字,就調(diào)用 accept 函數(shù)從底層全連接隊列獲取已經(jīng)建立好的連接,并且將對應(yīng)的套接字添加到 _fdarray 數(shù)組中

? 如果就緒的是與客戶端建立連接的套接字,就調(diào)用 read 函數(shù)將客戶端發(fā)送到數(shù)據(jù)進(jìn)行接收,并進(jìn)行打印輸出

? 服務(wù)器與客戶端建立連接的套接字讀事件就緒,也有可能是因為客戶端將連接的套接字關(guān)閉了,此時服務(wù)器應(yīng)該調(diào)用 close 關(guān)閉該套接字,并將該套接字從 _fdarray 數(shù)組中移除

細(xì)節(jié):

1、select 函數(shù)除了 nfds之外,其他都是輸入輸出型參數(shù),當(dāng) select 返回時,這些參數(shù)的值都已經(jīng)被修改了,每次調(diào)用時 select 函數(shù)需要對參數(shù)進(jìn)行重新設(shè)置,timeout 也一樣需要設(shè)置

2、select 需要傳入的被監(jiān)視的文件描述符中最大描述符值 +1,在遍歷 _fdarray 時,需要記錄最大文件描述符的值。

select 服務(wù)器

socket 類:

編寫一個 Socket 類,對套接字的接口進(jìn)行一定程度的封裝,為了外部通過作用域直接調(diào)用Socket 類當(dāng)中的封裝函數(shù),將這些接口函數(shù)定義成為靜態(tài)成員函數(shù):

// 進(jìn)行封裝 TCP socket 編寫
#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "logMessage.hpp"
#include "error.hpp"#define gbacklog 5class Sock
{
public:static int Socket(){// 1.創(chuàng)建套接字 面向字節(jié)流int listensock = socket(AF_INET, SOCK_STREAM, 0);if (listensock < 0){logMessage(FATAL, "Create Socket Fail!");exit(SOCKET_ERR);}logMessage(NORMAL, "Create Socket Success!");// 處理一下 Time_Wait 導(dǎo)致無法綁定端口號問題int opt = 1;setsockopt(listensock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));return listensock;}static void Bind(int sock, int port){// 2.bindstruct sockaddr_in peer;bzero(&peer, sizeof(peer));peer.sin_addr.s_addr = INADDR_ANY; // IP地址綁定任意的peer.sin_family = AF_INET;peer.sin_port = htons(port);if (bind(sock, (struct sockaddr *)&peer, sizeof(peer)) < 0){logMessage(FATAL, "Bind Socket Fail!");exit(BIND_ERR);}logMessage(NORMAL, "Bind Socket Success!");}static void Listen(int sock){// 3.Socket 設(shè)置為監(jiān)聽狀態(tài)if (listen(sock, gbacklog) < 0){// 設(shè)置失敗logMessage(FATAL, "Listen Socket Fail!");exit(LISTEN_ERR);}logMessage(NORMAL, "Listen Socket Success!");}// 獲取新連接static int Accpet(int listensock, uint16_t *clientPort, std::string *clientIP){struct sockaddr_in peer;socklen_t len = sizeof(peer);int sock = accept(listensock, (struct sockaddr *)&peer, &len);if (sock < 0){logMessage(FATAL, "Accept Socket Fail!");exit(ACCEPT_ERR);}else{logMessage(NORMAL, "Accpet Socket Success,get a new sock: %d", sock);*clientPort = ntohs(peer.sin_port);   // 網(wǎng)絡(luò)轉(zhuǎn)主機(jī)*clientIP = inet_ntoa(peer.sin_addr); // 網(wǎng)絡(luò)轉(zhuǎn)主機(jī),再轉(zhuǎn)字符串}return sock;}
};

selectServer 類

所編寫的服務(wù)器在綁定時,不需要顯示綁定IP地址,直接設(shè)置為 INADDR_ANY 即可,在類的成員變量中,只需要包含監(jiān)聽套接字,和端口號即可。

運行服務(wù)器:

select 服務(wù)器要做的是不斷調(diào)用select 函數(shù),當(dāng)有事件就緒時,做出處理動作

? 需要將數(shù)組中所有位置的值初始化為無效,并將監(jiān)聽套接字添加到數(shù)組的第一個位置

? select 函數(shù)返回后,如果返回0,說明 timeout 時間耗盡,直接進(jìn)行下一次的 select 調(diào)用即可,返回 -1,根據(jù)返回的錯誤碼來進(jìn)一步判斷,是否需要下一次繼續(xù)調(diào)用 select 函數(shù),返回值大于0,調(diào)用成功,已經(jīng)有文件描述符的讀事件就緒,對就緒事件進(jìn)一步處理

#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <functional>#include "sock.hpp"namespace Server
{using func_t = std::function<std::string(const std::string &)>;const uint16_t defaultPort = 8080;const int fdnum = sizeof(fd_set) * 8;const int defaultnum = -1;class SelectServer{public:SelectServer(func_t f, int port): _func(f), _listensock(-1), _port(port){}~SelectServer(){if (_listensock){close(_listensock);}if (_fdarray){delete[] _fdarray;}}void InitServer(){_listensock = Sock::Socket();Sock::Bind(_listensock, _port);Sock::Listen(_listensock);_fdarray = new int[fdnum];for (int i = 0; i < fdnum; i++){_fdarray[i] = defaultnum;}_fdarray[0] = _listensock;}void Start(){// 等 + 處理數(shù)據(jù)  select返回的是fd的個數(shù)for (;;){fd_set rfd;// 初始化rfdFD_ZERO(&rfd);int maxfd = _fdarray[0]; // 最大fd數(shù) +1for (int i = 0; i < fdnum; i++){if (_fdarray[i] == defaultnum){continue;}// 將這個fd 設(shè)置進(jìn)入rfd中FD_SET(_fdarray[i], &rfd);if (_fdarray[i] > maxfd){maxfd = _fdarray[i];}}logMessage(NORMAL, "max fd is: %d\n", maxfd);// 非阻塞 每隔一秒,詢問一下//struct timeval _timeval = {1, 0};// int n = select(maxfd + 1, &rfd, nullptr, nullptr, &_timeval);int n = select(maxfd + 1, &rfd, nullptr, nullptr, nullptr);switch (n){case 0:// 超時logMessage(NORMAL, "time out ...");break;case -1:logMessage(WARNING, "select errno code: %d, select errno message: %s", errno, strerror(errno));break;default:// 走到這里說明有fd就緒了,需要進(jìn)行處理,但是只有 listensock 就緒logMessage(NORMAL, "have event ready!\n");// 進(jìn)行業(yè)務(wù)邏輯處理Handerevent(rfd);break;}}}private:int _port;int _listensock;int *_fdarray; // 維護(hù)的accpet的fd數(shù)組func_t _func;};
}

timeout 時間測試

在運行服務(wù)器時,需要實例化一個對象,對select 服務(wù)器進(jìn)行初始化并調(diào)用 Start 函數(shù)運行服務(wù)器:

#include <iostream>
#include <string>
#include <memory>#include "error.hpp"
#include "selectServer.hpp"void Usage(std::string arg)
{std::cout << "\n Usage: \n\t" << arg << " port"<< "\n\t" << std::endl;
}// ./main 8080
int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);}std::unique_ptr<Server::SelectServer> us(new Server::SelectServer(transmition, atoi(argv[1])));us->InitServer();us->Start();return 0;
}

此時的服務(wù)器 select 函數(shù)將 timeout 設(shè)置為了 nullptr,因此select 函數(shù)調(diào)用時會阻塞等待,服務(wù)器在第一次調(diào)用 select 時,只監(jiān)視監(jiān)聽套接字(listensock),運行服務(wù)器后,客戶端沒有請求發(fā)送過來請求連接,讀事件就不會就緒,服務(wù)器第一次調(diào)用 select 函數(shù)中就會進(jìn)行阻塞等待:

?使用 telnet 工具向服務(wù)器發(fā)起連接請求,此時 select 函數(shù)會立馬檢測到監(jiān)聽套接字的讀事件就緒,然后對套接字做出對應(yīng)的處理邏輯:

如果此時將timeout的值設(shè)置為0,select 函數(shù)調(diào)用后就會進(jìn)行非阻塞等待,無論被監(jiān)視的文件描述符是否就緒,檢測后都會立馬返回,如果有事件就緒,select 函數(shù)的返回值就會大于0,沒有事件就緒,返回值就會等于0:

 struct timeval _timeval = {0, 0};int n = select(maxfd + 1, &rfd, nullptr, nullptr, &_timeval);switch (n){case 0:// 超時logMessage(NORMAL, "time out ...");break;case -1:logMessage(WARNING, "select errno code: %d, select errno message: %s", errno, strerror(errno));break;default:// 走到這里說明有fd就緒了,需要進(jìn)行處理,但是只有 listensock 就緒logMessage(NORMAL, "have event ready!\n");// 進(jìn)行業(yè)務(wù)邏輯處理Handerevent(rfd);break;}

此時如果沒有客戶端發(fā)送連接請求,select 函數(shù)就會一致進(jìn)行輪詢檢測,每次檢測的讀事件都不就緒,返回結(jié)果都是0,就會造成 time out ... 現(xiàn)象。?

?如果將 timeout 時間設(shè)置為特定的時間,比如這里設(shè)置為5s,那么select 函數(shù)調(diào)用后5秒內(nèi)會進(jìn)行阻塞等待,5秒后依舊沒有讀事件就緒,就會超時返回:

struct timeval _timeval = {5, 0};

運行服務(wù)器,此時無客戶端發(fā)送到連接請求,select 函數(shù)調(diào)用5秒后都會超時返回:

如果在5秒內(nèi)有讀事件就緒,那么?timeout 就會返回剩余的時間,所以在每次調(diào)用select 函數(shù)時,都需要對 timeout 時間重新設(shè)置。

事件處理?

當(dāng)select 檢測到有文件描述符就緒時并成功返回后,就需要對就緒事件進(jìn)行處理:

? 需要遍歷整個 _fdarray 數(shù)組當(dāng)中的文件描述符,依次判斷各個文件描述符的都事件是否就緒,如果就緒進(jìn)行處理

? 如果文件描述符就緒后,還需要判斷該文件描述符是否是監(jiān)聽套接字,如果是監(jiān)聽套接字就緒,就需要調(diào)用 accept 函數(shù),將底層的連接獲取上來,并添加到 _fdarray 數(shù)組當(dāng)中,在下一次調(diào)用select 函數(shù)之前,將 _fdarray 中的文件描述符設(shè)置進(jìn)入到 rfds 中

? 如果是客戶端建立的連接對應(yīng)的讀事件就緒,就需要調(diào)用 read 函數(shù)讀取客戶端發(fā)來的數(shù)據(jù),如果讀取成功就將讀到的數(shù)據(jù)進(jìn)行在服務(wù)器端打印,如果讀取失敗或者客戶端關(guān)閉了連接,那么服務(wù)器就調(diào)用close 函數(shù)關(guān)閉對應(yīng)的連接,并且將對應(yīng)的文件描述符從 _fdarray 中移除

   void Accpeter(int listensock){// 獲取新連接后,直接添加進(jìn)入到 _fdarray 數(shù)組中l(wèi)ogMessage(NORMAL, "Accpeter begin ...\n");uint16_t clientPort = 0;std::string clientIP;int sock = Sock::Accpet(listensock, &clientPort, &clientIP);if (sock < 0){return;}int i = 0;for (; i < fdnum; i++){if (_fdarray[i] != defaultnum){continue;}else{break;}}// 找到位置if (i == fdnum){// 說明已經(jīng)滿了logMessage(WARNING, "server is full,please wait ...\n");}else{_fdarray[i] = sock;}// 進(jìn)行打印Print();logMessage(NORMAL, "Accpeter end ...\n");}void Recver(int sock, int i){// 通過sock這個fd進(jìn)行接受數(shù)據(jù)logMessage(NORMAL, "Recver begin ...\n");char buffer[1024];ssize_t n = recv(sock, buffer, sizeof(buffer) - 1, 0); // 這里讀取不會阻塞,只有sock就緒了,才進(jìn)來if (n > 0){buffer[n - 1] = 0;logMessage(NORMAL, "client# %s", buffer);}else if (n < 0){// 讀取錯誤close(sock);_fdarray[i] = defaultnum;logMessage(WARNING, "recv error# %s", strerror(errno));return;}else // == 0{// client 退出了close(sock);_fdarray[i] = defaultnum;logMessage(NORMAL, "client quit,me too ...");return;}// 此時數(shù)據(jù)都在buffer當(dāng)中,處理 requeststd::string response = _func(buffer);write(sock, response.c_str(), response.size());logMessage(NORMAL, "Recver end ...\n");}// 處理邏輯void Handerevent(fd_set &rfd){// 判斷是listensock,還是普通sock的for (int i = 0; i < fdnum; i++){if (_fdarray[i] == defaultnum){continue; // 后面需要進(jìn)行置空,不能break}if (_fdarray[i] == _listensock && FD_ISSET(_fdarray[i], &rfd)){// 需要進(jìn)行accpetAccpeter(_fdarray[i]);}else if (FD_ISSET(_fdarray[i], &rfd)){// 其他fd 而且就緒Recver(_fdarray[i], i);}else{}}}

select 的優(yōu)缺點

優(yōu)點:

? 可以同時等待多個文件描述符,并且只負(fù)責(zé)等待,實際的IO操作由accept,read,write 函數(shù)來完成,這些接口在進(jìn)行IO操作時不會被阻塞。

? select 同時等待多個文件描述符,因此可以將“等”的事件重疊,提高IO的效率。

缺點:

? 每次調(diào)用select,都需要手動設(shè)置fd集合,從接口使用角度來說非常不方便

? 每次調(diào)用select,都需要把fd集合從用戶拷貝到內(nèi)核態(tài),這個開銷在fd很多時會很大

? 同時每次調(diào)用select都需要在內(nèi)核遍歷傳遞進(jìn)來的所有fd,這個開銷在fd很多時也很大

? select支持的文件描述符數(shù)量太小


select可監(jiān)控的fd個數(shù)

fd_set結(jié)構(gòu)本質(zhì)是一個位圖,用每一個比特位來標(biāo)記一個文件描述符,select可監(jiān)控的文件描述符個數(shù)取決于fd_set類型的比特位個數(shù),通過計算(sizeof(fd_set))* 8 可得總共1024個字節(jié),說明可監(jiān)控的文件描述符個數(shù)為1024,在初始化 _fdarray 數(shù)組時,需要將數(shù)組大小定義為 1024即可:

const int fdnum = sizeof(fd_set) * 8;

http://m.aloenet.com.cn/news/40680.html

相關(guān)文章:

  • wordpress seo插件中文版廣州網(wǎng)絡(luò)推廣seo
  • 南山做網(wǎng)站公司seo外鏈工具下載
  • php 網(wǎng)站 模板百度一下百度搜索百度
  • 上海設(shè)計網(wǎng)站開發(fā)日本疫情最新數(shù)據(jù)
  • 購物網(wǎng)站難做嗎google seo怎么優(yōu)化
  • 公司網(wǎng)站設(shè)計需要什么百度優(yōu)化點擊軟件
  • 怎么做網(wǎng)站的跳轉(zhuǎn)seo查詢外鏈
  • 在線制作logo設(shè)計百度seo排名優(yōu)化系統(tǒng)
  • 求職網(wǎng)站開發(fā)十大職業(yè)資格培訓(xùn)機(jī)構(gòu)
  • 阿里云一鍵安裝wordpress百度快照優(yōu)化培訓(xùn)班
  • 長春公司做網(wǎng)站如何在百度提交自己的網(wǎng)站
  • 學(xué)校門戶網(wǎng)站建設(shè)工作自己搭建一個網(wǎng)站
  • 企業(yè)郵箱注冊申請需要多少錢鄭州網(wǎng)站優(yōu)化外包
  • 做網(wǎng)上水果網(wǎng)站的調(diào)查百度sem代運營
  • wordpress網(wǎng)站反應(yīng)慢seo學(xué)習(xí)網(wǎng)站
  • 無錫網(wǎng)站建設(shè)公司排名網(wǎng)絡(luò)推廣包括哪些
  • 網(wǎng)站空間 php怎么在百度上發(fā)布信息廣告
  • 金昌網(wǎng)站建設(shè)seo服務(wù)公司招聘
  • 廈門排名推廣杭州百度首頁優(yōu)化
  • 福州哪里做網(wǎng)站網(wǎng)絡(luò)營銷的六大特征
  • 網(wǎng)站設(shè)計制作哪種快常見的網(wǎng)絡(luò)營銷方式有哪些
  • 公司網(wǎng)站建設(shè)制作難么網(wǎng)站開發(fā)的公司
  • 微網(wǎng)站建設(shè)高端網(wǎng)站定制杭州網(wǎng)站seo
  • 哪些網(wǎng)站可以做網(wǎng)站百度手機(jī)助手下載2021新版
  • 漢壽做網(wǎng)站的公司武漢seo首頁優(yōu)化技巧
  • flash可以做網(wǎng)站搜索引擎的優(yōu)化和推廣
  • 做網(wǎng)站的用處建網(wǎng)站公司哪里好
  • 制作網(wǎng)頁一般需要兼容哪些網(wǎng)站廣州網(wǎng)站seo
  • 廣州做網(wǎng)站網(wǎng)絡(luò)公司bt櫻桃 磁力島
  • 做網(wǎng)站的要求臺州百度推廣優(yōu)化