C++ 在 Windows 下实现最基础的 WebSocket 服务端与客户端
一、前言
在现代 Web 开发中,WebSocket 已成为实现实时通信的重要技术。它能在浏览器和服务器之间建立全双工长连接,相比传统的 HTTP 轮询,大大减少了延迟与网络开销。
常见应用场景包括:即时聊天、在线游戏、实时推送、协同编辑等等。
虽然已经有成熟的 C++ WebSocket 库(如 Boost.Beast、WebSocket++),但对于学习者来说,理解协议本身更有价值。
因此本文将带你使用 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
:Ping0xA
: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
五、运行测试
启动服务端
websocket_server.exe
控制台输出:
WebSocket server listening on port 8080. Waiting for a client...
启动客户端
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;
}