上一篇講的是簡單的發送數據的客戶端的實現。接下來講的是如何實現收發數據服務器。
這裡說的服務器其實就是一個進程,它需要等待任意數量的客戶端與之建立起連接,以便響應它們的請求。
服務器必須在已知的名稱上監聽連接(在TCP/IP中是ip地址和端口號,不同協議的尋址方案和命名方法也不同)
#define DEFAULT_PORT "27015"
struct addrinfo *result = NULL, *ptr = NULL, hints;
ZeroMemory(&hints, sizeof (hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
// Resolve the local address and port to be used by the server
iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
printf("getaddrinfo failed: %d\n", iResult);
WSACleanup();
return 1;
}
SOCKET ListenSocket = INVALID_SOCKET;
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ListenSocket == INVALID_SOCKET) {
printf("Error at socket(): %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
這裡的套接字設置的監聽地址為AF_INET,也就是IPv4的地址。如果想監聽IPv6的地址就可以設置為AF_INET6,如果想同時監聽的話就需要創建兩個監聽套接字,(vista系統之後提供了一個特殊的套接字能夠同時監聽IPv4和IPv6。詳情: Dual-Stack Sockets)
iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR) {
printf("bind failed with error: %d\n", WSAGetLastError());
freeaddrinfo(result);
closesocket(ListenSocket);
WSACleanup();
return 1;
}
freeaddrinfo(result);
bind函數:
int bind( _In_ SOCKET s, //等待客戶端連接的套接字 _In_ const struct sockaddr *name, // 指向進行綁定的地址結構 _In_ int namelen //表示要傳遞的,由協議決定的地址結構的長度 );
一旦綁定出錯,bind回返回SOCKET_ERROR。對bind而言,最常見的錯誤是WSAEADDRINUSE(表示另一進程已經同本地ip及端口號綁定,或者該ip和端口號處於TIME——WAIT狀態)和WSAEFAULT(調用的套接字已經被綁定)
bind函數被調用之後,getaddrinfo函數返回的地址信息就基本沒有什麼作用了,可以使用freeaddrinfo釋放地址信息。
if ( listen( ListenSocket, SOMAXCONN ) == SOCKET_ERROR ) {
printf( "Listen failed with error: %ld\n", WSAGetLastError() );
closesocket(ListenSocket);
WSACleanup();
return 1;
}
listen函數:
int listen( _In_ SOCKET s, // 被綁定的套接字 _In_ int backlog // 監聽隊列中允許保持的尚未處理的最大連接數量 );
backlog這個參數很重要,它指定了被擱置的連接的最大隊列長度(比如backlog參數為3,但是有4台客戶端同時發出請求,那麼前3個會被放在掛起隊列之中,而第四個會連接請求失敗返回WSAECONNREFUSED錯誤)
SOCKET ClientSocket;
ClientSocket = INVALID_SOCKET;
ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET) {
printf("accept failed: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
accept函數會返回一個新的套接字描述符,它對應已經接受了客戶端的連接,該客戶端的後續操作都用這個新的套接字,而原來LinstenSocket仍然處於監聽模式:
SOCKET accept( _In_ SOCKET s, //一個出於監聽模式的套接字 _Out_ struct sockaddr *addr, //一個指向sockaddr_in結構的指針,用於取得對方的地址信息 _Inout_ int *addrlen //addr結構的長度 );
#define DEFAULT_BUFLEN 512
char recvbuf[DEFAULT_BUFLEN];
int iResult, iSendResult;
int recvbuflen = DEFAULT_BUFLEN;
do {
iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
if (iResult > 0) {
printf("Bytes received: %d\n", iResult);
iSendResult = send(ClientSocket, recvbuf, iResult, 0);
if (iSendResult == SOCKET_ERROR) {
printf("send failed: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
printf("Bytes sent: %d\n", iSendResult);
} else if (iResult == 0)
printf("Connection closing...\n");
else {
printf("recv failed: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
} while (iResult > 0);
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown failed: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
closesocket(ClientSocket);
WSACleanup();
完整的server源代碼:
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <IPHlpApi.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")
//#pragma comment(lib, "Mssock.lib")
//#pragma comment(lib, "Advapi32.lib")
#define DEFAULT_PORT "27015"
#define DeFAULT_BUFLEN 512
int main()
{
WSADATA wsaData;
int iResult;
int iSendResult;
struct addrinfo *result = NULL;
struct addrinfo hints;
char recvbuf[DeFAULT_BUFLEN];
int recvbuflen = DeFAULT_BUFLEN;
SOCKET ListenSocket = INVALID_SOCKET;
SOCKET ClientSocket = INVALID_SOCKET;
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0)
{
printf("WSAStartup failed: %d\n", iResult);
return 1;
}
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
// 為服務器確定本地地址和端口
iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
if (iResult != 0)
{
printf("getaddrinfo failed: %d\n", iResult);
WSACleanup();
return 1;
}
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ListenSocket == INVALID_SOCKET)
{
printf("socket failed with error: %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR)
{
printf("bind failed with error: %d\n", WSAGetLastError());
freeaddrinfo(result);
closesocket(ListenSocket);
WSACleanup();
return 1;
}
freeaddrinfo(result);
iResult = listen(ListenSocket, SOMAXCONN);
if (iResult == SOCKET_ERROR)
{
printf("Listen failed with error: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET)
{
printf("accept failed: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
closesocket(ListenSocket);
do
{
iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
if (iResult > 0)
{
printf("Bytes received: %d\n", iResult);
iSendResult = send(ClientSocket, recvbuf, iResult, 0);
if (iSendResult == SOCKET_ERROR)
{
printf("send failed: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
printf("Bytes sent: %d\n", iSendResult);
}
else if (iResult == 0)
{
printf("Connection closing...\n");
}
else
{
printf("recv failed: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
} while (iResult > 0);
// 斷開連接
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR)
{
printf("shutdown failed: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
// 清理連接
printf("recv message: %s", recvbuf);
closesocket(ClientSocket);
WSACleanup();
return 0;
}
原文鏈接: http://www.bugcoding.com/entry/11