服務端套接字是編程最簡單的一個部分,甚至在各種環境下都可以用類似的代碼。對于一個服務端套接字而言,他的基本工作包括:
- 初始化
- 創建套接字
- 給套接字捆綁一個本地地址
- 【可選】設置套接字屬性
- 調用listen函數
- 進入accept循環,接受來自客戶端的請求
- 對PCS進行管理
- 釋放服務端套接字所占用的資源
3.1 初始化
對于每個需要處理套接字的程序,在調用任何其他套接字函數之前,必須首先調用WSAStartup函數。如果不調用它,其他函數調用會失敗,并返回WSANOTINITIALISED錯誤碼,表示WinSock庫還沒有初始化。對于除了其他操作系統,初始化的方法會不一樣,請參見對應的文檔找到初始化方法。
WSAStartup的函數原型為:
int WSAStartup(
WORD wVersionRequested,
LPWSADATA lpWSAData
);
其中,各個參數的含義如下:
wVersionRequested:
|
本程序所需要的最低版本號。請注意,MSDN中所說的是本程序可能用到的最高版本的函數,其含義和我所說的一樣。 |
lpWSAData
|
返回WSADATA結構,說明WinSock庫當前的實現細節 |
該函數成功時返回0,否則可能返回如下一些錯誤碼:
WSASYSNOTREADY
|
底層網絡系統未準備好
|
WSAVERNOTSUPPORTED
|
所要求版本號本實現不支持
|
WSAEINPROGRESS
|
一個阻塞性套接字操作未完成
|
WSAEPROCLIM
|
達到當前實現所支持的最大任務數
|
WSAEFAULT
|
lpWSAData是一個非法指針 |
一般用戶并不關心實現細節,只要當前實現庫滿足最低版本需求即可。常見代碼為:
WORD wVersion = MAKEWORD(2,2);//最低版本2.2
WSADATA WSAData;
int nResult;
if(ERROR_SUCCESS != (nResult = WSAStartup(wVersion,&WSAData)))
{
ReportError("WSAStartup", nResult);
return -1;
}
3.2 創建套接字
每個套接字任務都從創建套接字開始。我們可以用socket函數來創建套接字,該函數的原型為:
SOCKET socket(
int af,
int type,
int protocol
)
其中,各個參數的含義為:
協議號,說明該套接字所處理的協議。他的可選值隨前面兩個參數不同而不同。似乎在RAW協議中用的比較多,大家可以在ROUTPROT.h中找到類似定義。
af |
地址族,說明該socket支持的地址類型。我們可以在winsock2.h中找到所支持的地址族。不過一般來說,對于TCP/IP編程,我們都會設置為AF_INET |
type
|
協議類型,Winsock2.h中列出了5種類型,我們一般會使用其中的三種,SOCK_STREAM表示流協議,SOCK_DGRAM表示數據報協議,SOCK_RAW表示原始套接字。我會在數據傳輸部分詳細解釋這些內容 |
protocol |
用于偵聽的套接字需要是流套接字,下面代碼會創建這樣的套接字:
SOCKET hSocket;
int a = PROTO_ICMP;
hSocket = socket(AF_INET,SOCK_STREAM,0);
if(ERRORHANDLE(hSocket == INVALID_SOCKET))
{
WSACleanup();
return -1;
}
3.3 捆綁本地地址
每個套接字必須有一個地址才能和對方通訊。bind函數用于捆綁地址,它的函數原型為:
int bind(
SOCKET s,
const struct sockaddr* name,
int namelen
);
各個參數含義如下:
s
|
套接字號 |
name
|
地址信息:需要注意的是,套接字接口是給多種協議共享的,sockaddr結構只是一個占位符,不同協議使用不同的地址結構,例如,TCP/IP編程使用的結構是sockaddr_in |
namelen |
地址結構的長度,加入這個參數的原因也就是因為不同協議有不同的結構 |
需要注意的,bind函數只能給socket綁定本機的IP地址,如果你給出的地址信息是其他機器的,則必然會失敗。此時該函數返回SOCKET_ERROR,WSAGetLastError則返回WSAEADDRNOTAVAIL。
如果沒有特殊需求,應該設置該結構的IP地址為INADDR_ANY。對于端口,服務端套接字需要指定一個端口,而客戶端端口最好設置為0,讓系統選擇一個可用端口。
下面代碼初始化一個地址結構:
void InitializeAddress(DWORD ip, UINT port, sockaddr_in & addr)
{
memset(&addr,0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr= ip;
addr.sin_port = htons(port);
}
下面代碼則把一個套接字綁定到本機2000端口上:
if(ERRORHANDLE(SOCKET_ERROR == bind(hSocket, (const sockaddr*) & addr, sizeof(addr))))
{
closesocket(hSocket);
WSACleanup();
return -1;
}
3.4 進入偵聽狀態
當我們創建一個套接字,并綁定了地址后,我們需要設置這個套接字進入偵聽狀態,進入偵聽狀態后,該套接字就可以處理來自客戶端的鏈接請求。
listen函數設置套接字進入偵聽狀態,其函數原型為:
int listen(
SOCKET s,
int backlog
);
對于剛開始編程的人,最容易誤解的是backlog參數。許多人以為這就是該套接字最多能接收的鏈接的數目。實際上,一個套接字能接受的鏈接的數目不受這個參數控制,它只受系統資源的限制。例如對于linux,套接字用文件句柄實現,那么它可能受最大文件句柄數的限制。
這個參數的含義是最多未決連接的數目,也就是連接請求已經到了服務端套接字,但是用戶還沒有調用accept的套接字數目。對于WinSock2,這個參數最大值為5。
下面示例代碼說明了如何調用listen:
if(ERRORHANDLE(SOCKET_ERROR == listen(hSocket,5)))
{
closesocket(hSocket);
WSACleanup();
return -1;
}
3.5 accept循環
當一個套接字處于偵聽狀態以后,我們就可以循環調用accept來接受新連接。accept函數的原型如下:
SOCKET accept(
SOCKET s,
struct sockaddr* addr,
int* addrlen
);
這個函數的參數說明和前面bind的一樣。需要說明的是,在MSDN中說后面兩個參數都是out參數,經過我的測試,結論并不一樣。對于
addrlen參數,應該是一個in/out參數,也就是說,如果第二個參數是一個結構指針,則第三個參數必須是一個整型變量的指針,該整型變量還必須被
設置為該結構的長度。
3.6 PCS管理
由于一個一個服務端套接字可能接收無數個PCS,如何管理這些PCS就成為一個問題。不同的程序員有自己不同的管理方式,在此我就不準備細講了。
posted on 2008-07-03 15:17
SIMONE 閱讀(651)
評論(0) 編輯 收藏 所屬分類:
C++