У меня проблемы с созданием клиента / сервера websocket на c ++. Я учусь на исходном коде poco и ссылках на протоколы здесь: https://tools.ietf.org/html/rfc6455#section-1.3
Я мог бы использовать веб-сокеты poco, но я хочу создать асинхронные веб-сокеты, основанные на моей реализации асинхронного сокета
весь мой код написан только для 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;
}
любой онЛП приветствуется