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
相关产品推荐
相关产品推荐

