3 服务器
1 初始化Winsock
WSADATA wsaData;
int iResult;
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed: %d\n", iResult);
return 1;
}
2 为服务器创建套接字
2.1 设定参数
getaddrinfo函数用于确定sockaddr结构中的值:
AF_INET 用于指定IPv4地址族。
SOCK_STREAM 用于指定流套接字。
IPPROTO_TCP 用于指定TCP协议。
AI_PASSIVE 标志指示调用者打算在对bind函数的调用中使用返回的套接字地址结构。 当设置了AI_PASSIVE标志并且getaddrinfo函数的nodename参数为NULL指针时,套接字地址结构的IP地址部分对于IPv4地址设置为INADDR_ANY或对于IPv6地址设置为IN6ADDR_ANY_INIT。
27015是与客户端将连接到的服务器关联的端口号。
#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;
}
2.2 为服务器创建一个名为ListenSocket 的SOCKET对象,以侦听客户端连接。
SOCKET ListenSocket = INVALID_SOCKET;
2.3 调用套接字函数,并将其值返回到ListenSocket变量。
对于此服务器应用程序,使用调用返回的第一个IP地址获取与addsinfo参数(在hints参数中指定的地址系列,套接字类型和协议)相匹配的getaddrinfo。在此示例中,使用IPv4的地址族,SOCK_STREAM的套接字类型和IPPROTO_TCP的协议请求了IPv4的TCP流套接字。因此,为ListenSocket请求了一个IPv4地址。
如果服务器应用程序要侦听IPv6,则需要在hints参数中将地址族设置为AF_INET6 。如果服务器要同时监听IPv6和IPv4,则必须创建两个监听套接字,一个用于IPv6,另一个用于IPv4。这两个套接字必须由应用程序分别处理。
Windows Vista和更高版本提供了创建单个IPv6套接字的功能,该套接字置于双堆栈模式下,可以同时侦听IPv6和IPv4。有关此功能的更多信息,请参见Dual-Stack Sockets。
// Create a SOCKET for the server to listen for client connections
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
2.4 检查错误以确保该套接字是有效的套接字。
if (ListenSocket == INVALID_SOCKET) {
printf("Error at socket(): %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
3 绑定套接字
该sockaddr的结构保存有关家庭地址,IP地址和端口号的信息。
调用bind函数,将从getaddrinfo函数返回的创建的套接字和sockaddr结构作为参数传递。检查一般错误。
// Setup the TCP listening socket
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;
}
一旦绑定函数被调用,在返回的地址信息的getaddrinfo功能不再需要。该freeaddrinfo函数被调用,以释放该分配的内存的getaddrinfo这个地址信息的功能。
freeaddrinfo(result);
4 监听套接字
调用listen函数,将创建的套接字和backlog的值作为参数传递,backlog的值是要接受的未决连接队列的最大长度。在此示例中,backlog参数设置为SOMAXCONN。该值是一个特殊的常数,它指示Winsock提供程序为此套接字允许在队列中允许最大合理数量的挂起连接。检查返回值是否存在一般错误。
if ( listen( ListenSocket, SOMAXCONN ) == SOCKET_ERROR ) {
printf( "Listen failed with error: %ld\n", WSAGetLastError() );
closesocket(ListenSocket);
WSACleanup();
return 1;
}
5 接受连接
5.1 创建一个名为ClientSocket 的临时SOCKET对象,以接受来自客户端的连接。
SOCKET ClientSocket;
5.2 通常,服务器应用程序将被设计为侦听来自多个客户端的连接。对于高性能服务器,通常使用多个线程来处理多个客户端连接。
使用Winsock有几种不同的编程技术,可用于侦听多个客户端连接。一种编程技术是创建一个连续循环,该循环使用listen函数检查连接请求(请参见在Socket上侦听)。如果发生连接请求,则应用程序将调用accept,AcceptEx或WSAAccept函数,并将工作传递给另一个线程来处理该请求。其他几种编程技术也是可能的。
请注意,此基本示例非常简单,并且不使用多个线程。该示例还仅侦听并接受单个连接。
ClientSocket = INVALID_SOCKET;
// Accept a client socket
ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET) {
printf("accept failed: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
5.3 接受客户端连接后,服务器应用程序通常会将接受的客户端套接字(上述示例代码中的ClientSocket变量)传递给工作线程或I / O完成端口,然后继续接受其他连接。在此基本示例中,服务器继续进行下一步。
还有许多其他编程技术可用于侦听和接受多个连接。这些包括使用select或WSAPoll函数。Microsoft Windows软件开发工具包(SDK)附带的Advanced Winsock Samples中说明了其中各种编程技术的示例。
在Unix系统上,服务器的一种常见编程技术是使应用程序侦听连接。接受连接后,父进程将调用fork函数来创建一个新的子进程来处理客户端连接,并从父进程继承套接字。Windows不支持该编程技术,因为不支持fork功能。这种技术通常也不适合高性能服务器,因为创建新进程所需的资源比线程所需的资源大得多。
6 在套接字上接收和发送数据
#define DEFAULT_BUFLEN 512
char recvbuf[DEFAULT_BUFLEN];
int iResult, iSendResult;
int recvbuflen = DEFAULT_BUFLEN;
// Receive until the peer shuts down the connection
do {
iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
if (iResult > 0) {
printf("Bytes received: %d\n", iResult);
// Echo the buffer back to the sender
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);
的发送和recv的功能都返回分别发送或接收的,字节数,或错误的一个整数值。每个函数还采用相同的参数:活动套接字,char缓冲区,要发送或接收的字节数以及要使用的任何标志。
7 断开服务器
7.1 服务器完成向客户端的数据发送后,可以通过指定SD_SEND调用关闭函数,以关闭套接字的发送端。这允许客户端释放该套接字的一些资源。服务器应用程序仍可以在套接字上接收数据。
// shutdown the send half of the connection since no more data will be sent
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown failed: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
7.2 客户端应用程序完成数据接收后,将调用closesocket函数关闭套接字。
使用Windows套接字DLL完成客户端应用程序后,将调用WSACleanup函数以释放资源。
// cleanup
closesocket(ClientSocket);
WSACleanup();
return 0;
完整winsock服务器代码
#undef UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
// Need to link with Ws2_32.lib
#pragma comment (lib, "Ws2_32.lib")
// #pragma comment (lib, "Mswsock.lib")
#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "27015"
int __cdecl main(void)
{
WSADATA wsaData;
int iResult;
SOCKET ListenSocket = INVALID_SOCKET;
SOCKET ClientSocket = INVALID_SOCKET;
struct addrinfo *result = NULL;
struct addrinfo hints;
int iSendResult;
char recvbuf[DEFAULT_BUFLEN];
int recvbuflen = DEFAULT_BUFLEN;
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed with error: %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;
// Resolve the server address and port
iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
if ( iResult != 0 ) {
printf("getaddrinfo failed with error: %d\n", iResult);
WSACleanup();
return 1;
}
// Create a SOCKET for connecting to server
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;
}
// Setup the TCP listening socket
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;
}
// Accept a client socket
ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET) {
printf("accept failed with error: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
// No longer need server socket
closesocket(ListenSocket);
// Receive until the peer shuts down the connection
do {
iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
if (iResult > 0) {
printf("Bytes received: %d\n", iResult);
// Echo the buffer back to the sender
iSendResult = send( ClientSocket, recvbuf, iResult, 0 );
if (iSendResult == SOCKET_ERROR) {
printf("send failed with error: %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 with error: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
} while (iResult > 0);
// shutdown the connection since we're done
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown failed with error: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
// cleanup
closesocket(ClientSocket);
WSACleanup();
return 0;
}