Windows Sockets (Winsock) 是微软对 Berkeley Sockets API 的扩展实现,为 Windows 平台上的网络应用程序开发提供了核心接口,掌握 Winsock 是构建高效、稳定网络软件(如聊天工具、文件传输、游戏服务器、IoT 通信、Web 服务器等)的基础,它直接与 TCP/IP 协议栈交互,赋予开发者精细控制网络通信的能力。

Winsock 开发核心流程
-
初始化 Winsock 库 (WSAStartup)
任何 Winsock 程序的第一步都是加载和初始化动态链接库 (DLL),使用WSAStartup函数,指定请求的 Winsock 版本(如 2.2)。#include <winsock2.h> #include <ws2tcpip.h> // 用于较新的 IP 地址转换等功能 #pragma comment(lib, "ws2_32.lib") // 链接 Winsock 库 WSADATA wsaData; int result = WSAStartup(MAKEWORD(2, 2), &wsaData); if (result != 0) { printf("WSAStartup failed: %dn", result); return 1; } // 检查返回的版本是否 >= 请求的版本 (2.2) if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { printf("Could not find a usable version of Winsock.dlln"); WSACleanup(); return 1; } -
创建套接字 (socket)
套接字是网络通信的端点。socket函数指定地址族(通常是AF_INET或AF_INET6)、套接字类型(SOCK_STREAM用于 TCP,SOCK_DGRAM用于 UDP)和协议(通常为 0,表示默认协议)。// 创建 TCP 套接字 SOCKET ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (ListenSocket == INVALID_SOCKET) { printf("socket failed with error: %ldn", WSAGetLastError()); WSACleanup(); return 1; } -
绑定套接字到地址和端口 (bind)
服务器需要告诉操作系统它将在哪个本地 IP 地址和端口上监听连接。bind函数将套接字与一个本地地址结构 (sockaddr_in或sockaddr_in6) 关联。struct sockaddr_in service; service.sin_family = AF_INET; service.sin_addr.s_addr = INADDR_ANY; // 绑定到所有本地接口 service.sin_port = htons(27015); // 端口号,htons 确保网络字节序 if (bind(ListenSocket, (SOCKADDR)&service, sizeof(service)) == SOCKET_ERROR) { printf("bind failed with error: %ldn", WSAGetLastError()); closesocket(ListenSocket); WSACleanup(); return 1; } -
监听连接 (listen – TCP 服务器)
(TCP 专用) 服务器调用listen使套接字进入被动监听状态,准备接受客户端的连接请求,第二个参数backlog指定等待连接队列的最大长度。if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) { printf("listen failed with error: %ldn", WSAGetLastError()); closesocket(ListenSocket); WSACleanup(); return 1; } printf("Server listening on port 27015...n"); -
接受连接 (accept – TCP 服务器)
(TCP 专用)accept函数从监听队列中取出一个连接请求,创建一个新的套接字专门用于与这个客户端通信,原监听套接字继续监听新连接。SOCKET ClientSocket = INVALID_SOCKET; ClientSocket = accept(ListenSocket, NULL, NULL); // 通常需要传入 sockaddr 结构获取客户端地址 if (ClientSocket == INVALID_SOCKET) { printf("accept failed with error: %ldn", WSAGetLastError()); closesocket(ListenSocket); WSACleanup(); return 1; } printf("Client connected!n"); // 此时可以使用 ClientSocket 与特定客户端通信 // ListenSocket 依然用于 accept 其他客户端 -
连接到服务器 (connect – TCP 客户端 / UDP 可选)

- TCP 客户端: 使用
connect函数主动发起与服务器的连接请求。 - UDP:
connect在 UDP 中是可选的,用于为后续的send/recv调用固定远程地址,避免每次都指定,但并非建立真正的连接。
// TCP 客户端示例 SOCKET ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); ... // (错误检查) struct sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; InetPton(AF_INET, TEXT("127.0.0.1"), &serverAddr.sin_addr); // 转换IP地址 serverAddr.sin_port = htons(27015); if (connect(ConnectSocket, (SOCKADDR)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) { printf("Unable to connect to server!n"); closesocket(ConnectSocket); WSACleanup(); return 1; } printf("Connected to server.n"); - TCP 客户端: 使用
-
发送和接收数据 (send / recv, sendto / recvfrom)
- TCP (连接导向): 使用
send和recv,数据被视为可靠的字节流。send不保证一次性发完所有数据,需要检查返回值。recv可能返回少于请求的数据量。 - UDP (无连接): 使用
sendto(需指定目标地址) 和recvfrom(返回数据来源地址),数据以独立的数据报形式传输,可能丢失、重复或乱序,每个sendto对应一个完整的数据报。recvfrom一次接收一个数据报。
// TCP 发送示例 const char sendbuf = "Hello from client!"; int bytesSent = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0); if (bytesSent == SOCKET_ERROR) { ... } // TCP 接收示例 char recvbuf[512]; int bytesRecv = recv(ClientSocket, recvbuf, sizeof(recvbuf), 0); if (bytesRecv > 0) { recvbuf[bytesRecv] = ' - TCP (连接导向): 使用