伺服器IO模型之Select

2021-06-20 03:31:52 字數 4284 閱讀 9408

阻塞與非阻塞:

select模型目的:主要是避免在套接字呼叫上阻塞的應用程式有能力管理多個套接字,即是單一執行緒模式下只能處理乙個套接字的問題,這樣可以避免執行緒膨脹。

select模型函式:

int select(

_in_     int nfds,

_inout_  fd_set *readfds,

_inout_  fd_set *writefds,

_inout_  fd_set *exceptfds,

_in_     const

struct timeval *timeout

);

引數說明:

nfds [in]:忽略,僅是為了相容berkeley套接字

readfds [in, out]:用來檢查可讀的套接字組合

writefds [in, out]:用來檢查可寫的套接字組合

exceptfds [in, out]:用來檢查異常的套接字組合

timeout [in]:等待的時間, 如果為null,等待的時間為無窮大

返回值:select返回那些即將要被處理的socket總和,假如時間超時,將會返回socket_error,可以使用wsagetlasterror獲得出錯的原因

select處理過程:假設以read為例,在這裡windows主要是先將套接字s新增到readfds集合中,然後等待select函式返回,在select函式裡面會移除沒有未決的i/o操作的套接字控制代碼,即移除未響應的io套接字控制代碼,然後看s是否認仍然還是readfs集合中,在就說明s可讀了

應用程式:

cinitsock initsock;

sockaddr_in addr;

ushort usport = 6000;

socket slisten = ::socket(af_inet, sock_stream, ipproto_tcp );

if (slisten == invalid_socket )

addr.sin_family = af_inet;

addr.sin_port = htons(usport);

addr.sin_addr.s_un.s_addr = htonl(inaddr_any);

if (::bind(slisten, (sockaddr*)&addr, sizeof(addr)) == socket_error)

::listen(slisten, 5);

fd_set fdsocket;

fd_zero(&fdsocket);

fd_set(slisten, &fdsocket);

while (true)

for (unsigned int i = 0; i < fdsocket.fd_count; i++)

fd_set(snewclient, &fdsocket);

trace("new client: %s\n", inet_ntoa(addrremote.sin_addr));}}

else

else

}}//判斷fdsocket裡面的socket是否得到處理

} }closesocket(slisten);

slisten = invalid_socket;

return 0;

這裡我使用了cinitsock, 因為在使用socket之前要載入

ws2_32.lib,這裡我定義了乙個類如下:

#pragma once

#include#include #pragma comment(lib,"ws2_32.lib")

class cinitsock

;

cinitsock::cinitsock(byte minver, byte majver)

}cinitsock::~cinitsock(void)

預設建構函式裡面有乙個預設載入的版本,這裡在析構函式裡面將之前載入的dll資源進行釋放,基礎的socket伺服器模型通常要進行socket建立,繫結到本地位址和埠,監聽客戶端的連線,一旦有客戶端連線,缺省會放入fdsocket中,然後將此函式加入fd_set可讀的套接字集合中,select返回後,未響應的socket會被移除,即將要被處理的socket會保留下來,然後從fdsocket判斷,到底是哪些socket發生了可讀操作:

注意:可讀操作包括有未處理的連線請求,資料可讀,連線關閉/重啟/中斷

首先第乙個判斷的就是未處理的連線請求,如果有就建立新的連線通道,加入fdsocket;如果是資料可讀,就讀取資料;連線關閉會在下面進行測試

客戶端程式:使用的是以前的乙個簡易客戶端程式,如下

word wversionrequested; //請求的版本

wsadata wsadata;

int nerr;

//協商版本號

wversionrequested = makeword(1,1);

nerr = wsastartup(wversionrequested, &wsadata);

if(nerr != 0)

if( lobyte(wsadata.wversion) != 1 ||

hibyte(wsadata.wversion) != 1 )

//建立socket埠

socket sockclient = socket(af_inet, sock_stream, 0);

sockaddr_in addrsrv;

addrsrv.sin_addr.s_un.s_addr = inet_addr("127.0.0.1");

addrsrv.sin_family = af_inet;

addrsrv.sin_port = htons(6000);

//繫結埠號

connect(sockclient,(sockaddr*)&addrsrv,sizeof(sockaddr));

//收發資料

char recvbuf[100], sendbuf[100];

memset(recvbuf, 0, 100);

memset(sendbuf, 0, 100);

sprintf_s(sendbuf,"hello world");

send(sockclient, sendbuf, strlen(sendbuf)+1, 0);

recv(sockclient, recvbuf, 100, 0);

printf("%s\n", recvbuf);

//關閉socket通訊

closesocket(sockclient);

wsacleanup();

sleep(1000);

測試結果:

這裡需要先執行伺服器端,然後再開啟客戶端程式,伺服器端會建立新的連線,並讀取客戶端發過來的資料然後顯示出來,客戶端是沒有資料的,因為這裡伺服器端並沒有傳送資料,如下伺服器資料:

再次開啟乙個客戶端的效果如下:

這裡並沒有進行換行,兩行資料在一起了,並不影響測試結果,這裡還可以再強制關閉客戶端後的結果,如下

這裡看到error的**為10054,我們在winerror.h裡面找到如下定義:

//

// messageid: wsaeconnreset

//// messagetext:

//// an existing connection was forcibly closed by the remote host.

//#define wsaeconnreset 10054l

從這裡也能看出的確是強制關閉,注意伺服器端裡面的trace要給我printf才可以,trace預設是在除錯下使用的輸出語句

select不足:其實新增到fd_set套接字數量是有限制的,winsock2.h定義的64,自定義也不超過1024,因為值太大,會對伺服器的效能有影響,假設有1000個的話,在呼叫select之前就必須設定這1000個套接字,select返回之後,還必須檢查這1000個套接字,所以開銷較大。

6 12多路IO轉接伺服器之select

title date comments categories br 多路i o轉接伺服器之select 2020 3 18 true linux linux 伺服器 6.12 多路i o轉接伺服器也叫多工io伺服器或者i o多路復用技術。該類伺服器實現的主旨思想是,不再由應用程式自己監聽客戶端的連線...

I O復用之select伺服器

學習了select之後,也有好一段時間了,但是一直沒有提起寫一篇關於select的部落格,大概也是因為自己那會還沒搞懂吧,這段時間在看 linux高效能伺服器程式設計 時,又看到i o復用對於select,poll,epoll的用法例項和比較,又從頭看了一次之前寫的 雖然是在老師的指導下寫的,但是印...

網路程式設計 多路I O轉接伺服器之select

思路 利用select 函式監聽資訊,accept 函式非阻塞的建立連線。相關api include according to earlier standards include include include intselect int nfds,fd set readfds,fd set wri...