为服务器创建套接字
-
所述的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,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) { cout << "WSAStartup failed:" << iResult << endl; WSACleanup(); return 1; }
-
为服务器创建一个名为ListenSocket的SOCKET对象,以侦听客户端连接。
SOCKET ListenSocket = INVALID_SOCKET;
-
调用套接字函数,并将其值返回到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。
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
-
检查是否有错误,以确保该套接字是有效的套接字。
if (ListenSocket == INVALID_SOCKET) { cout << "Error at socket():" << WSAGetLastError() << endl; freeaddrinfo(result); WSACleanup(); return 1; }
绑定套接字
为了使服务器接受客户端连接,必须将其绑定到系统内的网络地址。以下代码演示了如何将已经创建的套接字绑定到IP地址和端口。客户端应用程序使用IP地址和端口连接到主机网络。
该sockaddr的结构保存有关家庭地址,IP地址和端口号的信息。
调用bind函数,将从getaddrinfo函数返回的创建的套接字和sockaddr结构作为参数传递。检查一般错误。
// Setup the TCP listening socket
iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR) {
cout << "bind failed with error:" << WSAGetLastError() << endl;
freeaddrinfo(result);
closesocket(ListenSocket);
WSACleanup();
return 1;
}
一旦绑定函数被调用,在返回的地址信息的getaddrinfo功能不再需要。该freeaddrinfo函数被调用,以释放该分配的内存的getaddrinfo这个地址信息的功能。
C ++复制
freeaddrinfo(result);
在套接字上侦听
将套接字绑定到系统上的IP地址和端口后,服务器必须随后在该IP地址和端口上侦听传入的连接请求。
调用listen函数,将创建的套接字和backlog的值作为参数传递,backlog的值是要接受的未决连接队列的最大长度。在此示例中,backlog参数设置为SOMAXCONN。此值是一个特殊的常数,它指示Winsock提供程序为此套接字允许在队列中允许最大数量的合理挂起连接。检查返回值是否存在一般错误。
if ( listen( ListenSocket, SOMAXCONN ) == SOCKET_ERROR ) {
cout << "Listen failed with error:" << WSAGetLastError() << endl;
closesocket(ListenSocket);
WSACleanup();
return 1;
}
接收连接
套接字侦听连接后,程序必须处理该套接字上的连接请求。
-
创建一个名为ClientSocket的临时SOCKET对象,以接受来自客户端的连接。
SOCKET ClientSocket;
-
通常,服务器应用程序将被设计为侦听来自多个客户端的连接。对于高性能服务器,通常使用多个线程来处理多个客户端连接。
使用Winsock有几种不同的编程技术,可用于侦听多个客户端连接。一种编程技术是创建一个连续循环,使用listen函数检查连接请求(请参见在Socket上侦听)。如果发生连接请求,则应用程序将调用accept,AcceptEx或WSAAccept函数,并将工作传递给另一个线程来处理该请求。其他几种编程技术也是可能的。
请注意,此基本示例非常简单,并且不使用多个线程。该示例还仅侦听并接受单个连接。
ClientSocket = INVALID_SOCKET; // Accept a client socket ClientSocket = accept(ListenSocket, NULL, NULL); if (ClientSocket == INVALID_SOCKET) { cout << "accept failed:" << WSAGetLastError() << endl; closesocket(ListenSocket); WSACleanup(); return 1; }
-
接受客户端连接后,服务器应用程序通常会将接受的客户端套接字(上述示例代码中的ClientSocket变量)传递给工作线程或I / O完成端口,然后继续接受其他连接。在此基本示例中,服务器继续进行下一步。
还有许多其他编程技术可用于侦听和接受多个连接。这些包括使用select或WSAPoll函数。Microsoft Windows软件开发工具包(SDK)附带的Advanced Winsock Samples中说明了其中各种编程技术的示例。
在服务器上接收和发送数据
以下代码演示了服务器使用的recv和send函数。
#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);
的send和recv的功能都返回分 别发送或接收的,字节数,或错误的一个整数值。每个函数还具有相同的参数:活动套接字,char缓冲区,要发送或接收的字节数以及要使用的任何标志。
断开服务器
服务器完成从客户端接收数据并将数据发送回客户端后,服务器将与客户端断开连接并关闭套接字。
断开和关闭套接字
-
服务器完成向客户端的数据发送后,可以通过指定SD_SEND调用shutdown函数来关闭套接字的发送端。这允许客户端释放该套接字的一些资源。服务器应用程序仍可以在套接字上接收数据。
// 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; }
-
客户端应用程序完成数据接收后,将调用closesocket函数关闭套接字。
使用Windows套接字DLL完成客户端应用程序后,将调用WSACleanup函数以释放资源。
// cleanup closesocket(ClientSocket); WSACleanup(); return 0;
示例
#undef UNICODE
#define WIN32_LEAN_AND_MEAN
#include<iostream>
#include<winsock2.h>
#include<ws2tcpip.h>
#include<stdlib.h>
#include<stdio.h>
#define DEFAULT_PORT "27015"
#define DEFAULT_BUFLEN 512
//#define WIN32_LEAN_AND_MEAN
using namespace std;
#pragma comment(lib,"ws2_32.lib")
//赋值操作
int main()
{
//初始化Winsock
WSADATA wsadata;
int iResult;
iResult = WSAStartup(MAKEWORD(2, 2), &wsadata);
if (iResult != 0) {
cout << "WSAStartup failed:" << iResult << endl;
return 1;
}
//为服务器创建套接字
//SOCKET serversocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
struct addrinfo* result = 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;
iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
cout << "getaddrinfo failed:" << iResult << endl;
WSACleanup();
return 1;
}
SOCKET ListenSocket = INVALID_SOCKET;
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ListenSocket == INVALID_SOCKET) {
cout << "Error at socket():" << WSAGetLastError() << endl;
freeaddrinfo(result);
WSACleanup();
return 1;
}
//绑定套接字
iResult = ::bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR) {
cout << "bind failed with error:" << WSAGetLastError() << endl;
freeaddrinfo(result);
closesocket(ListenSocket);
WSACleanup();
return 1;
}
freeaddrinfo(result);
//进入监听状态
if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) {
cout << "Listen failed with error:" << WSAGetLastError() << endl;
closesocket(ListenSocket);
WSACleanup();
return 1;
}
//接收客户端请求
SOCKET ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET) {
cout << "accept failed:" << WSAGetLastError() << endl;
closesocket(ListenSocket);
WSACleanup();
return 1;
}
//在服务器上接收和发送数据
char recvbuf[DEFAULT_BUFLEN];
int iSendResult;
int recvbuflen = DEFAULT_BUFLEN;
do {
iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
if (iResult > 0) {
cout << "Bytes received:" << iResult << endl;
iSendResult = send(ClientSocket, recvbuf, iResult, 0);
if (iSendResult == SOCKET_ERROR) {
cout << "send failed:" << WSAGetLastError() << endl;
closesocket(ClientSocket);
WSACleanup();
return 1;
}
cout << "Bytes sent:" << iSendResult << endl;
}
else if (iResult == 0) {
cout << "Connection closing..." << endl;
}
else {
cout << "recv failed:" << WSAGetLastError() << endl;
closesocket(ClientSocket);
WSACleanup();
return 1;
}
} while (iResult > 0);
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
cout << "shutdown failed: " << WSAGetLastError() << endl;
closesocket(ClientSocket);
WSACleanup();
return 1;
}
closesocket(ClientSocket);
WSACleanup();
return 0;
}