当前位置: 首页 > news >正文

C++ 在 Windows 下实现最基础的 WebSocket 服务端与客户端

一、前言

在现代 Web 开发中,WebSocket 已成为实现实时通信的重要技术。它能在浏览器和服务器之间建立全双工长连接,相比传统的 HTTP 轮询,大大减少了延迟与网络开销。
常见应用场景包括:即时聊天、在线游戏、实时推送、协同编辑等等。

虽然已经有成熟的 C++ WebSocket 库(如 Boost.BeastWebSocket++),但对于学习者来说,理解协议本身更有价值。
因此本文将带你使用 C++ + Winsock2 在 Windows 上实现一个最小可运行的 WebSocket 服务端和客户端,直观感受握手与帧传输的底层细节。


二、WebSocket 协议简述

在正式写代码前,先简要了解 WebSocket 的两个关键点:

1. 握手(Handshake)

WebSocket 连接的第一步是一次 HTTP 升级请求
客户端发出:

GET /chat HTTP/1.1
Host: 127.0.0.1:8080
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: 随机字符串(Base64)
Sec-WebSocket-Version: 13

服务器验证后返回:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: (基于客户端 Key + GUID 计算的值)

这样 HTTP 协议就升级为 WebSocket 通道。

2. 数据帧(Frame)

之后所有通信都通过 WebSocket 帧完成:

  • 每一帧都有 FIN、Opcode、Payload 长度、掩码等字段

  • 客户端发给服务端必须带掩码,而服务端发给客户端则不用。

  • 常见的 Opcode

    • 0x1:文本帧(UTF-8)

    • 0x2:二进制帧

    • 0x8:关闭连接

    • 0x9:Ping

    • 0xA:Pong

理解这两个环节,几乎就掌握了 WebSocket 的“心脏”。


三、服务端实现

下面是一个最小化的 WebSocket 服务端,功能包括:

  • 监听 8080 端口;

  • 接受客户端连接并完成握手;

  • 接收文本消息并原样回显;

  • 处理 ping/pong/close 控制帧。

👉  websocket_server.cpp

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <thread>
#include <mutex>
#include <algorithm>
#include <cstdint>
#include <cstring>#pragma comment(lib, "ws2_32.lib")// -------------------- Base64 --------------------
static const char b64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";std::string base64_encode(const unsigned char* data, size_t len) {std::string out;out.reserve(((len + 2) / 3) * 4);unsigned int val = 0;int valb = -6;for (size_t i = 0; i < len; ++i) {val = (val << 8) + data[i];valb += 8;while (valb >= 0) {out.push_back(b64_table[(val >> valb) & 0x3F]);valb -= 6;}}if (valb > -6) out.push_back(b64_table[((val << 8) >> (valb + 8)) & 0x3F]);while (out.size() % 4) out.push_back('=');return out;
}// -------------------- SHA1 --------------------
typedef uint32_t u32;
typedef uint8_t u8;
inline u32 rol(u32 value, unsigned int bits) { return (value << bits) | (value >> (32 - bits)); }void sha1(const unsigned char* data, size_t len, unsigned char out[20]) {u32 h0 = 0x67452301, h1 = 0xEFCDAB89, h2 = 0x98BADCFE, h3 = 0x10325476, h4 = 0xC3D2E1F0;size_t new_len = len + 1;while (new_len % 64 != 56) ++new_len;std::vector<unsigned char> msg(new_len + 8);memcpy(msg.data(), data, len);msg[len] = 0x80;uint64_t bits = (uint64_t)len * 8;for (int i = 0; i < 8; ++i) msg[new_len + i] = (unsigned char)((bits >> ((7 - i) * 8)) & 0xFF);for (size_t chunk = 0; chunk < msg.size(); chunk += 64) {u32 w[80];for (int i = 0; i < 16; ++i) {w[i] = (msg[chunk + i * 4 + 0] << 24) | (msg[chunk + i * 4 + 1] << 16) |(msg[chunk + i * 4 + 2] << 8) | (msg[chunk + i * 4 + 3]);}for (int i = 16; i < 80; ++i) w[i] = rol(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);u32 a = h0, b = h1, c = h2, d = h3, e = h4;for (int i = 0; i < 80; ++i) {u32 f, k;if (i < 20) { f = (b & c) | ((~b) & d); k = 0x5A827999; }else if (i < 40) { f = b ^ c ^ d; k = 0x6ED9EBA1; }else if (i < 60) { f = (b & c) | (b & d) | (c & d); k = 0x8F1BBCDC; }else { f = b ^ c ^ d; k = 0xCA62C1D6; }u32 temp = rol(a, 5) + f + e + k + w[i];e = d; d = c; c = rol(b, 30); b = a; a = temp;}h0 += a; h1 += b; h2 += c; h3 += d; h4 += e;}u32 hs[5] = { h0,h1,h2,h3,h4 };for (int i = 0; i < 5; ++i) {out[i * 4 + 0] = (hs[i] >> 24) & 0xFF;out[i * 4 + 1] = (hs[i] >> 16) & 0xFF;out[i * 4 + 2] = (hs[i] >> 8) & 0xFF;out[i * 4 + 3] = (hs[i] >> 0) & 0xFF;}
}// -------------------- Utilities --------------------
std::string recv_all(SOCKET s) {std::string data;char buf[2048];while (true) {int r = recv(s, buf, sizeof(buf), 0);if (r <= 0) return data;data.append(buf, buf + r);if (data.find("\r\n\r\n") != std::string::npos) break;}return data;
}std::string get_header_value(const std::string& headers, const std::string& name) {std::string lower = headers;std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);std::string lname = name;std::transform(lname.begin(), lname.end(), lname.begin(), ::tolower);size_t pos = lower.find(lname + ":");if (pos == std::string::npos) return "";pos += lname.size() + 1;while (pos < lower.size() && (lower[pos] == ' ' || lower[pos] == '\t')) ++pos;size_t end = lower.find("\r\n", pos);if (end == std::string::npos) return "";std::string val = headers.substr(pos, end - pos);while (!val.empty() && (val.back() == '\r' || val.back() == '\n' || val.back() == ' ')) val.pop_back();return val;
}bool read_exact(SOCKET s, unsigned char* buf, size_t len) {size_t got = 0;while (got < len) {int r = recv(s, (char*)buf + got, (int)(len - got), 0);if (r <= 0) return false;got += r;}return true;
}bool recv_ws_frame(SOCKET s, std::string& out_payload, uint8_t& opcode, bool& fin) {unsigned char hdr[2];if (!read_exact(s, hdr, 2)) return false;fin = (hdr[0] & 0x80) != 0;opcode = hdr[0] & 0x0F;bool masked = (hdr[1] & 0x80) != 0;uint64_t payload_len = hdr[1] & 0x7F;if (payload_len == 126) {unsigned char ext[2];if (!read_exact(s, ext, 2)) return false;payload_len = (ext[0] << 8) | ext[1];}else if (payload_len == 127) {unsigned char ext[8];if (!read_exact(s, ext, 8)) return false;payload_len = 0;for (int i = 0; i < 8; ++i) payload_len = (payload_len << 8) | ext[i];}unsigned char mask_key[4] = { 0,0,0,0 };if (masked) {if (!read_exact(s, mask_key, 4)) return false;}out_payload.clear();out_payload.resize(payload_len);if (payload_len > 0) {if (!read_exact(s, (unsigned char*)out_payload.data(), payload_len)) return false;if (masked) {for (uint64_t i = 0; i < payload_len; ++i) {out_payload[i] = out_payload[i] ^ mask_key[i % 4];}}}return true;
}bool send_ws_frame(SOCKET s, const std::string& payload, uint8_t opcode = 1, bool fin = true) {std::vector<unsigned char> frame;uint8_t b0 = (fin ? 0x80 : 0x00) | (opcode & 0x0F);frame.push_back(b0);size_t len = payload.size();if (len <= 125) frame.push_back((uint8_t)len);else if (len <= 0xFFFF) {frame.push_back(126);frame.push_back((len >> 8) & 0xFF);frame.push_back((len >> 0) & 0xFF);}else {frame.push_back(127);for (int i = 7; i >= 0; --i) frame.push_back((len >> (8 * i)) & 0xFF);}frame.insert(frame.end(), payload.begin(), payload.end());int sent = send(s, (const char*)frame.data(), (int)frame.size(), 0);return sent == (int)frame.size();
}// -------------------- 多客户端管理 --------------------
std::vector<SOCKET> clients;
std::mutex clients_mutex;void broadcast(const std::string& message, SOCKET exclude = INVALID_SOCKET) {std::lock_guard<std::mutex> lock(clients_mutex);for (auto it = clients.begin(); it != clients.end(); ) {SOCKET client = *it;if (client != exclude && !send_ws_frame(client, message)) {closesocket(client);it = clients.erase(it);}else {++it;}}
}// -------------------- 客户端处理 --------------------
void handle_client(SOCKET client) {// 握手std::string req = recv_all(client);std::string key = get_header_value(req, "Sec-WebSocket-Key");if (key.empty()) { closesocket(client); return; }const std::string GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";unsigned char sha_out[20];sha1((const unsigned char*)(key + GUID).c_str(), key.size() + GUID.size(), sha_out);std::string accept_val = base64_encode(sha_out, 20);std::ostringstream resp;resp << "HTTP/1.1 101 Switching Protocols\r\n"<< "Upgrade: websocket\r\n"<< "Connection: Upgrade\r\n"<< "Sec-WebSocket-Accept: " << accept_val << "\r\n\r\n";std::string resp_str = resp.str();send(client, resp_str.c_str(), (int)resp_str.size(), 0);{std::lock_guard<std::mutex> lock(clients_mutex);clients.push_back(client);}bool running = true;while (running) {std::string payload;uint8_t opcode;bool fin;if (!recv_ws_frame(client, payload, opcode, fin)) break;if (opcode == 0x1) {std::cout << "Received: " << payload << "\n";broadcast("Broadcast: " + payload, client);}else if (opcode == 0x8) {send_ws_frame(client, "", 0x8);break;}else if (opcode == 0x9) {send_ws_frame(client, payload, 0xA);}}closesocket(client);{std::lock_guard<std::mutex> lock(clients_mutex);clients.erase(std::remove(clients.begin(), clients.end(), client), clients.end());}std::cout << "Client disconnected.\n";
}void push_messages_loop() {int counter = 1;while (true) {std::string msg = "Server message #" + std::to_string(counter++);{std::lock_guard<std::mutex> lock(clients_mutex);for (auto it = clients.begin(); it != clients.end(); ) {SOCKET client = *it;if (!send_ws_frame(client, msg)) {closesocket(client);it = clients.erase(it);} else {++it;}}}std::this_thread::sleep_for(std::chrono::seconds(5));}
}// -------------------- Main --------------------
int main() {WSADATA wsa;if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) return 1;SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);int reuse = 1;setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse));sockaddr_in srv{};srv.sin_family = AF_INET;srv.sin_addr.s_addr = INADDR_ANY;srv.sin_port = htons(8080);bind(listen_sock, (sockaddr*)&srv, sizeof(srv));listen(listen_sock, SOMAXCONN);std::cout << "WebSocket server listening on port 8080...\n";// 启动服务端主动推送线程std::thread(push_messages_loop).detach();while (true) {SOCKET client = accept(listen_sock, nullptr, nullptr);if (client != INVALID_SOCKET) {std::thread(handle_client, client).detach();}}closesocket(listen_sock);WSACleanup();return 0;
}

编译方式(Visual Studio):

cl /EHsc websocket_server.cpp ws2_32.lib

四、客户端实现

客户端的任务:

  • 主动连接服务端;

  • 发送握手请求并解析响应;

  • 发送一条消息;

  • 接收服务器回显;

  • 发送关闭帧并退出。

👉  websocket_client.cpp

// websocket_client.cpp
// 简单 WebSocket 客户端(Windows)
// 编译:
//   cl /EHsc websocket_client.cpp ws2_32.lib
// 或 MinGW:
//   g++ -std=c++11 websocket_client.cpp -lws2_32 -o websocket_client.exe#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <random>
#include <cstdint>#pragma comment(lib, "ws2_32.lib")// ---------------- Base64 (和服务端一样) ----------------
static const char b64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";std::string base64_encode(const unsigned char* data, size_t len) {std::string out;out.reserve(((len + 2) / 3) * 4);unsigned int val = 0;int valb = -6;for (size_t i = 0; i < len; ++i) {val = (val << 8) + data[i];valb += 8;while (valb >= 0) {out.push_back(b64_table[(val >> valb) & 0x3F]);valb -= 6;}}if (valb > -6) out.push_back(b64_table[((val << 8) >> (valb + 8)) & 0x3F]);while (out.size() % 4) out.push_back('=');return out;
}// ---------------- 工具函数 ----------------
bool read_exact(SOCKET s, unsigned char* buf, size_t len) {size_t got = 0;while (got < len) {int r = recv(s, (char*)buf + got, (int)(len - got), 0);if (r <= 0) return false;got += r;}return true;
}// ---------------- WebSocket 帧 ----------------
bool send_ws_frame(SOCKET s, const std::string& payload, uint8_t opcode = 1, bool fin = true) {std::vector<unsigned char> frame;uint8_t b0 = (fin ? 0x80 : 0x00) | (opcode & 0x0F);frame.push_back(b0);size_t len = payload.size();if (len <= 125) {frame.push_back((uint8_t)(0x80 | len)); // mask=1} else if (len <= 0xFFFF) {frame.push_back(0x80 | 126);frame.push_back((len >> 8) & 0xFF);frame.push_back((len >> 0) & 0xFF);} else {frame.push_back(0x80 | 127);for (int i = 7; i >= 0; --i) frame.push_back((len >> (8*i)) & 0xFF);}// 生成 mask keyunsigned char mask_key[4];for (int i = 0; i < 4; ++i) mask_key[i] = rand() % 256;frame.insert(frame.end(), mask_key, mask_key+4);// payload 异或for (size_t i = 0; i < len; ++i) {frame.push_back(payload[i] ^ mask_key[i % 4]);}int sent = send(s, (const char*)frame.data(), (int)frame.size(), 0);return sent == (int)frame.size();
}bool recv_ws_frame(SOCKET s, std::string& out_payload, uint8_t& opcode, bool& fin) {unsigned char hdr[2];if (!read_exact(s, hdr, 2)) return false;fin = (hdr[0] & 0x80) != 0;opcode = hdr[0] & 0x0F;bool masked = (hdr[1] & 0x80) != 0;uint64_t payload_len = hdr[1] & 0x7F;if (payload_len == 126) {unsigned char ext[2];if (!read_exact(s, ext, 2)) return false;payload_len = (ext[0] << 8) | ext[1];} else if (payload_len == 127) {unsigned char ext[8];if (!read_exact(s, ext, 8)) return false;payload_len = 0;for (int i = 0; i < 8; ++i) payload_len = (payload_len << 8) | ext[i];}unsigned char mask_key[4] = {0,0,0,0};if (masked) {if (!read_exact(s, mask_key, 4)) return false;}out_payload.clear();out_payload.resize(payload_len);if (payload_len > 0) {if (!read_exact(s, (unsigned char*)out_payload.data(), payload_len)) return false;if (masked) {for (uint64_t i = 0; i < payload_len; ++i) {out_payload[i] = out_payload[i] ^ mask_key[i % 4];}}}return true;
}// ---------------- main ----------------
int main() {WSADATA wsa;if (WSAStartup(MAKEWORD(2,2), &wsa) != 0) {std::cerr << "WSAStartup failed\n"; return 1;}SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (sock == INVALID_SOCKET) {std::cerr << "socket failed\n"; WSACleanup(); return 1;}sockaddr_in server{};server.sin_family = AF_INET;server.sin_port = htons(8080);inet_pton(AF_INET, "127.0.0.1", &server.sin_addr);if (connect(sock, (sockaddr*)&server, sizeof(server)) == SOCKET_ERROR) {std::cerr << "connect failed\n"; closesocket(sock); WSACleanup(); return 1;}std::cout << "Connected to server.\n";// 生成随机 Sec-WebSocket-Keyunsigned char random_bytes[16];for (int i = 0; i < 16; ++i) random_bytes[i] = rand() % 256;std::string sec_key = base64_encode(random_bytes, 16);// 发送握手请求std::ostringstream req;req << "GET /chat HTTP/1.1\r\n"<< "Host: 127.0.0.1:8080\r\n"<< "Upgrade: websocket\r\n"<< "Connection: Upgrade\r\n"<< "Sec-WebSocket-Key: " << sec_key << "\r\n"<< "Sec-WebSocket-Version: 13\r\n\r\n";std::string req_str = req.str();send(sock, req_str.c_str(), (int)req_str.size(), 0);// 接收握手响应char buf[2048];int r = recv(sock, buf, sizeof(buf)-1, 0);if (r <= 0) { std::cerr << "handshake failed\n"; closesocket(sock); WSACleanup(); return 1; }buf[r] = 0;std::cout << "Handshake response:\n" << buf << "\n";// 发送一条消息std::string msg = "Hello WebSocket Server!";if (!send_ws_frame(sock, msg)) {std::cerr << "failed to send frame\n"; closesocket(sock); WSACleanup(); return 1;}std::cout << "Sent: " << msg << "\n";// 等待回应std::string payload;uint8_t opcode = 0; bool fin = false;if (recv_ws_frame(sock, payload, opcode, fin)) {if (opcode == 0x1) {std::cout << "Received text: " << payload << "\n";} else {std::cout << "Received opcode=" << (int)opcode << "\n";}} else {std::cout << "Failed to receive frame.\n";}// 发送 close 帧send_ws_frame(sock, "", 0x8, true);closesocket(sock);WSACleanup();std::cout << "Client terminated.\n";return 0;
}

编译方式(Visual Studio):

cl /EHsc websocket_client.cpp ws2_32.lib

五、运行测试

  1. 启动服务端

websocket_server.exe

控制台输出:

WebSocket server listening on port 8080. Waiting for a client...
  1. 启动客户端

websocket_client.exe

客户端输出:

Connected to server.
Handshake response:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: xxxxxxxxSent: Hello WebSocket Server!
Received text: Echo: Hello WebSocket Server!
Client terminated.

服务端输出:

Client connected.
Handshake sent. Entering WebSocket receive loop.
Received text: Hello WebSocket Server!

说明服务端与客户端的 WebSocket 通信已成功建立。


六、总结与扩展

本文通过 不到 500 行 C++ 代码,完整演示了 WebSocket 的最小工作流程:

  • 握手阶段:HTTP 协议升级

  • 数据传输:帧格式解析与封装

  • 简单控制帧处理ping/pong/close

七、附录

服务器向客户端持续发送信息

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <thread>
#include <mutex>
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <chrono>#pragma comment(lib, "ws2_32.lib")// -------------------- Base64 --------------------
static const char b64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";std::string base64_encode(const unsigned char* data, size_t len) {std::string out;out.reserve(((len + 2) / 3) * 4);unsigned int val = 0;int valb = -6;for (size_t i = 0; i < len; ++i) {val = (val << 8) + data[i];valb += 8;while (valb >= 0) {out.push_back(b64_table[(val >> valb) & 0x3F]);valb -= 6;}}if (valb > -6) out.push_back(b64_table[((val << 8) >> (valb + 8)) & 0x3F]);while (out.size() % 4) out.push_back('=');return out;
}// -------------------- SHA1 --------------------
typedef uint32_t u32;
typedef uint8_t u8;
inline u32 rol(u32 value, unsigned int bits) { return (value << bits) | (value >> (32 - bits)); }void sha1(const unsigned char* data, size_t len, unsigned char out[20]) {u32 h0 = 0x67452301, h1 = 0xEFCDAB89, h2 = 0x98BADCFE, h3 = 0x10325476, h4 = 0xC3D2E1F0;size_t new_len = len + 1;while (new_len % 64 != 56) ++new_len;std::vector<unsigned char> msg(new_len + 8);memcpy(msg.data(), data, len);msg[len] = 0x80;uint64_t bits = (uint64_t)len * 8;for (int i = 0; i < 8; ++i) msg[new_len + i] = (unsigned char)((bits >> ((7 - i) * 8)) & 0xFF);for (size_t chunk = 0; chunk < msg.size(); chunk += 64) {u32 w[80];for (int i = 0; i < 16; ++i) {w[i] = (msg[chunk + i * 4 + 0] << 24) | (msg[chunk + i * 4 + 1] << 16) |(msg[chunk + i * 4 + 2] << 8) | (msg[chunk + i * 4 + 3]);}for (int i = 16; i < 80; ++i) w[i] = rol(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);u32 a = h0, b = h1, c = h2, d = h3, e = h4;for (int i = 0; i < 80; ++i) {u32 f, k;if (i < 20) { f = (b & c) | ((~b) & d); k = 0x5A827999; }else if (i < 40) { f = b ^ c ^ d; k = 0x6ED9EBA1; }else if (i < 60) { f = (b & c) | (b & d) | (c & d); k = 0x8F1BBCDC; }else { f = b ^ c ^ d; k = 0xCA62C1D6; }u32 temp = rol(a, 5) + f + e + k + w[i];e = d; d = c; c = rol(b, 30); b = a; a = temp;}h0 += a; h1 += b; h2 += c; h3 += d; h4 += e;}u32 hs[5] = { h0,h1,h2,h3,h4 };for (int i = 0; i < 5; ++i) {out[i * 4 + 0] = (hs[i] >> 24) & 0xFF;out[i * 4 + 1] = (hs[i] >> 16) & 0xFF;out[i * 4 + 2] = (hs[i] >> 8) & 0xFF;out[i * 4 + 3] = (hs[i] >> 0) & 0xFF;}
}// -------------------- Utils --------------------
std::string recv_all(SOCKET s) {std::string data;char buf[2048];while (true) {int r = recv(s, buf, sizeof(buf), 0);if (r <= 0) break;data.append(buf, buf + r);if (data.find("\r\n\r\n") != std::string::npos) break;}return data;
}std::string get_header_value(const std::string& headers, const std::string& name) {std::string lower = headers;std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);std::string lname = name;std::transform(lname.begin(), lname.end(), lname.begin(), ::tolower);size_t pos = lower.find(lname + ":");if (pos == std::string::npos) return "";pos += lname.size() + 1;while (pos < lower.size() && (lower[pos] == ' ' || lower[pos] == '\t')) ++pos;size_t end = lower.find("\r\n", pos);if (end == std::string::npos) return "";std::string val = headers.substr(pos, end - pos);while (!val.empty() && (val.back() == '\r' || val.back() == '\n' || val.back() == ' ')) val.pop_back();return val;
}bool read_exact(SOCKET s, unsigned char* buf, size_t len) {size_t got = 0;while (got < len) {int r = recv(s, (char*)buf + got, (int)(len - got), 0);if (r <= 0) return false;got += r;}return true;
}bool recv_ws_frame(SOCKET s, std::string& out_payload, uint8_t& opcode, bool& fin) {unsigned char hdr[2];if (!read_exact(s, hdr, 2)) return false;fin = (hdr[0] & 0x80) != 0;opcode = hdr[0] & 0x0F;bool masked = (hdr[1] & 0x80) != 0;uint64_t payload_len = hdr[1] & 0x7F;if (payload_len == 126) {unsigned char ext[2];if (!read_exact(s, ext, 2)) return false;payload_len = (ext[0] << 8) | (ext[1]);}else if (payload_len == 127) {unsigned char ext[8];if (!read_exact(s, ext, 8)) return false;payload_len = 0;for (int i = 0; i < 8; ++i) payload_len = (payload_len << 8) | ext[i];}unsigned char mask_key[4] = { 0,0,0,0 };if (masked) {if (!read_exact(s, mask_key, 4)) return false;}out_payload.clear();out_payload.resize(payload_len);if (payload_len > 0) {if (!read_exact(s, (unsigned char*)out_payload.data(), payload_len)) return false;if (masked) {for (uint64_t i = 0; i < payload_len; ++i)out_payload[i] ^= mask_key[i % 4];}}return true;
}bool send_ws_frame(SOCKET s, const std::string& payload, uint8_t opcode = 1, bool fin = true) {std::vector<unsigned char> frame;uint8_t b0 = (fin ? 0x80 : 0x00) | (opcode & 0x0F);frame.push_back(b0);size_t len = payload.size();if (len <= 125) frame.push_back((uint8_t)len);else if (len <= 0xFFFF) {frame.push_back(126);frame.push_back((len >> 8) & 0xFF);frame.push_back((len >> 0) & 0xFF);}else {frame.push_back(127);for (int i = 7; i >= 0; --i) frame.push_back((len >> (8 * i)) & 0xFF);}frame.insert(frame.end(), payload.begin(), payload.end());int sent = send(s, (const char*)frame.data(), (int)frame.size(), 0);return sent == (int)frame.size();
}// -------------------- 多客户端管理 --------------------
std::vector<SOCKET> clients;
std::mutex clients_mutex;void broadcast(const std::string& message, SOCKET exclude = INVALID_SOCKET) {std::lock_guard<std::mutex> lock(clients_mutex);for (auto it = clients.begin(); it != clients.end(); ) {SOCKET client = *it;if (client != exclude && !send_ws_frame(client, message)) {closesocket(client);it = clients.erase(it);}else {++it;}}
}// -------------------- 客户端处理 --------------------
void handle_client(SOCKET client) {std::string req = recv_all(client);std::string key = get_header_value(req, "Sec-WebSocket-Key");if (key.empty()) { closesocket(client); return; }const std::string GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";unsigned char sha_out[20];sha1((const unsigned char*)(key + GUID).c_str(), key.size() + GUID.size(), sha_out);std::string accept_val = base64_encode(sha_out, 20);std::ostringstream resp;resp << "HTTP/1.1 101 Switching Protocols\r\n"<< "Upgrade: websocket\r\n"<< "Connection: Upgrade\r\n"<< "Sec-WebSocket-Accept: " << accept_val << "\r\n\r\n";std::string resp_str = resp.str();send(client, resp_str.c_str(), (int)resp_str.size(), 0);{std::lock_guard<std::mutex> lock(clients_mutex);clients.push_back(client);}bool running = true;while (running) {std::string payload;uint8_t opcode;bool fin;if (!recv_ws_frame(client, payload, opcode, fin)) break;if (opcode == 0x1) {std::cout << "Received: " << payload << "\n";broadcast("Broadcast: " + payload, client);}else if (opcode == 0x8) {send_ws_frame(client, "", 0x8);break;}else if (opcode == 0x9) {send_ws_frame(client, payload, 0xA);}}closesocket(client);{std::lock_guard<std::mutex> lock(clients_mutex);clients.erase(std::remove(clients.begin(), clients.end(), client), clients.end());}std::cout << "Client disconnected.\n";
}// -------------------- 服务端主动推送 --------------------
void push_messages_loop() {int counter = 1;while (true) {std::string msg = "Server message #" + std::to_string(counter++);std::lock_guard<std::mutex> lock(clients_mutex);for (auto it = clients.begin(); it != clients.end(); ) {SOCKET client = *it;if (!send_ws_frame(client, msg)) {closesocket(client);it = clients.erase(it);}else {++it;}}std::this_thread::sleep_for(std::chrono::seconds(5)); // 每5秒推送一次}
}// -------------------- Main --------------------
int main() {WSADATA wsa;if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) return 1;SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);int reuse = 1;setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse));sockaddr_in srv{};srv.sin_family = AF_INET;srv.sin_addr.s_addr = INADDR_ANY;srv.sin_port = htons(8080);bind(listen_sock, (sockaddr*)&srv, sizeof(srv));listen(listen_sock, SOMAXCONN);std::cout << "WebSocket server listening on port 8080...\n";// 启动主动推送线程std::thread(push_messages_loop).detach();while (true) {SOCKET client = accept(listen_sock, nullptr, nullptr);if (client != INVALID_SOCKET) {std::thread(handle_client, client).detach();}}closesocket(listen_sock);WSACleanup();return 0;
}
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <string>
#include <thread>
#include <cstdlib>
#include <vector>
#include <sstream>#pragma comment(lib, "ws2_32.lib")static const char b64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";std::string base64_encode(const unsigned char* data, size_t len) {std::string out;out.reserve(((len + 2) / 3) * 4);unsigned int val = 0; int valb = -6;for (size_t i = 0; i < len; i++) {val = (val << 8) + data[i]; valb += 8;while (valb >= 0) { out.push_back(b64_table[(val >> valb) & 0x3F]); valb -= 6; }}if (valb > -6) out.push_back(b64_table[((val << 8) >> (valb + 8)) & 0x3F]);while (out.size() % 4) out.push_back('=');return out;
}// -------------------- WebSocket --------------------
bool read_exact(SOCKET s, unsigned char* buf, size_t len) {size_t got = 0;while (got < len) {int r = recv(s, (char*)buf + got, (int)(len - got), 0);if (r <= 0) return false;got += r;}return true;
}bool send_ws_frame(SOCKET s, const std::string& payload, uint8_t opcode = 1, bool fin = true) {std::vector<unsigned char> frame;uint8_t b0 = (fin ? 0x80 : 0x00) | (opcode & 0x0F);frame.push_back(b0);size_t len = payload.size();if (len <= 125) frame.push_back(0x80 | len);else if (len <= 0xFFFF) {frame.push_back(0x80 | 126);frame.push_back((len >> 8) & 0xFF);frame.push_back((len >> 0) & 0xFF);}else {frame.push_back(0x80 | 127);for (int i = 7; i >= 0; i--) frame.push_back((len >> (8 * i)) & 0xFF);}unsigned char mask[4];for (int i = 0; i < 4; i++) mask[i] = rand() % 256;frame.insert(frame.end(), mask, mask + 4);for (size_t i = 0; i < len; i++) frame.push_back(payload[i] ^ mask[i % 4]);int sent = send(s, (const char*)frame.data(), (int)frame.size(), 0);return sent == (int)frame.size();
}bool recv_ws_frame(SOCKET s, std::string& out_payload, uint8_t& opcode, bool& fin) {unsigned char hdr[2];if (!read_exact(s, hdr, 2)) return false;fin = (hdr[0] & 0x80) != 0;opcode = hdr[0] & 0x0F;bool masked = (hdr[1] & 0x80) != 0;uint64_t payload_len = hdr[1] & 0x7F;if (payload_len == 126) {unsigned char ext[2]; if (!read_exact(s, ext, 2)) return false;payload_len = (ext[0] << 8) | ext[1];}else if (payload_len == 127) {unsigned char ext[8]; if (!read_exact(s, ext, 8)) return false;payload_len = 0; for (int i = 0; i < 8; i++) payload_len = (payload_len << 8) | ext[i];}unsigned char mask_key[4] = { 0,0,0,0 };if (masked) {if (!read_exact(s, mask_key, 4)) return false;}out_payload.resize(payload_len);if (payload_len > 0) {if (!read_exact(s, (unsigned char*)out_payload.data(), payload_len)) return false;if (masked) {for (uint64_t i = 0; i < payload_len; i++) out_payload[i] ^= mask_key[i % 4];}}return true;
}std::string recv_all(SOCKET s) {std::string data;char buf[2048];while (true) {int r = recv(s, buf, sizeof(buf), 0);if (r <= 0) break;data.append(buf, buf + r);if (data.find("\r\n\r\n") != std::string::npos) break;}return data;
}// -------------------- Main --------------------
int main() {WSADATA wsa; if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) return 1;SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (sock == INVALID_SOCKET) return 1;sockaddr_in server{}; server.sin_family = AF_INET; server.sin_port = htons(8080);inet_pton(AF_INET, "127.0.0.1", &server.sin_addr);if (connect(sock, (sockaddr*)&server, sizeof(server)) == SOCKET_ERROR) { closesocket(sock); WSACleanup(); return 1; }unsigned char random_bytes[16]; for (int i = 0; i < 16; i++) random_bytes[i] = rand() % 256;std::string sec_key = base64_encode(random_bytes, 16);std::ostringstream req;req << "GET /chat HTTP/1.1\r\n"<< "Host: 127.0.0.1:8080\r\n"<< "Upgrade: websocket\r\n"<< "Connection: Upgrade\r\n"<< "Sec-WebSocket-Key: " << sec_key << "\r\n"<< "Sec-WebSocket-Version: 13\r\n\r\n";std::string req_str = req.str();send(sock, req_str.c_str(), (int)req_str.size(), 0);std::string resp = recv_all(sock);std::cout << "Handshake response:\n" << resp << "\n";std::thread recv_thread([sock]() {while (true) {std::string payload; uint8_t opcode; bool fin;if (!recv_ws_frame(sock, payload, opcode, fin)) break;if (opcode == 0x1) std::cout << payload << "\n";}});while (true) {std::string msg;std::getline(std::cin, msg);if (msg == "/quit") break;if (!send_ws_frame(sock, msg)) break;}send_ws_frame(sock, "", 0x8);closesocket(sock);recv_thread.join();WSACleanup();return 0;
}

http://www.dtcms.com/a/389361.html

相关文章:

  • 并发、分布式和实时设计方法
  • C语言第15讲
  • windows 下使用 bat 批处理运行 Chrome 无头模式刷一波访问量
  • 项目名称:基于Qt框架的跨平台天气预报应用程序​​
  • 王自如重操旧业拆箱iPhone:苹果新机发售旧机发热是惯例……
  • 鸿蒙Next Core File Kit:文件管理的高效安全之道
  • Java-128 深入浅出 MySQL MyCat 分布式数据库中间件详解:架构、功能与应用场景
  • gozero使用gRPC-gateway生成http网关
  • Go语言100个实战案例-项目实战篇:股票行情数据爬虫
  • Python开发最新 PyCharm 2025使用(附详细教程)
  • 【session基础】
  • 客户流失预警中uplift建模案例、入门学习(二)
  • SSH远程管理工具
  • 4644电源芯片的介绍和使用
  • MIPI D-PHY布线规则
  • 《深入理解Java虚拟机》第四章节读书笔记:虚拟机性能监控、故障处理工具
  • ​​[硬件电路-251]:电源相关常见的专业术语
  • 日志中的SQL语句直接转为可执行的SQL
  • Java 大视界 -- Java 大数据在智慧文旅旅游景区游客情感分析与服务改进中的应用实践
  • Nginx-RTMP-Module开源项目全解析:从基础部署到企业级应用实践
  • 新代系统如何输入期限密码
  • 【C++】STL--stack(栈)queue(队列)使用及其重要接口模拟实现
  • 计算机组成原理:奔腾系列机的虚存组织
  • 架构模式的双雄会:Reactor与Proactor的高并发哲学
  • 【C++】STL详解(八)—stack和queue的模拟实现
  • 【LeetCode Hot100----08-二叉树篇中(06-10),包含多种方法,详细思路与代码,让你一篇文章看懂所有!】
  • ARM(12) - ADC 检测光照强度
  • 网格生成引擎:设计原则、关键组件
  • 【开发AI】Spring AI Alibaba:集成AI应用的Java项目实战
  • Spark专题-第二部分:Spark SQL 入门(2)-算子介绍-Scan/Filter/Project