You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++ SOCKET默认协议解析及客户端Socket关闭逻辑疑问

C++网络编程聊天应用疑问解答

我正在学习C++以了解网络编程的基础原理及系统工作机制,为此跟着教程实现了一个聊天应用,代码如下:

ChatServer

#include <iostream>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <tchar.h>
#include <thread>
#include <vector>

using namespace std;

#pragma comment(lib, "ws2_32.lib")

/*
    Initialize winsock Library
    Create Socket
    Get IP and Port
    Bind IP/Porrt with Socket
    Listen on the socket
    Accept 
    Rec and Send
    Close the Socket
    Clean up the Lib
*/

bool Initialization() {
    WSADATA data;
    return WSAStartup(MAKEWORD(2, 2), &data) == 0;
}

void InteractionWithClient(SOCKET clientSocket, vector<SOCKET>& clients) {
    char buffer[4096];
    while (true) {
        int bytesReceived = recv(clientSocket, buffer, sizeof(buffer), 0);
        if (bytesReceived <= 0) {
            cout << "Client Dosconnected\n";
            break;
        }
        string message(buffer, bytesReceived);
        cout << "Message from Client: " << message << endl;

        for (auto client : clients) {
            if(client != clientSocket) 
                send(client, message.c_str(), message.length(), 0);
        }
    }
    auto it = find(clients.begin(), clients.end(), clientSocket);
    if (it != clients.end())
        clients.erase(it);
    closesocket(clientSocket);
}

int main() {
    const int PORT = 12345;
    if (!Initialization()) {
        cout << "WinSock Initialization Failed " << endl;
        return 1;
    }

    cout << "Chat Server" << endl;

    SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (listenSocket == INVALID_SOCKET) {
        cout << "Socket Creation Failed " << endl;
        return 1;
    }

    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(PORT);


    if (InetPton(AF_INET, _T("0.0.0.0"), &serverAddr.sin_addr) != 1) {
        cout << "Setting Address structure Failed" << endl;
        closesocket(listenSocket);
        WSACleanup();
        return 1;
    }

    if (bind(listenSocket, reinterpret_cast<sockaddr*>(&serverAddr), sizeof serverAddr) == SOCKET_ERROR) {
        cout << "Socket Binding to Address Failed" << endl;
        closesocket(listenSocket);
        WSACleanup();
        return 1;
    }

    if (listen(listenSocket, SOMAXCONN) == SOCKET_ERROR) {
        cout << "Server Listening Failed" << endl;
        closesocket(listenSocket);
        WSACleanup();
        return 1;
    }

    cout << "Server Started on Port " << PORT << endl;

    vector<SOCKET> clients;
    while (true) {
        SOCKET clientSocket = accept(listenSocket, nullptr, nullptr);
        if (clientSocket == INVALID_SOCKET) {
            cout << "Invalid Client Socket" << endl;
        }

        clients.push_back(clientSocket);
        thread t(InteractionWithClient, clientSocket, std::ref(clients));
        t.detach();
    }
    
    closesocket(listenSocket);
    WSACleanup();
    return 0;
}

Client

#include <iostream>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <tchar.h>
#include <thread>

using namespace std;

#pragma comment(lib, "ws2_32.lib")

/*
    Initialize winsock Library
    Create Socket
    Get IP and Port
    Bind IP/Porrt with Socket
    Listen on the socket
    Accept
    Rec and Send
    Close the Socket
    Clean up the Lib
*/

bool Initialization() {
    WSADATA data;
    return WSAStartup(MAKEWORD(2, 2), &data) == 0;
}

void SendMessageToServer(SOCKET s) {
    cout << "SendMessageToServer:: socket " << s << endl;
    cout << "Enter You Chat name:\t";
    string name;
    getline(cin, name);
    string message;

    while (true) {
        cout << "Message:: \t";
        getline(cin, message);

        if (message == "quit") {
            cout << "Stopping the application\n";
            break;
        }

        message = name + " :: " + message;
        int bytesSent = send(s, message.c_str(), message.length(), 0);
        if (bytesSent == SOCKET_ERROR) {
            cout << "Error Sending Message\n";
            break;
        }
    }

    closesocket(s);
    WSACleanup();
}

void ReceiveMessage(SOCKET s) {
    cout << "ReceiveMessage:: socket " << s << endl;
    char buffer[4096];
    int recvLength;
    string msg;
    while (true) {
        recvLength = recv(s, buffer, sizeof(buffer), 0);
        if (recvLength <= 0) {
            cout << "Disconned from the Server\n";
            break;
        }
        msg = string(buffer, recvLength);
        cout << msg << endl;
    }

    closesocket(s);
    WSACleanup();
}

int main() {
    const int PORT = 12345;
    if (!Initialization()) {
        cout << "WinSock Initialization Failed " << endl;
        return 1;
    }

    cout << "Chat Server" << endl;

    SOCKET s = socket(AF_INET, SOCK_STREAM, 0);
    if (s == INVALID_SOCKET) {
        cout << "Socket Creation Failed " << endl;
        return 1;
    }

    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(PORT);
    InetPton(AF_INET, _T("127.0.0.1"), &serverAddr.sin_addr);

    if (connect(s, reinterpret_cast<sockaddr*>(&serverAddr), sizeof serverAddr) == SOCKET_ERROR) {
        cout << "Socket Binding to Server Address Failed" << endl;
        closesocket(s);
        WSACleanup();
        return 1;
    }


    cout << "Successfully connected to server\n";
    thread senderThread(SendMessageToServer, s);
    thread receiverThread(ReceiveMessage, s);

    senderThread.join();
    receiverThread.join();
    return 0;
}

疑问解答

1. C++中的SOCKET默认使用什么协议?是WebSocket、HTTP还是其他?从实现来看似乎是WebSocket?

你的代码里创建套接字的调用是 socket(AF_INET, SOCK_STREAM, 0):

  • 第三个参数填0表示使用该套接字类型对应的默认传输层协议,对于SOCK_STREAM(流式套接字)+AF_INET(IPv4地址族),默认协议是TCP。
  • WebSocket和HTTP都属于应用层协议,它们是基于TCP构建的上层规范,你的代码直接使用了底层的TCP套接字,并没有实现WebSocket或HTTP的应用层规则,因此和这两个协议无关。

2. 客户端代码中,为何要在SendMessageToServer和ReceiveMessage两个线程函数中分别调用closesocket(s)?能否仅在main函数中调用一次?

目前代码里的写法存在问题:

  • Windows下的SOCKET句柄带有引用计数,每次调用closesocket会减少计数,只有当计数归0时才会真正关闭套接字。两个线程持有同一个SOCKET句柄,各自调用closesocket会导致第一次调用仅减少计数,套接字并未真正关闭,第二次调用才会完成关闭,这种写法容易引发不可预期的线程同步问题。
  • 更严重的是WSACleanup,整个进程只能调用一次,两个线程都调用会导致后续的Winsock操作失败,甚至程序崩溃。

正确的做法是:

  • 不需要在两个线程中分别调用closesocket和WSACleanup。当一个线程触发退出(比如输入quit),调用closesocket后,另一个线程的recv会返回错误(字节数<=0),从而退出循环。然后在main函数中,等两个线程都结束后,统一调用一次closesocket和WSACleanup即可。
  • 如果担心线程同步问题,可以用全局标志或原子变量来通知另一个线程主动退出,再统一清理资源。

学习资源

  • 《TCP/IP详解 卷1:协议》:深入理解TCP/IP协议栈的核心原理,是网络编程的基础读物
  • 《Windows网络编程》:针对WinSock API的详细讲解,覆盖Windows平台下套接字开发的各类场景
  • C20标准库网络模块:现代C原生的网络编程接口,无需依赖第三方库,适合学习现代网络编程范式
  • 微软官方WinSock文档:Windows平台套接字API的权威参考,涵盖API细节和最佳实践

内容的提问来源于stack exchange,提问作者Himanshu Singh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 05:40:38