一、Socket(套接字)
1. 介绍
Socket(套接字)是 网络编程的抽象接口,用于实现不同设备(或进程)之间的网络通信。它本质上是一个“通信端点”,通过操作系统提供的 API 屏蔽了底层网络协议的复杂细节,让开发者可以通过简单的函数调用实现数据传输。
- 作用:作为应用程序与网络协议栈之间的桥梁,负责数据的发送、接收和连接管理。
- 分类:
- 流式Socket(SOCK_STREAM):基于 TCP 协议,提供可靠、面向连接的通信(如代码中使用)。
- 数据报Socket(SOCK_DGRAM):基于 UDP 协议,提供不可靠、无连接的通信。
2. Socket 服务端和客户端核心区别
- 服务器:先启动 → 绑定固定地址 → 监听 → 被动接受连接;
- 客户端:后启动 → 无需固定地址 → 主动发起连接。
- 服务器(Server):被动等待连接的一方
- 核心行为:先启动,通过
bind()绑定固定的地址(IP + 端口),再通过listen()进入“监听状态”,最后通过accept()阻塞等待客户端的连接请求。 - 形象比喻:如同“开门营业的商店”,先固定地址(绑定端口),打开门(监听),等待顾客(客户端)上门。
- 核心行为:先启动,通过
- 客户端(Client):主动发起连接的一方
- 核心行为:后启动,无需绑定固定地址(系统会自动分配临时端口),直接通过
connect()向服务器的固定地址(已知的 IP + 端口)发起连接请求。 - 形象比喻:如同“上门购物的顾客”,知道商店地址(服务器 IP + 端口),主动前往连接。
这种区分是“逻辑角色”的划分,而非物理设备的差异,同一台机器上可以同时运行多个服务器和客户端进程。
- 核心行为:后启动,无需绑定固定地址(系统会自动分配临时端口),直接通过
二、Socket通讯在OSI七层模型中的层级
1. Socket 本地通讯(同一设备内进程间通信)
本地通讯通常不经过物理传输,核心是进程间的数据交互,涉及以下层级:
- 应用层
- 作用:由应用程序(如客户端/服务端进程)定义通讯的数据格式和逻辑,通过 Socket API(如
send()/recv())发起交互。 - 举例:进程 A 向进程 B 发送特定格式的消息(如 JSON 数据)。
- 作用:由应用程序(如客户端/服务端进程)定义通讯的数据格式和逻辑,通过 Socket API(如
- 传输层
- 作用:本地通讯可能使用 TCP 或 UDP 协议(也可能简化为更轻量的本地协议,如 Unix 域套接字),负责数据的分片、组装和端口标识(区分同一设备上的不同进程)。
- 举例:通过端口号定位目标进程,确保数据准确交付到对应进程。
- 网络层及以下
- 本地通讯不涉及实际的网络路由和物理传输,因此网络层(IP 地址)、数据链路层、物理层通常不参与,或仅用本地回环地址(如 127.0.0.1)象征性标识。
2. Socket 跨终端通讯(不同设备间通信)
跨终端通讯需要通过网络传输,涉及的层级更完整:
- 应用层
- 作用:同本地通讯,应用程序通过 Socket API 定义数据格式和交互逻辑(如 HTTP、FTP 等基于 Socket 的协议)。
- 传输层
- 作用:使用 TCP 或 UDP 协议,负责端到端的数据可靠传输(TCP 有确认、重传机制)或快速传输(UDP 无连接,效率高),通过端口号区分目标设备上的进程。
- 网络层
- 作用:通过 IP 协议进行路由选择,根据目标 IP 地址确定数据从源设备到目标设备的传输路径。
- 举例:通过路由器转发数据,跨越不同子网。
- 数据链路层
- 作用:将网络层的 IP 数据包封装成帧,通过 MAC 地址在局域网内传输,处理数据的差错校验和链路管理。
- 举例:以太网中通过 MAC 地址识别同一局域网内的设备。
- 物理层
- 作用:将数据链路层的帧转换为电信号、光信号等物理信号,通过网线、无线电磁波等物理介质传输。
本地通讯聚焦于“进程间数据交互”,主要依赖应用层和传输层;跨终端通讯需“跨设备传输”,因此额外涉及网络层、数据链路层和物理层,完成路由、封装和物理信号传输。
- 作用:将数据链路层的帧转换为电信号、光信号等物理信号,通过网线、无线电磁波等物理介质传输。
三、Socket技术与TCP的关系
- Socket 是通信接口:是操作系统提供的一套用于进程间通信的 API(如
socket()、bind()、send()等),定义了数据收发的规范,本身不绑定特定协议。 - TCP 是传输层协议:是 Socket 可选用的传输方式之一(另一种是 UDP)。
- 当 Socket 使用
SOCK_STREAM类型时,底层默认使用 TCP 协议(提供可靠、有序的字节流传输); - 当使用
SOCK_DGRAM类型时,底层使用 UDP 协议(提供无连接、不可靠的数据包传输)。
- 当 Socket 使用
TCP 是 Socket 实现跨设备可靠通信的一种手段,而 Socket 可以不依赖 TCP(如本地的 Unix 域套接字)。跨进程通信中,Socket 是更通用的技术:本地用 Unix 域套接字(高效),跨设备用 TCP/UDP 网络 Socket(兼容网络)。
四、Socket通信的底层逻辑
Socket 跨进程通信的底层逻辑是通过内核中的“通信端点”和“协议栈转发” 实现。
1. 核心前提:Socket 的本质是“内核中的通信端点”
无论本地还是跨设备,Socket 本质是操作系统内核为进程创建的**“通信端点”**(可理解为内核中的一个“数据缓冲区 + 状态管理结构”)。进程通过 socket() 系统调用向内核申请这个端点,后续的 bind()(绑定标识)、send()(发数据)、recv()(收数据)都是对这个内核端点的操作。
- 每个 Socket 端点有唯一标识
本地用“文件路径”(Unix 域套接字)或“回环 IP + 端口”,跨设备用“IP + 端口”。 - 数据传输的核心
进程将数据写入自己的 Socket 端点,内核负责将数据从发送端端点“搬运”到接收端端点,接收进程再从自己的端点中读取数据。
Socket 的核心实现和关键操作主要由操作系统内核负责,但用户进程通过系统调用与内核交互来使用 Socket 功能,整体可以理解为“内核提供底层支持,用户进程通过接口调用”。具体可从以下两方面看: - 内核层面:
当进程调用socket()创建套接字时,内核会在内部生成一个管理通信的结构体(包含缓冲区、协议信息、状态等),并维护这个“通信端点”的生命周期。后续的bind()(绑定地址)、listen()(监听连接)、send()/recv()(收发数据)等操作,本质是进程通过系统调用通知内核,由内核完成实际的地址绑定、数据缓冲、协议处理(如 TCP 握手、数据分片)等核心工作。 - 用户进程层面:
进程仅持有一个“文件描述符”(在 Unix 系统中,Socket 被抽象为特殊的文件描述符),作为访问内核中 Socket 结构体的“接口”。用户进程无需关心底层协议细节,只需通过这个描述符调用系统函数,触发内核的对应操作。
简单说:Socket 的“实体”存在于内核中,用户进程通过系统调用“委托”内核完成通信相关的底层工作,自身仅处理数据的应用层逻辑。
2. 以本地跨进程通信(以Unix 域套接字)为例的底层逻辑
数据发送阶段
- 发送进程调用
send(),数据从用户态内存(进程的缓冲区)拷贝到内核中发送端 Socket 的缓冲区(内核态)。 - 内核根据 Socket 绑定的“文件路径”(Unix 域套接字的标识),找到对应的接收端 Socket 端点(内核中已通过
bind()和listen()关联)。 - 内核直接将数据从发送端缓冲区拷贝到接收端 Socket 的缓冲区(全程在内核中完成,不经过网络协议栈,无额外封装)。
数据接收阶段
- 接收进程调用
recv(),内核将接收端 Socket 缓冲区中的数据拷贝到接收进程的用户态内存(进程自己的缓冲区)。 - 整个过程:用户态 → 内核态(发送端)→ 内核态(接收端)→ 用户态,无物理传输,效率极高。
3. 跨设备跨进程通信(基于 TCP)的底层逻辑
TCP 在此场景中负责数据的可靠封装、传输和重组,核心是“协议栈逐层处理 + 物理介质传输”。
发送端:数据从进程到物理介质
- 应用层:进程通过
send()将数据写入发送端 Socket 缓冲区(内核态)。 - 传输层(TCP):
- 内核的 TCP 模块将数据拆分成“TCP 段”(添加源端口、目标端口、序列号、校验和等头部),确保有序和可靠(如超时重传、确认机制)。
- 网络层(IP):
- 给 TCP 段添加“IP 头部”(源 IP、目标 IP、路由信息等),形成“IP 数据包”,通过路由算法确定传输路径。
- 数据链路层:
- 给 IP 数据包添加“MAC 头部”(源 MAC、目标 MAC)和尾部,形成“数据帧”,通过局域网内的交换机/路由器转发。
- 物理层:
- 将数据帧转换为电信号/光信号,通过网线、无线电磁波等物理介质传输到目标设备。
接收端:数据从物理介质到进程
- 物理层:目标设备的网卡接收物理信号,转换为数据帧传递给数据链路层。
- 数据链路层:校验帧的完整性,剥离 MAC 头部,将 IP 数据包交给网络层。
- 网络层(IP):校验 IP 头部,剥离后将 TCP 段交给传输层。
- 传输层(TCP):
- 校验 TCP 段的序列号和完整性,重组为完整数据(处理乱序、丢包等问题),存入接收端 Socket 缓冲区(内核态)。
- 应用层:接收进程调用
recv(),内核将 Socket 缓冲区中的数据拷贝到进程的用户态内存,完成接收。
4. 总结
- 核心机制:
Socket 跨进程通信的底层是内核中“通信端点”的关联 + 数据在内核态的拷贝/转发(本地),或协议栈逐层封装 + 物理传输 + 逐层解封装(跨设备)。文件句柄指向磁盘文件或设备文件,而 Socket 端点是内核中专门用于进程间通信的抽象结构,虽在 Unix 系统中 Socket 会被映射为一个“文件描述符”(类似句柄的整数标识),但本质是通信端点的引用,而非文件。 - TCP 的角色:
在跨设备场景中,TCP 是传输层的“搬运工 + 质检员”,负责数据的可靠拆分、封装和重组,确保数据从发送端 Socket 到接收端 Socket 的有序、完整传输。
本地通信靠内核“直接搬运”,跨设备通信靠 TCP/IP 协议栈“打包快递 + 送货上门”,最终都是通过双方的 Socket 端点完成数据交接。
五、Socket技术的使用流程(以TCP为例)
1. 服务端流程
① 初始化网络环境(如Windows的WSAStartup)
② 创建Socket:socket(AF_INET, SOCK_STREAM, 0) // AF_INET表示IPv4,SOCK_STREAM表示TCP
③ 绑定地址和端口:bind(Socket, 服务器地址, 地址长度)
④ 开始监听连接:listen(Socket, 最大等待队列)
⑤ 接受客户端连接:accept(Socket, 客户端地址, 地址长度) // 返回与客户端通信的新Socket
⑥ 收发数据:recv(客户端Socket, 缓冲区, 长度, 0) / send(客户端Socket, 数据, 长度, 0)
⑦ 关闭Socket:closesocket(Socket)
⑧ 清理网络环境(如WSACleanup)
2. 客户端流程
① 初始化网络环境
② 创建Socket:socket(AF_INET, SOCK_STREAM, 0)
③ 连接服务器:connect(Socket, 服务器地址, 地址长度)
④ 收发数据:send(Socket, 数据, 长度, 0) / recv(Socket, 缓冲区, 长度, 0)
⑤ 关闭Socket:closesocket(Socket)
⑥ 清理网络环境
六、Socket实现
实现一个基于 TCP 协议 的简易即时通信系统,包含服务端和客户端
1. 伪代码实现
服务端伪代码
1. 初始化网络环境(Winsock)和窗口界面
2. 创建服务器Socket,绑定端口(8888)并开始监听客户端连接
3. 启动线程循环接受客户端连接:
- 客户端连接后,将其加入客户端列表
- 为每个客户端Socket设置异步消息通知(接收消息/断开连接)
4. 窗口界面功能:
- 列表框显示连接状态和消息
- 输入框和发送按钮:向所有已连接客户端广播消息
5. 处理客户端消息:接收后显示并转发给其他客户端
6. 关闭时清理资源(关闭所有Socket,释放网络环境)
客户端伪代码
1. 初始化网络环境(Winsock)和窗口界面
2. 窗口界面功能:
- 连接按钮:创建客户端Socket,连接服务器(127.0.0.1:8888)
- 发送按钮:向服务器发送消息(连接后可用)
- 列表框显示连接状态和收发消息
3. 接收服务器消息:异步监听并显示在列表框
4. 断开连接时清理资源
2. C++实现
服务端
// server.cpp
#include <winsock2.h>
#include <windows.h>
#include <string>
#include <vector>
#include <mutex>
#include <commctrl.h>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "comctl32.lib")
#define SERVER_PORT 8888
#define BUFFER_SIZE 1024
#define WM_SOCKET (WM_USER + 1)
#define IDC_LISTBOX 1001
#define IDC_SEND_BUTTON 1002
#define IDC_MESSAGE_EDIT 1003
std::vector<std::string> messages;
std::vector<SOCKET> clients;
std::mutex messageMutex;
std::mutex clientMutex;
HWND hListBox, hMessageEdit, hSendButton;
SOCKET serverSocket;
bool isRunning = false;
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
DWORD WINAPI AcceptThread(LPVOID lpParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
// 初始化COMCTL32库
INITCOMMONCONTROLSEX icc;
icc.dwSize = sizeof(icc);
icc.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&icc);
// 初始化Winsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
MessageBox(NULL, "WSAStartup failed!", "Error", MB_ICONERROR);
return 1;
}
// 注册窗口类
WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = "ServerWindowClass";
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
if (!RegisterClass(&wc)) {
MessageBox(NULL, "Window registration failed!", "Error", MB_ICONERROR);
return 1;
}
// 创建窗口
HWND hwnd = CreateWindow(
wc.lpszClassName, "TCP Server", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 600, 400,
NULL, NULL, hInstance, NULL
);
if (hwnd == NULL) {
MessageBox(NULL, "Window creation failed!", "Error", MB_ICONERROR);
return 1;
}
// 显示窗口
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// 创建服务器套接字
serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket == INVALID_SOCKET) {
MessageBox(hwnd, "Socket creation failed!", "Error", MB_ICONERROR);
return 1;
}
// 设置服务器地址
sockaddr_in serverAddr = {};
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = htons(SERVER_PORT);
// 绑定套接字
if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
MessageBox(hwnd, "Bind failed!", "Error", MB_ICONERROR);
closesocket(serverSocket);
return 1;
}
// 监听连接
if (listen(serverSocket, 5) == SOCKET_ERROR) {
MessageBox(hwnd, "Listen failed!", "Error", MB_ICONERROR);
closesocket(serverSocket);
return 1;
}
isRunning = true;
// 添加启动消息
std::string startMsg = "Server started. Listening on port " + std::to_string(SERVER_PORT);
{
std::lock_guard<std::mutex> lock(messageMutex);
messages.push_back(startMsg);
SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)startMsg.c_str());
}
// 创建接受连接的线程
CreateThread(NULL, 0, AcceptThread, (LPVOID)hwnd, 0, NULL);
// 消息循环
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// 清理资源
isRunning = false;
closesocket(serverSocket);
// 关闭所有客户端连接
{
std::lock_guard<std::mutex> lock(clientMutex);
for (SOCKET client : clients) {
closesocket(client);
}
clients.clear();
}
WSACleanup();
return (int)msg.wParam;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_CREATE: {
// 创建列表框
hListBox = CreateWindow(
"LISTBOX", "",
WS_CHILD | WS_VISIBLE | WS_BORDER | LBS_STANDARD | WS_VSCROLL,
10, 10, 560, 280, hwnd, (HMENU)IDC_LISTBOX, NULL, NULL
);
// 创建消息输入框
hMessageEdit = CreateWindow(
"EDIT", "",
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE | ES_AUTOHSCROLL | ES_AUTOVSCROLL,
10, 300, 400, 50, hwnd, (HMENU)IDC_MESSAGE_EDIT, NULL, NULL
);
// 创建发送按钮
hSendButton = CreateWindow(
"BUTTON", "Send",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
420, 300, 70, 50, hwnd, (HMENU)IDC_SEND_BUTTON, NULL, NULL
);
// 添加初始消息
std::string initialMsg = "Server initializing...";
{
std::lock_guard<std::mutex> lock(messageMutex);
messages.push_back(initialMsg);
SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)initialMsg.c_str());
}
break;
}
case WM_COMMAND: {
if (LOWORD(wParam) == IDC_SEND_BUTTON && HIWORD(wParam) == BN_CLICKED) {
char buffer[BUFFER_SIZE] = {};
GetWindowText(hMessageEdit, buffer, BUFFER_SIZE);
std::string message = buffer;
if (message.empty()) {
break;
}
// 清空输入框
SetWindowText(hMessageEdit, "");
// 更新界面
std::string displayMsg = "Server: " + message;
{
std::lock_guard<std::mutex> lock(messageMutex);
messages.push_back(displayMsg);
SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)displayMsg.c_str());
SendMessage(hListBox, LB_SETTOPINDEX, SendMessage(hListBox, LB_GETCOUNT, 0, 0) - 1, 0);
}
// 发送消息给所有客户端
{
std::lock_guard<std::mutex> lock(clientMutex);
for (SOCKET client : clients) {
send(client, message.c_str(), message.length(), 0);
}
}
}
break;
}
case WM_SOCKET: {
if (WSAGETSELECTERROR(lParam)) {
// 处理错误
SOCKET clientSocket = (SOCKET)wParam;
{
std::lock_guard<std::mutex> lock(clientMutex);
for (auto it = clients.begin(); it != clients.end(); ++it) {
if (*it == clientSocket) {
closesocket(clientSocket);
clients.erase(it);
std::string disconnectMsg = "Client disconnected. Total clients: " + std::to_string(clients.size());
{
std::lock_guard<std::mutex> lock(messageMutex);
messages.push_back(disconnectMsg);
SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)disconnectMsg.c_str());
SendMessage(hListBox, LB_SETTOPINDEX, SendMessage(hListBox, LB_GETCOUNT, 0, 0) - 1, 0);
}
break;
}
}
}
break;
}
switch (WSAGETSELECTEVENT(lParam)) {
case FD_READ: {
// 接收客户端消息
SOCKET clientSocket = (SOCKET)wParam;
char buffer[BUFFER_SIZE] = {};
int bytesReceived = recv(clientSocket, buffer, BUFFER_SIZE, 0);
if (bytesReceived > 0) {
std::string clientMsg = "Client: " + std::string(buffer, bytesReceived);
{
std::lock_guard<std::mutex> lock(messageMutex);
messages.push_back(clientMsg);
SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)clientMsg.c_str());
SendMessage(hListBox, LB_SETTOPINDEX, SendMessage(hListBox, LB_GETCOUNT, 0, 0) - 1, 0);
}
}
else {
// 客户端断开连接
{
std::lock_guard<std::mutex> lock(clientMutex);
for (auto it = clients.begin(); it != clients.end(); ++it) {
if (*it == clientSocket) {
closesocket(clientSocket);
clients.erase(it);
std::string disconnectMsg = "Client disconnected. Total clients: " + std::to_string(clients.size());
{
std::lock_guard<std::mutex> lock(messageMutex);
messages.push_back(disconnectMsg);
SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)disconnectMsg.c_str());
SendMessage(hListBox, LB_SETTOPINDEX, SendMessage(hListBox, LB_GETCOUNT, 0, 0) - 1, 0);
}
break;
}
}
}
}
break;
}
}
break;
}
case WM_DESTROY:
isRunning = false;
closesocket(serverSocket);
// 关闭所有客户端连接
{
std::lock_guard<std::mutex> lock(clientMutex);
for (SOCKET client : clients) {
closesocket(client);
}
clients.clear();
}
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
DWORD WINAPI AcceptThread(LPVOID lpParam) {
HWND hwnd = (HWND)lpParam;
while (isRunning) {
// 接受客户端连接
sockaddr_in clientAddr = {};
int clientAddrLen = sizeof(clientAddr);
SOCKET clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, &clientAddrLen);
if (clientSocket == INVALID_SOCKET) {
if (!isRunning) break; // 服务器正在关闭
continue;
}
// 添加客户端到列表
{
std::lock_guard<std::mutex> lock(clientMutex);
clients.push_back(clientSocket);
}
// 设置异步模式
WSAAsyncSelect(clientSocket, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);
// 显示客户端连接信息
std::string clientIP = inet_ntoa(clientAddr.sin_addr);
int clientPort = ntohs(clientAddr.sin_port);
std::string connectMsg = "Client connected: " + clientIP + ":" + std::to_string(clientPort) +
". Total clients: " + std::to_string(clients.size());
{
std::lock_guard<std::mutex> lock(messageMutex);
messages.push_back(connectMsg);
SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)connectMsg.c_str());
SendMessage(hListBox, LB_SETTOPINDEX, SendMessage(hListBox, LB_GETCOUNT, 0, 0) - 1, 0);
}
}
return 0;
}
客户端
// client.cpp
#include <winsock2.h>
#include <windows.h>
#include <string>
#include <vector>
#include <mutex>
#include <commctrl.h>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "comctl32.lib")
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8888
#define BUFFER_SIZE 1024
#define WM_SOCKET (WM_USER + 1)
#define IDC_LISTBOX 1001
#define IDC_SEND_BUTTON 1002
#define IDC_MESSAGE_EDIT 1003
#define IDC_CONNECT_BUTTON 1004
std::vector<std::string> messages;
std::mutex messageMutex;
HWND hListBox, hMessageEdit, hSendButton, hConnectButton;
SOCKET clientSocket;
bool isConnected = false;
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
DWORD WINAPI ReceiveThread(LPVOID lpParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
// 初始化COMCTL32库
INITCOMMONCONTROLSEX icc;
icc.dwSize = sizeof(icc);
icc.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&icc);
// 初始化Winsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
MessageBox(NULL, "WSAStartup failed!", "Error", MB_ICONERROR);
return 1;
}
// 注册窗口类
WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = "ClientWindowClass";
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
if (!RegisterClass(&wc)) {
MessageBox(NULL, "Window registration failed!", "Error", MB_ICONERROR);
return 1;
}
// 创建窗口
HWND hwnd = CreateWindow(
wc.lpszClassName, "TCP Client", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 600, 400,
NULL, NULL, hInstance, NULL
);
if (hwnd == NULL) {
MessageBox(NULL, "Window creation failed!", "Error", MB_ICONERROR);
return 1;
}
// 显示窗口
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// 消息循环
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// 清理资源
if (isConnected) {
closesocket(clientSocket);
}
WSACleanup();
return (int)msg.wParam;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_CREATE: {
// 创建列表框
hListBox = CreateWindow(
"LISTBOX", "",
WS_CHILD | WS_VISIBLE | WS_BORDER | LBS_STANDARD | WS_VSCROLL,
10, 10, 560, 280, hwnd, (HMENU)IDC_LISTBOX, NULL, NULL
);
// 创建消息输入框
hMessageEdit = CreateWindow(
"EDIT", "",
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE | ES_AUTOHSCROLL | ES_AUTOVSCROLL,
10, 300, 400, 50, hwnd, (HMENU)IDC_MESSAGE_EDIT, NULL, NULL
);
// 创建发送按钮
hSendButton = CreateWindow(
"BUTTON", "Send",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
420, 300, 70, 50, hwnd, (HMENU)IDC_SEND_BUTTON, NULL, NULL
);
EnableWindow(hSendButton, FALSE);
// 创建连接按钮
hConnectButton = CreateWindow(
"BUTTON", "Connect",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
500, 300, 70, 50, hwnd, (HMENU)IDC_CONNECT_BUTTON, NULL, NULL
);
// 添加初始消息
std::string initialMsg = "Client started. Click Connect to join server.";
messages.push_back(initialMsg);
SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)initialMsg.c_str());
break;
}
case WM_COMMAND: {
if (LOWORD(wParam) == IDC_CONNECT_BUTTON && HIWORD(wParam) == BN_CLICKED) {
if (!isConnected) {
// 创建客户端套接字
clientSocket = socket(AF_INET, SOCK_STREAM, 0);
if (clientSocket == INVALID_SOCKET) {
MessageBox(hwnd, "Socket creation failed!", "Error", MB_ICONERROR);
break;
}
// 设置服务器地址
sockaddr_in serverAddr = {};
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
serverAddr.sin_port = htons(SERVER_PORT);
// 连接服务器
if (connect(clientSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
std::string errorMsg = "Connect failed! Error code: " + std::to_string(WSAGetLastError());
MessageBox(hwnd, errorMsg.c_str(), "Error", MB_ICONERROR);
closesocket(clientSocket);
break;
}
// 设置异步模式
WSAAsyncSelect(clientSocket, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);
// 更新UI状态
isConnected = true;
SetWindowText(hConnectButton, "Disconnect");
EnableWindow(hSendButton, TRUE);
// 添加连接成功消息
std::string connectMsg = "Connected to server: " + std::string(SERVER_IP) + ":" + std::to_string(SERVER_PORT);
{
std::lock_guard<std::mutex> lock(messageMutex);
messages.push_back(connectMsg);
SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)connectMsg.c_str());
SendMessage(hListBox, LB_SETTOPINDEX, SendMessage(hListBox, LB_GETCOUNT, 0, 0) - 1, 0);
}
// 创建接收线程
CreateThread(NULL, 0, ReceiveThread, (LPVOID)hwnd, 0, NULL);
}
else {
// 断开连接
closesocket(clientSocket);
isConnected = false;
SetWindowText(hConnectButton, "Connect");
EnableWindow(hSendButton, FALSE);
// 添加断开连接消息
std::string disconnectMsg = "Disconnected from server";
{
std::lock_guard<std::mutex> lock(messageMutex);
messages.push_back(disconnectMsg);
SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)disconnectMsg.c_str());
SendMessage(hListBox, LB_SETTOPINDEX, SendMessage(hListBox, LB_GETCOUNT, 0, 0) - 1, 0);
}
}
}
else if (LOWORD(wParam) == IDC_SEND_BUTTON && HIWORD(wParam) == BN_CLICKED) {
if (!isConnected) {
MessageBox(hwnd, "Not connected to server!", "Error", MB_ICONERROR);
break;
}
char buffer[BUFFER_SIZE] = {};
GetWindowText(hMessageEdit, buffer, BUFFER_SIZE);
std::string message = buffer;
if (message.empty()) {
break;
}
// 清空输入框
SetWindowText(hMessageEdit, "");
// 更新界面
std::string displayMsg = "You: " + message;
{
std::lock_guard<std::mutex> lock(messageMutex);
messages.push_back(displayMsg);
SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)displayMsg.c_str());
SendMessage(hListBox, LB_SETTOPINDEX, SendMessage(hListBox, LB_GETCOUNT, 0, 0) - 1, 0);
}
// 发送消息
send(clientSocket, message.c_str(), message.length(), 0);
}
break;
}
case WM_SOCKET: {
if (WSAGETSELECTERROR(lParam)) {
MessageBox(hwnd, "Socket error!", "Error", MB_ICONERROR);
closesocket(clientSocket);
isConnected = false;
SetWindowText(hConnectButton, "Connect");
EnableWindow(hSendButton, FALSE);
break;
}
switch (WSAGETSELECTEVENT(lParam)) {
case FD_READ: {
char buffer[BUFFER_SIZE] = {};
int bytesReceived = recv(clientSocket, buffer, BUFFER_SIZE, 0);
if (bytesReceived > 0) {
std::string message = "Server: " + std::string(buffer, bytesReceived);
{
std::lock_guard<std::mutex> lock(messageMutex);
messages.push_back(message);
SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)message.c_str());
SendMessage(hListBox, LB_SETTOPINDEX, SendMessage(hListBox, LB_GETCOUNT, 0, 0) - 1, 0);
}
}
break;
}
case FD_CLOSE: {
closesocket(clientSocket);
isConnected = false;
SetWindowText(hConnectButton, "Connect");
EnableWindow(hSendButton, FALSE);
std::string disconnectMsg = "Server disconnected";
{
std::lock_guard<std::mutex> lock(messageMutex);
messages.push_back(disconnectMsg);
SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)disconnectMsg.c_str());
SendMessage(hListBox, LB_SETTOPINDEX, SendMessage(hListBox, LB_GETCOUNT, 0, 0) - 1, 0);
}
break;
}
}
break;
}
case WM_DESTROY:
if (isConnected) {
closesocket(clientSocket);
}
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
DWORD WINAPI ReceiveThread(LPVOID lpParam) {
HWND hwnd = (HWND)lpParam;
while (isConnected) {
// 实际应用中可以在这里添加更复杂的接收逻辑
Sleep(100);
}
return 0;
}
3. 编译
# 编译服务器程序
g++ server.cpp -o server.exe -lws2_32 -lcomctl32 -mwindows
# 编译客户端程序
g++ client.cpp -o client.exe -lws2_32 -lcomctl32 -mwindows
Socket技术提供了编程接口,TCP协议保证了通信的可靠性,两者结合实现了服务端与客户端之间的即时消息交互。Socket是网络编程的基础,而TCP是Socket支持的一种核心协议(适用于需要可靠传输的场景,如聊天、文件传输等)。
1609

被折叠的 条评论
为什么被折叠?



