上一篇講的是簡單的發送數據的客戶端的實現。接下來說的是如何實現收發數據服務器。
這裏說的服務器其實就是一個進程,它須要等待任意數量的客戶端與之創建起鏈接,以便響應它們的請求。windows
服務器必須在已知的名稱上監聽鏈接(在TCP/IP中是ip地址和端口號,不一樣協議的尋址方案和命名方法也不一樣)api
#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函數:socket
int bind( _In_ SOCKET s, //等待客戶端鏈接的套接字 _In_ const struct sockaddr *name, // 指向進行綁定的地址結構 _In_ int namelen //表示要傳遞的,由協議決定的地址結構的長度 );
一旦綁定出錯,bind回返回SOCKET_ERROR。對bind而言,最多見的錯誤是WSAEADDRINUSE(表示另外一進程已經同本地ip及端口號綁定,或者該ip和端口號處於TIME——WAIT狀態)和WSAEFAULT(調用的套接字已經被綁定)tcp
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錯誤)server
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仍然處於監聽模式:blog
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; }