WinSock网络编程基础(3)server
上一篇讲的是简单的发送数据的客户端的实现。接下来讲的是如何实现收发数据服务器。
这里说的服务器其实就是一个进程,它需要等待任意数量的客户端与之建立起连接,以便响应它们的请求。
服务器必须在已知的名称上监听连接(在TCP/IP中是ip地址和端口号,不同协议的寻址方案和命名方法也不同)
创建服务器程序的第一步和客户端一样,要先初始化并且创建SOCKET(LISTENSOCKET用于监听客户端连接)
#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)
接下来需要将套接字绑定到它已知的地址(IP和端口)
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结构的长度 );
接受客户端的连接之后,就使用RECV和SEND函数接收发送数据了
#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; }