流程:
// 为服务器创建套接字
// 绑定套接字
// 侦听套接字
// 接受连接
// 在服务器上接收和发送数据
// 断开服务器连接
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
#pragma comment(lib, "Ws2_32.lib")
#define DEFAULT_PORT "27015"
#define DEFAULT_BUFLEN 512
int iResult;
// 创建名为 ClientSocket 的临时 SOCKET 对象,用于接受来自客户端的连接。
SOCKET ClientSocket;
// 创建名为 wsaData 的 WSADATA 对象。
WSADATA wsaData;
// addrinfo 结构由 getaddrinfo 函数使用。
struct addrinfo *result = NULL, *ptr = NULL, hints;
// Initialize Winsock
bool Init_Socket() {
// 调用 WSAStartup 函数以启动WS2_32.dll的使用。
// WSADATA 结构包含有关Windows套接字实现的信息。
// WSAStartup 的 MAKEWORD (2,2) 参数对系统上的 Winsock 版本 2.2 发出请求,
// 并将传递的版本设置为调用方可以使用的Windows套接字支持的最高版本。
// 调用 WSAStartup 并将其值作为整数返回并检查错误
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed: %d\n", iResult);
return false;
}
printf("WSAStartup success: %d\n", iResult);
return true;
}
// 创建一个名为 ListenSocket 的 SOCKET 对象,供服务器侦听客户端连接。
int CreatSocket(){
// AF_INET 用于指定 IPv4 地址系列。
// SOCK_STREAM 用于指定流套接字。
// IPPROTO_TCP 用于指定 TCP 协议。
// AI_PASSIVE 标志指示调用方打算在对 绑定 函数的调用中使用返回的套接字地址结构。
// 当将AI_PASSIVE标志设置为 getaddrinfo 函数的 nodename 参数为 NULL 指针时,
// 套接字地址结构的 IP 地址部分设置为INADDR_ANY IPv4 地址或 IPv6 地址的IN6ADDR_ANY_INIT。
// 5001 是与客户端将连接到的服务器关联的端口号。
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
// addrinfo 结构由 getaddrinfo 函数使用。
iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
printf("getaddrinfo failed: %d\n", iResult);
WSACleanup();
return 1;
}
SOCKET ListenSocket = INVALID_SOCKET;
// 调用 套接字 函数并将其值返回到 ListenSocket 变量。
// 对于此服务器应用程序,请使用调用返回的第一个 IP 地址来匹配提示参数中指定的地址系列、套接字类型和协议的 getaddrinfo。
// 在此示例中,请求 IPv4 的 TCP 流套接字具有 IPv4 的地址系列、SOCK_STREAM的套接字类型和IPPROTO_TCP协议。
// 因此,为 ListenSocket 请求 IPv4 地址。
/*
如果服务器应用程序想要侦听 IPv6,则需要将地址系列设置为 提示 参数中的AF_INET6。
如果服务器想要同时侦听 IPv6 和 IPv4,则必须创建两个侦听套接字,一个用于 IPv6,一个用于 IPv4。
这两个套接字必须由应用程序单独处理。
Windows Vista 及更高版本提供创建单个 IPv6 套接字的功能,该套接字处于双堆栈模式以侦听 IPv6 和 IPv4。
*/
// Create a SOCKET for the server to listen for client connections
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
// 检查错误以确保套接字是有效的套接字。
if (ListenSocket == INVALID_SOCKET) {
printf("create socket failed");
// freeaddrinfo(result);
// WSACleanup();
return false;
}
return true;
}
// 若要使服务器接受客户端连接,它必须绑定到系统中的网络地址。
// 以下代码演示如何将已创建的套接字绑定到 IP 地址和端口。
// 客户端应用程序使用 IP 地址和端口连接到主机网络。
int BindSock(){
// 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());
// 调用 绑定 函数后,不再需要 getaddrinfo 函数返回的地址信息。
// 调用 freeaddrinfo 函数以释放 getaddrinfo 函数为此地址信息分配的内存。
freeaddrinfo(result);
closesocket(ListenSocket);
WSACleanup();
return 1;
}
}
// 调用 侦听 函数,以参数的形式传递所创建的套接字和 积压工作的值、要接受的挂起连接队列的最大长度。
// 在此示例中, 积压工作 参数设置为 SOMAXCONN。
// 此值是一个特殊常量,指示此套接字的 Winsock 提供程序允许队列中最大合理数量的挂起连接。
// 检查返回值是否存在常规错误。
int ListenSock(){
if ( listen( ListenSocket, SOMAXCONN ) == SOCKET_ERROR ) {
printf( "Listen failed with error: %ld\n", WSAGetLastError() );
closesocket(ListenSocket);
WSACleanup();
return 1;
}
}
int AcceptClient(){
//通常,服务器应用程序设计为侦听来自多个客户端的连接。 对于高性能服务器,通常使用多个线程来处理多个客户端连接。
// 使用 Winsock 有多种不同的编程技术可用于侦听多个客户端连接。
// 一种编程技术是创建一个连续循环,该循环使用 侦听 函数检查连接请求, (看到 套接字上的侦听) 。
// 如果发生连接请求,应用程序将调用 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;
}
// 接受客户端连接后,服务器应用程序通常会将接受的客户端套接字 (上述示例代码中的 ClientSocket 变量)
// 工作线程或 I/O 完成端口,并继续接受其他连接。 在此基本示例中,服务器将继续执行下一步。
// 有多种其他编程技术可用于侦听和接受多个连接。 这包括使用 select 或 WSAPoll 函数。
// Microsoft Windows软件开发 (工具包) 随附的高级 Winsock 示例演示了其中一些各种编程技术的示例。
}
int SendClient(){
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 函数分别返回发送或接收的字节数的整数值或错误。
// 每个函数还采用相同的参数:活动套接字、 字符 缓冲区、要发送或接收的字节数以及要使用的任何标志。
}
int CloseSer(){
// 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;
}
// cleanup
closesocket(ClientSocket);
WSACleanup();
return 0;
}