Socket通讯

一、Socket(套接字)

1. 介绍

Socket(套接字)是 网络编程的抽象接口,用于实现不同设备(或进程)之间的网络通信。它本质上是一个“通信端点”,通过操作系统提供的 API 屏蔽了底层网络协议的复杂细节,让开发者可以通过简单的函数调用实现数据传输。

  • 作用:作为应用程序与网络协议栈之间的桥梁,负责数据的发送、接收和连接管理。
  • 分类
    • 流式Socket(SOCK_STREAM):基于 TCP 协议,提供可靠、面向连接的通信(如代码中使用)。
    • 数据报Socket(SOCK_DGRAM):基于 UDP 协议,提供不可靠、无连接的通信。
2. Socket 服务端和客户端核心区别
  • 服务器:先启动 → 绑定固定地址 → 监听 → 被动接受连接;
  • 客户端:后启动 → 无需固定地址 → 主动发起连接。
  1. 服务器(Server):被动等待连接的一方
    • 核心行为:先启动,通过 bind() 绑定固定的地址(IP + 端口),再通过 listen() 进入“监听状态”,最后通过 accept() 阻塞等待客户端的连接请求。
    • 形象比喻:如同“开门营业的商店”,先固定地址(绑定端口),打开门(监听),等待顾客(客户端)上门。
  2. 客户端(Client):主动发起连接的一方
    • 核心行为:后启动,无需绑定固定地址(系统会自动分配临时端口),直接通过 connect() 向服务器的固定地址(已知的 IP + 端口)发起连接请求。
    • 形象比喻:如同“上门购物的顾客”,知道商店地址(服务器 IP + 端口),主动前往连接。
      这种区分是“逻辑角色”的划分,而非物理设备的差异,同一台机器上可以同时运行多个服务器和客户端进程。

二、Socket通讯在OSI七层模型中的层级

1. Socket 本地通讯(同一设备内进程间通信)

本地通讯通常不经过物理传输,核心是进程间的数据交互,涉及以下层级:

  1. 应用层
    • 作用:由应用程序(如客户端/服务端进程)定义通讯的数据格式和逻辑,通过 Socket API(如 send()/recv())发起交互。
    • 举例:进程 A 向进程 B 发送特定格式的消息(如 JSON 数据)。
  2. 传输层
    • 作用:本地通讯可能使用 TCP 或 UDP 协议(也可能简化为更轻量的本地协议,如 Unix 域套接字),负责数据的分片、组装和端口标识(区分同一设备上的不同进程)。
    • 举例:通过端口号定位目标进程,确保数据准确交付到对应进程。
  3. 网络层及以下
    • 本地通讯不涉及实际的网络路由和物理传输,因此网络层(IP 地址)、数据链路层、物理层通常不参与,或仅用本地回环地址(如 127.0.0.1)象征性标识。
2. Socket 跨终端通讯(不同设备间通信)

跨终端通讯需要通过网络传输,涉及的层级更完整:

  1. 应用层
    • 作用:同本地通讯,应用程序通过 Socket API 定义数据格式和交互逻辑(如 HTTP、FTP 等基于 Socket 的协议)。
  2. 传输层
    • 作用:使用 TCP 或 UDP 协议,负责端到端的数据可靠传输(TCP 有确认、重传机制)或快速传输(UDP 无连接,效率高),通过端口号区分目标设备上的进程。
  3. 网络层
    • 作用:通过 IP 协议进行路由选择,根据目标 IP 地址确定数据从源设备到目标设备的传输路径。
    • 举例:通过路由器转发数据,跨越不同子网。
  4. 数据链路层
    • 作用:将网络层的 IP 数据包封装成帧,通过 MAC 地址在局域网内传输,处理数据的差错校验和链路管理。
    • 举例:以太网中通过 MAC 地址识别同一局域网内的设备。
  5. 物理层
    • 作用:将数据链路层的帧转换为电信号、光信号等物理信号,通过网线、无线电磁波等物理介质传输。
      本地通讯聚焦于“进程间数据交互”,主要依赖应用层和传输层;跨终端通讯需“跨设备传输”,因此额外涉及网络层、数据链路层和物理层,完成路由、封装和物理信号传输。

三、Socket技术与TCP的关系

  • Socket 是通信接口:是操作系统提供的一套用于进程间通信的 API(如 socket()bind()send() 等),定义了数据收发的规范,本身不绑定特定协议。
  • TCP 是传输层协议:是 Socket 可选用的传输方式之一(另一种是 UDP)。
    • 当 Socket 使用 SOCK_STREAM 类型时,底层默认使用 TCP 协议(提供可靠、有序的字节流传输);
    • 当使用 SOCK_DGRAM 类型时,底层使用 UDP 协议(提供无连接、不可靠的数据包传输)。

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支持的一种核心协议(适用于需要可靠传输的场景,如聊天、文件传输等)。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值