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帧发送导致服务器关闭连接的主要内容,如果未能解决你的问题,请参考以下文章

WebSocket - 关闭框架

Python websockets keepalive ping 超时;没有收到关闭帧

如何在 PHP 服务器上向客户端发送 WebSocket 关闭帧?

Websocket阻止来自服务器的传入请求

Websocket 连接因错误“收到意外的延续帧”而关闭。

WebSocket,解码数据帧(c++)