C++ - 网络编程之初始连接(Winsock2 概述、初始连接案例、初始连接案例解读)
一、Winsock2 概述
- Winsock2(Windows Sockets 2)是微软提供的 Windows 平台网络编程库
二、初始连接案例
1、Server
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>#pragma comment(lib, "ws2_32.lib")using namespace std;const int listen_port = 12345;int main() {WSADATA wsaData;int WSAStartupResult = WSAStartup(MAKEWORD(2, 2), &wsaData);if (WSAStartupResult != 0) {cerr << "WSAStartup() failed: " << WSAStartupResult << endl;return 1;}SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (listenSocket == INVALID_SOCKET) {cerr << "socket() failed: " << WSAGetLastError() << endl;WSACleanup();return 1;}sockaddr_in serverAddr;serverAddr.sin_family = AF_INET;serverAddr.sin_port = htons(listen_port);serverAddr.sin_addr.s_addr = INADDR_ANY;int bindResult = bind(listenSocket, (sockaddr*)&serverAddr, sizeof(serverAddr));if (bindResult == SOCKET_ERROR) {cerr << "bind() failed: " << WSAGetLastError() << endl;closesocket(listenSocket);WSACleanup();return 1;}if (listen(listenSocket, SOMAXCONN) == SOCKET_ERROR) {cerr << "listen() failed: " << WSAGetLastError() << endl;closesocket(listenSocket);WSACleanup();return 1;}cout << "Server is listening on port " << listen_port << endl;sockaddr_in clientAddr;int clientAddrSize = sizeof(clientAddr);SOCKET clientSocket = accept(listenSocket, (sockaddr*)&clientAddr, &clientAddrSize);if (clientSocket == INVALID_SOCKET) {cerr << "accept() failed: " << WSAGetLastError() << endl;}char clientIP[INET_ADDRSTRLEN];inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, INET_ADDRSTRLEN);cout << "Client connected from " << clientIP << ":" << ntohs(clientAddr.sin_port) << endl;return 0;
}
2、Client
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>#pragma comment(lib, "ws2_32.lib")using namespace std;int main() {WSADATA wsaData;int WSAStartupResult = WSAStartup(MAKEWORD(2, 2), &wsaData);if (WSAStartupResult != 0) {cerr << "WSAStartup() failed: " << WSAStartupResult << endl;return 1;}SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (sock == INVALID_SOCKET) {cerr << "socket() failed: " << WSAGetLastError() << endl;WSACleanup();return 1;}sockaddr_in serverAddr;serverAddr.sin_family = AF_INET;serverAddr.sin_port = htons(12345);inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);int ConnectionResult = connect(sock, (sockaddr*)&serverAddr, sizeof(serverAddr));if (ConnectionResult == SOCKET_ERROR) {cerr << "connect() failed: " << WSAGetLastError() << endl;closesocket(sock);WSACleanup();return 1;}cout << "Successfully connected to server!" << endl;closesocket(sock);WSACleanup();return 0;
}
3、Test
- 启动 Server,输出结果
# ServerServer is listening on port 12345
- 启动 Client,输出结果
# ClientSuccessfully connected to server!
# ServerClient connected from 127.0.0.1:63154
三、初始连接案例解读
1、Server
(1)头文件与库引入
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>#pragma comment(lib, "ws2_32.lib")using namespace std;
-
winsock2.h
是 Winsock 编程的核心头文件,包含了大部分套接字函数和数据结构的定义 -
ws2tcpip.h
提供 IP 地址转换等功能 -
iostream
标准输入输出 -
#pragma comment
指令用于链接ws2_32.lib
库 -
using namespace
启用命名空间std
(2)初始化 Winsock 库
WSADATA wsaData;
int WSAStartupResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (WSAStartupResult != 0) {cerr << "WSAStartup() failed: " << WSAStartupResult << endl;return 1;
}
-
WSADATA
结构用于接收库的详细信息 -
WSAStartup()
是 Winsock 程序的第一个调用,用于初始化库,MAKEWORD(2, 2)
表示初始化 Winsock 2.2 版本 -
必须检查返回值,失败时应立即退出
(3)创建监听套接字
SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listenSocket == INVALID_SOCKET) {cerr << "socket() failed: " << WSAGetLastError() << endl;WSACleanup();return 1;
}
-
socket()
用于创建套接字,失败时返回INVALID_SOCKET
-
AF_INET
表示使用 IPv4 地址族,SOCK_STREAM
表示面向连接的 TCP 套接字,IPPROTO_TCP
表示指定 TCP 协议 -
失败时调用
WSAGetLastError()
获取错误代码,然后调用WSACleanup()
释放资源
(4)设置服务端地址
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(listen_port);
serverAddr.sin_addr.s_addr = INADDR_ANY;
-
sockaddr_in
结构用于指定服务器地址信息 -
sin_family
表示地址族(IPv4),sin_port
表示端口号(使用htons
将主机字节序转换为网络字节序),sin_addr.s_addr
使用的INADDR_ANY
表示绑定到所有可用接口
(5)绑定套接字
int bindResult = bind(listenSocket, (sockaddr*)&serverAddr, sizeof(serverAddr));
if (bindResult == SOCKET_ERROR) {cerr << "bind() failed: " << WSAGetLastError() << endl;closesocket(listenSocket);WSACleanup();return 1;
}
-
bind()
将套接字与特定 IP 和端口关联,如果未发生错误,绑定返回 0,否则,返回SOCKET_ERROR
-
失败时调用
WSAGetLastError()
获取错误代码,然后调用closesocket()
关闭现有套接字,然后调用WSACleanup()
释放资源
(6)开始监听
if (listen(listenSocket, SOMAXCONN) == SOCKET_ERROR) {cerr << "listen() failed: " << WSAGetLastError() << endl;closesocket(listenSocket);WSACleanup();return 1;
}cout << "Server is listening on port " << listen_port << endl;
-
listen()
使套接字进入监听状态,如果未发生错误,返回 0,否则,返回SOCKET_ERROR
-
SOMAXCONN
是系统允许的最大挂起连接数 -
失败时调用
WSAGetLastError()
获取错误代码,然后调用closesocket()
关闭现有套接字,然后调用WSACleanup()
释放资源
(7)接受客户端连接
sockaddr_in clientAddr;
int clientAddrSize = sizeof(clientAddr);
SOCKET clientSocket = accept(listenSocket, (sockaddr*)&clientAddr, &clientAddrSize);
if (clientSocket == INVALID_SOCKET) {cerr << "accept() failed: " << WSAGetLastError() << endl;
}
-
clientAddr
结构用于获取客户端地址信息 -
accept()
接受传入的连接请求,阻塞直到有客户端连接,返回一个新的套接字用于与客户端通信
(8)显示客户端信息
char clientIP[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, INET_ADDRSTRLEN);
cout << "Client connected from " << clientIP << ":" << ntohs(clientAddr.sin_port) << endl;
-
inet_ntop()
将二进制 IP 地址转换为可读字符串 -
ntohs()
将网络字节序的端口转换为主机字节序
2、Client
(1)头文件与库引入
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>#pragma comment(lib, "ws2_32.lib")using namespace std;
-
winsock2.h
是 Winsock 编程的核心头文件,包含了大部分套接字函数和数据结构的定义 -
ws2tcpip.h
提供 IP 地址转换等功能 -
iostream
标准输入输出 -
#pragma comment
指令用于链接ws2_32.lib
库 -
using namespace
启用命名空间std
(2)初始化 Winsock 库
WSADATA wsaData;
int WSAStartupResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (WSAStartupResult != 0) {cerr << "WSAStartup() failed: " << WSAStartupResult << endl;return 1;
}
(3)创建套接字
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == INVALID_SOCKET) {cerr << "socket() failed: " << WSAGetLastError() << endl;WSACleanup();return 1;
}
-
socket()
用于创建套接字,失败时返回INVALID_SOCKET
-
AF_INET
表示使用 IPv4 地址族,SOCK_STREAM
表示面向连接的 TCP 套接字,IPPROTO_TCP
表示指定 TCP 协议 -
失败时调用
WSAGetLastError()
获取错误代码,然后调用WSACleanup()
释放资源
(4)设置服务端地址
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(12345);
inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);
-
sockaddr_in
结构用于指定服务器地址信息 -
sin_family
表示地址族(IPv4),sin_port
表示端口号(使用htons
将主机字节序转换为网络字节序),sin_addr
使用inet_pton
将字符串 IP 转换为二进制格式
(5)连接服务端
int ConnectionResult = connect(sock, (sockaddr*)&serverAddr, sizeof(serverAddr));
if (ConnectionResult == SOCKET_ERROR) {cerr << "connect() failed: " << WSAGetLastError() << endl;closesocket(sock);WSACleanup();return 1;
}
-
connect()
发起连接,如果成功,返回 0,否则,返回SOCKET_ERROR
-
失败时调用
WSAGetLastError()
获取错误代码,然后调用closesocket()
关闭现有套接字,然后调用WSACleanup()
释放资源