1. 关于 Servers 和 Clients
有两种不同类型的套接字网络应用程序:服务器和客户端。
服务器和客户端的行为不同;因此,创建它们的流程也是不一样的。以下是用于创建流式 TCP/IP 服务器和客户端的一般模型。
1.1 Server
-
Initialize Winsock.
-
Create a socket.
-
Bind the socket.
-
Listen on the socket for a client.
-
Accept a connection from a client.
-
Receive and send data.
-
Disconnect.
1.2 Client
-
Initialize Winsock.
-
Create a socket.
-
Connect to the server.
-
Send and receive data.
-
Disconnect.
2. Create a Basic Winsock Application
创建一个基本的 Winsock 应用需要基于以下条件:
-
确保编译环境可以引用 SDK 的 include,Lib 和 Src 目录。
-
确保编译环境可以链接到 Winsock 库文件(Ws2_32.lib)。可以通过 #pragma comment(lib, “ws2_32.lib”) 显式的链接。
-
编程开始前,使用 Winsock API 要包含 Winsock2.h 头文件,它包含了常用的 Winsock 的函数,结构体和定义。Ws2tcpip.h 头文件包含 WinSock 2 协议特定附件文档中针对 TCP/IP 引入的定义,其中包括用于检索 IP 地址的较新的函数和结构。
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")
int main()
{
return 0;
}
Note: 如果需要使用 IP Helper APIs,需要包含 lphlpapi.h 头文件,但必须置于 Winsock2.h 头文件之后。 Winsock2.h 头文件内部包含了 Windows.h 中的核心元素,因此不需要再包含 Windows.h 头文件。如果需要包含 Windows.h 头文件,则需要定义宏 #define WIN32_LEAN_AND_MEAN。由于历史原因,Windows.h 标头默认包含 Windows Socket 1.1 的 Winsock.h 头文件。Winsock.h 头文件中的声明将与 Windows Socket 2.0 所需的Winsock2.h 头文件中的声明冲突。WIN32_LEAN_AND_MEAN 宏可防止 Winsock.h 包含在 Windows.h 头文件中。下面示例了一个说明这一点。
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#pragma comment(lib, "Ws2_32.lib")
int main()
{
return 0;
}
3. Initializing Winsock
调用 Winsock 函数的所有进程(应用或者 DLLs)都必须先初始化 Windows Sockets DLL。
这也用于确认系统是否支持 Winsock。
3.1 初始化 Winsock
- 创建一个 WSADATA 对象,名为 wsaData。
WSADATA wsaData;
- 调用 WSAStartup 函数并检查其返回值。
int iResult;
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0)
{
printf("WSAStartup failed: %d\n", iResult);
return 1;
}
调用 WSAStartup 函数以启用 WS2_32.dll。
WSADATA 结构体包含 Windows Sockets 的信息。WSAStartup 的 MAKEWORD(2,2) 参数在系统上发出对 Winsock 版本 2.2 的请求,并将传递的版本设置为调用方可以使用的最高版本的 Windows Sockets 支持。
4A. Winsock server application
4A.1 Create a Socket for Server
在初始化后,必须实例化 SOCKET 对象以供服务器使用。
4A.1.1 To create a socket for the server
- [getaddrinfo](getaddrinfo function (ws2tcpip.h) - Win32 apps | Microsoft Learn) 函数用于决定 [sockaddr](sockaddr - Win32 apps | Microsoft Learn) 结构体中的值:
-
AF_INET 用于指定 IPv4 地址家族。
-
SOCK_STREAM 用于指定一个流 socket。
-
IPPROTO_TCP 用于指定 TCP 协议。
-
AI_PASSIVE flag 表明调用方打算在调用 bind 函数时使用返回 socket 地址结构。当 AI_PASSOVE flag 置位,并传给 getaddrinfo 函数的 nodename 参数为 NULL 指针,对于 IPv4 地址,socket 地址结构的 IP 地址部分为 INADDR_ANY。 而 IPv6 地址,socket 地址结构的 IP 地址部分为 IN6ADDR_ANY_INIT。
-
27015 是与客户端将连接到的服务器关联的端口号。
// addrinfo 结构体被 getaddrinfo 函数使用。
#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,用于服务器监听客户端的连接。
SOCKET ListenSocket = INVALID_SOCKET;
- 调用 socket 函数,ListenSocket 变量用于接收函数返回值。对于这个服务器应用程序,采用 getaddrinfo 函数返回的第一个与 hints 参数设置相匹配的 IP 地址。
如果服务器应用程序希望监听 IPv6,则将 hints.ai_family = AF_INET6 即可。如果一个服务器希望同时监听 IPv4 和 IPv6,则必须创建两个监听 sockets。一个 IPv4,一个 IPv6,并且应用程序必须单独处理这两个 sockets 。
// Create a SOCKET for the server to listen for client connections
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
- 检查 socket,确保创建的 socket 有效。
if (ListenSocket == INVALID_SOCKET)
{
printf("Error at socket(): %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
4A.2 Binding a Socket
要使服务器接受客户端连接,它必须绑定到系统中的网络地址。客户端应用程序使用 IP 地址和端口连接到主机网络。
sockaddr 结构体保存着 address family, IP address 和 port number。
调用 bind 函数,传入已创建的 socket 和 从 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;
}
Note: 一旦 bind 函数被调用,getaddrinfo 函数返回的 address 信息将不再需要。调用 freeaddrinfo 函数释放 getaddrinfo 函数分配的内存空间。
freeaddrinfo(result);
4A.3 Listening on a Socket
将 socket 绑定到系统上的 IP 地址和端口后,服务器必须侦听该 IP 地址和端口以接收传入的连接请求。
调用 listen 函数,将创建的 socket 和 blacklog(要接受的挂起连接的队列的最大长度)值作为参数传递给 listen 函数。以下示例中,backlog 值设置为 SOMAXCONN。该值是一个特殊常量,指示 Winsock 提供程序为该 socket 允许队列中最大数量的挂起连接。检查常规错误的返回值。文章来源:https://www.toymoban.com/news/detail-444212.html
if ( listen( ListenSocket, SOMAXCONN ) == SOCKET_ERROR )
{
printf( "Listen failed with error: %ld\n", WSAGetLastError() );
closesocket(ListenSocket);
WSACleanup();
return 1;
}
4A.4 Accepting a Connection
socket 监听连接后,程序必须处理该 socket 上的连接请求。文章来源地址https://www.toymoban.com/news/detail-444212.html
4A.4.1 To accept a connection on a socket
- 创建一个临时的 SOCKET
到了这里,关于Windows 上的网络通信编程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!