阻塞與非阻塞:
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...