c++ websocket帧发送导致服务器关闭连接
Posted
技术标签:
【中文标题】c++ websocket帧发送导致服务器关闭连接【英文标题】:c++ websocket frame send causes server to close the connection 【发布时间】:2019-10-08 13:50:30 【问题描述】:我在用 c++ 创建 websocket 客户端/服务器时遇到了一些问题。我从这里学习 poco 源代码和协议引用: https://www.rfc-editor.org/rfc/rfc6455#section-1.3
我本可以使用 poco websockets,但我想在我的异步套接字实现上构建一个异步 websockets
我所有的代码都是为 Windows 编写的,我正在使用 chrome 调试协议进行测试:https://chromedevtools.github.io/devtools-protocol/tot
我首先使用这个功能连接到服务器:
bool LIB_NAMESPACE::net::WebSocket::Connect(string_view Host, uint16_t Port, string_view Path, string_view Origin)
Dns dns;
if (!dns.Resolve(Host))
return false;
SockAddr *AddrPtr;
for (auto& Addr : dns.AddrsInfo) // std::variant<IpV4Addr, IpV6Addr>
if (Addr.Addrs.index() == 0)
AddrPtr = static_cast<SockAddr*>(&std::get<0>(Addr.Addrs));
else if (Addr.Addrs.index() == 1)
AddrPtr = static_cast<SockAddr*>(&std::get<1>(Addr.Addrs));
else
continue;
AddrPtr->port = Port;
if (Connect(*AddrPtr, Path, Host, Origin))
return true;
return false;
bool LIB_NAMESPACE::net::WebSocket::Connect(const SockAddr & addr, string_view Path, string_view host, string_view Origin)
sock.Close();
Socket ConnectorSock addr.Family(), SocketType::TcpStream, ProtocolType::Tcp ;
if (!ConnectorSock)
return false;
if (!ConnectorSock.Connect(addr))
return false;
std::string RequesBuffer =
"GET " + static_cast<std::string>(Path) + " HTTP/1.1\r\n";
RequesBuffer += "Host: " + (!host.empty() ? static_cast<std::string>(host) : addr.IpStr());
if (addr.port == 80)
RequesBuffer += "\r\n";
else
RequesBuffer += ':' + std::to_string(addr.port) + "\r\n";
RequesBuffer +=
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n\r\n";
auto Sent = ConnectorSock.Send(RequesBuffer.c_str(), RequesBuffer.size());
if (Sent <= 0 || Sent != RequesBuffer.size())
return false;
string Response;
char chunk[100];
while (!Response.ends_with("\r\n\r\n"))
auto Recved = ConnectorSock.Recv(chunk, 100);
if (Recved <= 0)
return false;
Response.append(chunk, chunk + Recved);
std::cout << Response << std::endl;
/*
HTTP/1.1 101 WebSocket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
*/
// needs better http parsing
if (!Response.icontains("HTTP/1.1 101") || !Response.icontains("Upgrade: websocket")
|| !Response.icontains("Connection: Upgrade") || !Response.icontains("Sec-WebSocket-Accept"))
return false;
sock = std::move(ConnectorSock);
return true;
连接被打开,但是我错过了验证 Sec-WebSocket-Accept 但我认为现在这很好,以后可能会实现
然后我声明这些结构将数据发送到服务器:
class big_endian_u16
uint16_t big_endian_val;
public:
big_endian_u16() : big_endian_val(0)
big_endian_u16(uint16_t val) : big_endian_val(_byteswap_ushort(val))
operator uint16_t() const return _byteswap_ushort(big_endian_val);
;
class big_endian_u32
uint32_t big_endian_val;
public:
big_endian_u32() : big_endian_val(0)
big_endian_u32(uint32_t val) : big_endian_val(_byteswap_ulong(val))
operator uint32_t() const return _byteswap_ulong(big_endian_val);
;
class big_endian_u64
uint64_t big_endian_val;
public:
big_endian_u64() : big_endian_val(0)
big_endian_u64(uint64_t val) : big_endian_val(_byteswap_uint64(val))
operator uint64_t() const return _byteswap_uint64(big_endian_val);
;
struct WebSocketHeaderRaw
bool FinalFragment : 1;
char RSV1 : 1;
char RSV2 : 1;
char RSV3 : 1;
WebSocketFrame OpCode : 4;
bool IsMasked : 1;
unsigned char PayloadLength : 7;
WebSocketHeaderRaw()
static_assert(sizeof(WebSocketHeaderRaw) == 2, "invalid size of WebSocketHeaderRaw");
;
enum class WebSocketFrame : unsigned char
Continuation = 0X0,
Text= 0x1,
Binary = 0x2,
Close = 0x8,
Ping = 0x9,
Pong = 0xA,
;
constexpr int MaxPayloadSize = std::numeric_limits<int>::max();
inline void MaskUnmask(char *Buffer, const char *UnMaskedBuffer, int Length,
char Mask[4])
for (auto i : range(0, Length))
Buffer[i] = UnMaskedBuffer[i] ^ Mask[i % 4];
这是发送方法:
int LIB_NAMESPACE::net::WebSocket::SendFrame(WebSocketFrame FrameType, const char * Buffer, int Length)
WebSocketHeaderRaw RawHeader;
RawHeader.FinalFragment = true;
RawHeader.RSV1 = 0;
RawHeader.RSV2 = 0;
RawHeader.RSV3 = 0;
RawHeader.IsMasked = MustMask; // this is true because I'm using the websocket as client
RawHeader.OpCode = FrameType; // I send only text frames -> 0x1
big_endian_u16 u16Length;
big_endian_u64 u64Length;
char Mask[4];
uint32_t u32Length = static_cast<uint32_t>(Length);
if (u32Length < 126)
RawHeader.PayloadLength = static_cast<unsigned char>(u32Length);
else if (u32Length < std::numeric_limits<uint16_t>::max())
RawHeader.PayloadLength = 126;
u16Length = static_cast<uint16_t>(u32Length);
else
RawHeader.PayloadLength = 127;
u64Length = static_cast<uint64_t>(u32Length);
std::vector<char> MaskedBuffer;
if (MustMask)
*(uint32_t*)Mask = RandomULongLong(std::numeric_limits<uint32_t>::max(), std::numeric_limits<uint64_t>::max() - 1); // uses c++ random library
MaskedBuffer.resize(Length);
MaskUnmask(MaskedBuffer.data(), Buffer, Length, Mask);
Buffer = MaskedBuffer.data();
int Sent = sock.Send((char*)&RawHeader, sizeof(RawHeader));
if (Sent <= 0)
return Sent;
if (u16Length)
Sent = sock.Send((char*)&u16Length, sizeof(u16Length));
if (Sent <= 0)
return Sent;
else if (u64Length)
Sent = sock.Send((char*)&u16Length, sizeof(u64Length));
if (Sent <= 0)
return Sent;
if (MustMask)
Sent = sock.Send(Mask, sizeof(Mask));
if (Sent <= 0)
return Sent;
return sock.Send(Buffer, Length);
在我向服务器发送任何内容后,它会关闭连接,当我开始接收 recv
时返回 0,因为服务器关闭了连接
在尝试接收标头时,接收操作很早就失败了
LIB_NAMESPACE::net::WebSocketHeader LIB_NAMESPACE::net::WebSocket::RecvHeader()
WebSocketHeaderRaw RawHeader;
WebSocketHeader header 0 ;
int Recved = sock.RecvAll((char*)&RawHeader, sizeof(RawHeader));
if (Recved <= 0) // --> it fails here
std::cout << "[!] RecvAll (header) failed with error " << WSAGetLastError() << std::endl;
return header;
char LengthBuffer[sizeof(uint64_t)];
if (RawHeader.PayloadLength == 127)
Recved = sock.RecvAll(LengthBuffer, sizeof(uint64_t));
if (Recved <= 0)
std::cout << "[!] RecvAll (length 127) failed with error " << WSAGetLastError() << std::endl;
return header;
uint64_t Length = _byteswap_uint64(*(uint64_t*)LengthBuffer);
if (Length < MaxPayloadSize)
header.PayloadLength = static_cast<int>(Length);
else if (RawHeader.PayloadLength == 126)
Recved = sock.RecvAll(LengthBuffer, sizeof(uint16_t));
if (Recved <= 0)
std::cout << "[!] RecvAll (length 126) failed with error " << WSAGetLastError() << std::endl;
return header;
uint16_t Length = _byteswap_ushort(*(uint16_t*)LengthBuffer);
if (Length < MaxPayloadSize)
header.PayloadLength = static_cast<int>(Length);
else if (RawHeader.PayloadLength < MaxPayloadSize)
header.PayloadLength = static_cast<int>(RawHeader.PayloadLength);
if (!header.PayloadLength)
std::cout << "[!] PayloadLength is 0 " << std::endl;
return header;
if (RawHeader.IsMasked)
Recved = sock.RecvAll(header.MaskKey, sizeof(header.MaskKey));
if (Recved <= 0)
std::cout << "[!] RecvAll (Mask) failed with error " << WSAGetLastError() << std::endl;
header.PayloadLength = 0;
return header;
header.FinalFragment = RawHeader.FinalFragment;
header.IsMasked = RawHeader.IsMasked;
header.OpCode = RawHeader.OpCode;
header.extensions.RSV1 = RawHeader.RSV1;
header.extensions.RSV2 = RawHeader.RSV2;
header.extensions.RSV3 = RawHeader.RSV3;
return header;
这是我的用法:
int wmain(int argc, wchar_t **argv)
std::string wsPath; // chrome path of webSocketDebuggerUrl
cout << "enter the path : ";
cin >> wsPath;
rad::net::WebSocket ws;
if (!ws.Connect("localhost", 9222, std::string_view(wsPath)))
cout << "[!] failed to connect" << endl;
else
cout << "[*] connected successfully" << endl;
if (ws.SendTextFrame(R"("id": 1, "method": "Network.enable" )") <= 0) // this passes but the server closes the connection
cout << "[!] failed to send the frame !" << endl;
getchar();
getchar();
return 0;
while (1)
std::vector<char> Buffer;
if (ws.RecvTextFrame(Buffer) <= 0) // always fails here
break;
std::cout << ">> " << string_view(Buffer.data(), Buffer.size()) << std::endl;
cout << "[!] failed to receive the frame !" << endl;
getchar();
getchar();
return 0;
欢迎任何帮助
【问题讨论】:
【参考方案1】:位域在内存中是从低到高排序的,但是头包中的位是从高到低排序的
根据这个参考,这个位字段的顺序在 msvc 中得到保证: https://docs.microsoft.com/en-us/cpp/cpp/cpp-bit-fields?view=vs-2019
我尝试使用 gcc,它似乎具有相同的行为
所以我不得不反转结构中的每个字节组件以接收正确的结果
我还尝试使用按位运算符设置和提取位,它的工作原理和位域一样好,但位域对我来说更优雅
【讨论】:
以上是关于c++ websocket帧发送导致服务器关闭连接的主要内容,如果未能解决你的问题,请参考以下文章
Python websockets keepalive ping 超时;没有收到关闭帧