以下是使用 C++ 构建 WebSocket 数据帧回送给客户端的示例代码:
#include <iostream>
#include <string>
#include <cstring>
#include <vector>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib") // Link with ws2_32.lib
#define MAX_BUFFER_SIZE 4096 // Maximum buffer size for receiving client data
// Structure for storing the WebSocket frame header information
struct FrameHeader
{
bool Fin;
unsigned int OpCode;
bool Masked;
unsigned long long PayloadLength;
unsigned char MaskingKey[4];
};
// Function to parse the incoming WebSocket frame header
FrameHeader ParseFrameHeader(unsigned char* data)
{
FrameHeader header;
header.Fin = (data[0] & 0x80) != 0;
header.OpCode = data[0] & 0x0F;
header.Masked = (data[1] & 0x80) != 0;
header.PayloadLength = data[1] & 0x7F;
if (header.PayloadLength == 126)
{
header.PayloadLength = (data[2] << 8) | data[3];
if (header.Masked)
{
memcpy(header.MaskingKey, data + 4, 4);
}
}
else if (header.PayloadLength == 127)
{
header.PayloadLength = ((unsigned long long)data[2] << 56) |
((unsigned long long)data[3] << 48) |
((unsigned long long)data[4] << 40) |
((unsigned long long)data[5] << 32) |
((unsigned long long)data[6] << 24) |
((unsigned long long)data[7] << 16) |
((unsigned long long)data[8] << 8) |
(unsigned long long)data[9];
if (header.Masked)
{
memcpy(header.MaskingKey, data + 10, 4);
}
}
else
{
if (header.Masked)
{
memcpy(header.MaskingKey, data + 2, 4);
}
}
return header;
}
// Function to create a WebSocket frame with the specified payload data
std::vector<unsigned char> CreateFrame(unsigned int opCode, const std::string& payloadData, bool masked = true)
{
unsigned char header[14];
size_t headerSize = 2;
size_t payloadSize = payloadData.size();
if (payloadSize < 126)
{
header[1] = static_cast<unsigned char>(payloadSize);
}
else if (payloadSize <= 0xFFFF)
{
header[1] = 126;
header[2] = static_cast<unsigned char>((payloadSize >> 8) & 0xFF);
header[3] = static_cast<unsigned char>(payloadSize & 0xFF);
headerSize += 2;
}
else
{
header[1] = 127;
header[2] = 0;
header[3] = 0;
header[4] = 0;
header[5] = 0;
header[6] = static_cast<unsigned char>((payloadSize >> 24) & 0xFF);
header[7] = static_cast<unsigned char>((payloadSize >> 16) & 0xFF);
header[8] = static_cast<unsigned char>((payloadSize >> 8) & 0xFF);
header[9] = static_cast<unsigned char>(payloadSize & 0xFF);
headerSize += 8;
}
header[0] = 0x80 | opCode;
if (masked)
{
header[1] |= 0x80;
unsigned char maskingKey[4];
maskingKey[0] = static_cast<unsigned char>(rand() % 256);
maskingKey[1] = static_cast<unsigned char>(rand() % 256);
maskingKey[2] = static_cast<unsigned char>(rand() % 256);
maskingKey[3] = static_cast<unsigned char>(rand() % 256);
memcpy(header + headerSize, maskingKey, 4);
headerSize += 4;
std::vector<unsigned char> maskedPayloadData(payloadData.begin(), payloadData.end());
for (size_t i = 0; i < payloadSize; ++i)
{
maskedPayloadData[i] ^= maskingKey[i % 4];
}
std::vector<unsigned char> frame(headerSize + payloadSize);
memcpy(frame.data(), header, headerSize);
memcpy(frame.data() + headerSize, maskedPayloadData.data(), payloadSize);
return frame;
}
else
{
std::vector<unsigned char> frame(headerSize + payloadSize);
memcpy(frame.data(), header, headerSize);
memcpy(frame.data() + headerSize, payloadData.data(), payloadSize);
return frame;
}
}
int main()
{
WSADATA wsaData;
int result = WSAStartup(MAKEWORD(2,2), &wsaData); // Initialize Winsock library
if (result != 0)
{
std::cerr << "WSAStartup failed: " << result << std::endl;
return result;
}
SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // Create a TCP socket
if (serverSocket == INVALID_SOCKET)
{
std::cerr << "socket failed: " << WSAGetLastError() << std::endl;
WSACleanup();
return WSAGetLastError();
}
sockaddr_in serverAddress = { 0 };
serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = INADDR_ANY;
serverAddress.sin_port = htons(8080);
result = bind(serverSocket, (sockaddr*)&serverAddress, sizeof(serverAddress)); // Bind the socket to the specified address and port
if (result != 0)
{
std::cerr << "bind failed: " << WSAGetLastError() << std::endl;
closesocket(serverSocket);
WSACleanup();
return WSAGetLastError();
}
result = listen(serverSocket, SOMAXCONN); // Listen for incoming connections
if (result != 0)
{
std::cerr << "listen failed: " << WSAGetLastError() << std::endl;
closesocket(serverSocket);
WSACleanup();
return WSAGetLastError();
}
std::cout << "Waiting for incoming connections...\n";
SOCKET clientSocket = INVALID_SOCKET;
while (true)
{
clientSocket = accept(serverSocket, NULL, NULL); // Accept an incoming connection
if (clientSocket == INVALID_SOCKET)
{
std::cerr << "accept failed: " << WSAGetLastError() << std::endl;
closesocket(serverSocket);
WSACleanup();
return WSAGetLastError();
}
std::cout << "Client connected.\n";
std::vector<unsigned char> buffer(MAX_BUFFER_SIZE);
int bytesReceived = recv(clientSocket, (char*)buffer.data(), MAX_BUFFER_SIZE, 0); // Receive data from the client
if (bytesReceived == SOCKET_ERROR)
{
std::cerr << "recv failed: " << WSAGetLastError() << std::endl;
closesocket(clientSocket);
continue;
}
FrameHeader frameHeader = ParseFrameHeader(buffer.data()); // Parse the WebSocket frame header
std::string payloadData((char*)buffer.data() + 2, bytesReceived - 2); // Extract the payload data from the received frame
std::vector<unsigned char> responseFrame = CreateFrame(frameHeader.OpCode, payloadData); // Create a new WebSocket frame with the same payload data
int bytesSent = send(clientSocket, (const char*)responseFrame.data(), responseFrame.size(), 0); // Send the response frame back to the client
if (bytesSent == SOCKET_ERROR)
{
std::cerr << "send failed: " << WSAGetLastError() << std::endl;
closesocket(clientSocket);
continue;
}
std::cout << "Response sent.\n";
closesocket(clientSocket);
}
closesocket(serverSocket);
WSACleanup();
return 0;
}
说明:
ParseFrameHeader函数用于解析一个 WebSocket 数据帧的头部信息,返回一个FrameHeader结构体,包含该数据帧是否结束、操作码、是否使用掩码等信息。CreateFrame函数用于创建一个 WebSocket 数据帧,需要指定操作码和负载数据,并可以选择是否使用掩码。如果使用掩码,则会随机生成一个掩码密钥并对负载数据进行异或操作,以确保数据传输的安全性。- 在
main函数中,首先初始化 Winsock 库,并创建一个 TCP 套接字。然后绑定到指定的地址和端口,并开始监听 incoming connections。 - 在主循环中,不断接受来自客户端的连接,并等待客户端发送数据。当接收到数据时,解析数据帧头部信息并提取负载数据。然后根据操作码和负载数据创建一个新的数据帧,并将其发送回客户端。如果发生任何错误,程序将继续等待下一个连接。




