C ++ websocket服务器处理消息碎片

时间:2018-03-11 03:03:31

标签: c++ websocket winsock

我正在尝试通过websocket从一个javascript / html客户端发送图像到另一个。问题是服务器错误地接收图像。我将所有图像作为数据URI发送到文本中,这样当javascript客户端收到它时,它只需将img的src设置为URI即可。问题(我相信)来自于我如何处理消息碎片。发送简单的文本消息工作正常,因此我开始相信它导致问题的消息大小,唯一的主要代码差异是我如何处理消息碎片。从this文档中,我开始相信所有必须做的就是取消屏蔽每个碎片帧的有效负载并将缓冲区连接在一起。在服务器上读取的URI比图像的实际数据URI短得多。在客户端,我所做的就是调用socket.send()函数。我已经确认我在javascript FileReader中读取的数据URI是正确的(在客户端)。

    int wSock::readData(/*input socket data buffer*/ char ** sockp, /*output payload*/ char ** buffer, /*output payload info*/ WebSocketFrameData * data) {
    char * sock = *sockp;
    if (!webSocketIsOpened(sock)) return -32; //checks if the socket is open
    u_long package_size;
    SOCKET socket;
    size_t dataRead = 0;
    size_t dr = 0;
    size_t firstLength = 0;
    memcpy_s(&socket, 4, sock, 4);
    ioctlsocket(socket, FIONREAD, &package_size);
    if (package_size <= 0) return 1;
    char * buf = new char[package_size + 1];
    while (dataRead < package_size) {
        dr = recv(socket, buf + dataRead, package_size - dataRead, NULL);
        if (dr == SOCKET_ERROR) {
            delete[] buf; 
            return WSAGetLastError();
        }
        dataRead += dr;
    }
    *(buf + package_size) = '\0';
    if (package_size > 0) {
        decodeFrame(buf, buffer, &firstLength);
        if (data != NULL) {
            data->payloadLength = firstLength;
            data->opcode = *buf & 0b00001111;
        }
    }
    else return 1;

    // code handling other opcodes such as a close frame or a ping

    char fin = (*buf) >> 7;
    if (!fin) { //start handling message fragmentation
        printf("Fragmentation! \n");
        FD_SET tempRead;
        size_t totalLength = firstLength -1; //firstLength includes the null terminator
        char * combinedPayloads = new char[totalLength];
        memcpy_s(combinedPayloads, totalLength, *buffer, totalLength);
        printf("First frage of size: %u \n", totalLength);
        while (fin != 1) {
            FD_ZERO(&tempRead);
            FD_SET(socket, &tempRead);
            select(0, &tempRead, NULL, NULL, NULL);

            package_size = 0;
            ioctlsocket(socket, FIONREAD, &package_size);
            printf("Reading next frag of size: %u \n", package_size);
            char * contBuf = new char[package_size];
            dataRead = 0;
            while (dataRead < package_size) {
                dr = recv(socket, contBuf + dataRead, package_size - dataRead, NULL);
                if (dr == SOCKET_ERROR) { 
                    delete[] contBuf; 
                    return WSAGetLastError();
                }
                dataRead += dr;
            }
            char * payload;
            size_t payloadLength = 0;
            decodeFrame(contBuf, &payload, &payloadLength);
            payloadLength--; //the output payloadLength from the decodeFrame function includes a null terminator
            char * backBuffer = new char[totalLength];
            memcpy_s(backBuffer, totalLength, combinedPayloads, totalLength);
            delete[] combinedPayloads;

            combinedPayloads = new char[totalLength + payloadLength];
            memcpy_s(combinedPayloads, totalLength, backBuffer, totalLength);
            memcpy_s(combinedPayloads + totalLength, payloadLength, payload, payloadLength);
            fin = contBuf[0] >> 7;
            totalLength += payloadLength;
            delete[] backBuffer;
            delete[] contBuf;
            delete[] payload;
            if (fin) break;
        }
        delete[] *buffer;
        *buffer = new char[totalLength + 1];
        memcpy_s(*buffer, totalLength, combinedPayloads, totalLength);
        (*buffer)[totalLength] = '\0';
        delete[] combinedPayloads;
        data->payloadLength = totalLength;
        printf("Finished fragment! Total size: %u \n", totalLength);
    }
    delete[] buf;
    return 0;
}

这是解码每个websocket框架的代码。正如我所提到的,服务器适用于较小的聊天消息,因此我认为问题是消息重新组装,但我将包含decodeFrame函数,希望它有助于理解。

    int wSock::decodeFrame(char * message, char ** output, size_t * payloadLength)
{
    char read;
    memcpy_s(&read, 1, message + 1, 1);
    unsigned long long size = read & 0b01111111;
    //takes bits 9 - 15;
    int lastByte = 2;
    if (size == 126) {
        unsigned short rr;
        memcpy_s(&rr, 2, message + 2, 2);
        size = ntohs(rr);
        lastByte = 4;
    }
    else if (size == 127) {
        unsigned long long data;
        memcpy_s(&data, 8, message + 2, 8);
        size = ntohll(data);
        lastByte = 10;
    }
    if(payloadLength != NULL)
        *payloadLength = size + 1;
    char mask[4];
    memcpy_s(mask, 4, message + lastByte, 4);
    *output = new char[(size + 1)];
    lastByte += 4;
    for (int i = 0; i < size; i++) {
        (*output)[i] = message[lastByte + i] ^ mask.mask[i % 4];
    }
    (*output)[size] = '\0';
    return 0;
}

在服务器端进行调试,我将读取的消息写入文本文件。但是,写入的URI只有大约4,000 - 6,000个字符长,最后200 - 400个字符不是有效的base64字符,但是这些无效字符之前的字符与真实数据上的相应字符匹配URI。重新组装过程中的printf语句将倾向于读取大约262,368字节(总计),而实际URI长度为389,906个字符。读取URI后,服务器将其发送给客户端,这会导致它们断开连接。所以我提到我的猜测是,当我重新组装数据帧时出现了问题。任何帮助将不胜感激。谢谢你提前。

2 个答案:

答案 0 :(得分:0)

ioctlsocket(socket, FIONREAD, &package_size);
  • FIONREAD返回可以无阻塞地读取的字节数。这意味着此代码行之后的recv()循环完全是徒劳的一个 recv() 读取该数据量。不可能。

  • 您也没有正确处理流的结尾(recv()返回零)。

答案 1 :(得分:0)

好的,我明白了。我忘记考虑的是TCP消息碎片。正如@EJP所提到的,ioctlsocket仅返回一次recv()次调用中可读取的字节数。我将每个收到的数据片段视为自己的WebSocket框架,而情况并非总是这样。通常(几乎所有时间)单个recv()调用仅读取部分帧,第一帧的下一部分将在第二帧recv()处与第二帧的第一部分一起读取呼叫。然后第二个缓冲区(现在是两个不同的不完整帧的混合)显然不会被正确解除掩码并且解码的大小将是不正确的。 javascript客户端将每个WebSocket帧分段为大约131K字节,底层TCP层将这些帧进一步分解为大约65K字节的数据包。所以我所做的就是在一次recv()调用中收到了所有数据,然后使用了以下函数:

    unsigned long long wSock::decodeTotalFrameSize(char * frame)
{
    char secondByte = 0;
    memcpy_s(&secondByte, 1, frame + 1, 1);
    unsigned long long size = secondByte & 0b01111111;
    int headerSize = 2 + 4;
    if (size == 126) {
        unsigned short length;
        memcpy_s(&length, 2, frame + 2, 2);
        size = ntohs(length);
        headerSize += 2;
    }
    else if (size == 127) {
        unsigned long long length;
        memcpy_s(&length, 8, frame + 2, 8);
        size = ntohll(length);
        headerSize += 8;
    }
    return size + headerSize;
}

获取WebSocket总帧大小。然后循环,直到您将该字节数读入单个帧。类似于:

            FD_ZERO(&tempRead);
            FD_SET(socket, &tempRead);
            select(0, &tempRead, NULL, NULL, NULL);

            package_size = 0;
            ioctlsocket(socket, FIONREAD, &package_size);
            char * contBuf = new char[package_size];
            dataRead = 0;
            dr = recv(socket, contBuf, package_size, NULL);
            if (dr == SOCKET_ERROR) { 
                delete[] contBuf; 
                return WSAGetLastError();
            }
            unsigned long long actualSize = decodeTotalFrameSize(contBuf);
            if (package_size < actualSize) {
                char * backBuffer = new char[package_size];
                memcpy_s(backBuffer, package_size, contBuf, package_size);
                delete[] contBuf;
                contBuf = new char[actualSize];
                memcpy_s(contBuf, actualSize, backBuffer, package_size);
                delete[] backBuffer;
                dataRead = package_size;
                dr = 0;
                while (dataRead < actualSize) {
                    dr = recv(socket, contBuf + dataRead, actualSize - dataRead, NULL);
                    if (dr == SOCKET_ERROR) {
                        delete[] contBuf;
                        return WSAGetLastError();
                    }
                    else if (dr == 0) break;
                    dataRead += dr;
                }
                printf("Read total frag of %u \n", dataRead);

            }