1. 程式人生 > >Visual Studio 2019使用C語言進行websocket程式設計

Visual Studio 2019使用C語言進行websocket程式設計

一直在寫C#程式碼好多年不寫C語言程式碼了,記錄一下之前某個專案裡用C寫的一個websocket服務,用C的優勢是寫的東西體積小效能高,但是寫業務的話還得用C#、Java之類的語言,不然會折騰死人。。。

 

用Visual Studio新建一個C++(因為不能直接建C語言專案)專案,我演示就建立一個控制檯專案。專案建立完後首先要新增socket程式設計需要的依賴庫ws2_32.lib,新增方式如下圖

也可以在程式碼檔案裡新增這句程式碼:#pragma comment(lib,"Ws2_32.lib"),接著新增b64、cJSON、sha1依賴庫。

新增完成後就可以開始寫程式碼了,說句題外話,Visual Studio寫C語言最好把SDL檢查也關掉。

新建一個wsserver.h標頭檔案,標頭檔案相關定義程式碼如下

#pragma once
#include <WinSock2.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <windef.h>
#include <stdlib.h>
#include "sha1.h"
#include "b64.h"
#include "cJSON.h"

typedef enum FrameType
{
    frameType_continuation,
    frameType_text,
    frameType_binary,
    frameType_connectionClose,
    frameType_ping,
    frameType_pong
} FrameType;

typedef enum FrameState
{
    frameState_init,            // 未讀取任何位元組
    frameState_firstByte,       // 已讀取首位元組FIN、RSV、opcode
    frameState_mask,            // 已讀取掩碼
    frameState_7bitLength,      // 已讀取7bit長度
    frameState_16bitLengthWait, // 等待讀取16bit長度
    frameState_63bitLengthWait, // 等待讀取63bit長度
    frameState_16bitLength,     // 已讀取16bit長度
    frameState_63bitLength,        // 已讀取63bit長度
    frameState_maskingKey,      // 已讀取Masking-key
    frameState_readingData,     // 正在讀取載荷資料
    frameState_success,         // 讀取完畢
    frameState_failure          // 讀取錯誤
} FrameState;

typedef struct WsFrame
{
    FrameState state;
    bool FIN;
    FrameType frameType;
    uint8_t  mask[4];
    unsigned char* buff;   // 資料存放的空間
    uint64_t buffSize;      // 當前申請的buff大小
    uint64_t handledLen;    // 已處理的幀長度
    uint64_t headerLen;     // 幀頭長度 只有在state為'已讀取掩碼'及之後才有意義
    uint64_t payloadLen;    // 載荷長度 只有在state為'已讀取xbit長度'後才有意義
    struct WsFrame* next;   // 下一幀的指標
} WsFrame;

void initWsFrameStruct(WsFrame* wsFrame);
char* convertToWebSocketFrame(const char* data, FrameType type, size_t len, size_t* newLen);
int readWebSocketFrameStream(WsFrame* wsFrame, const char* buff, int len);
void freeWebSocketFrame(WsFrame* wsFrame);
int wsShakeHands(const char* recvBuff, int recvLen, SOCKET socket, const char* path);
int wsFrameSend(SOCKET socket, const char* buff, int len, FrameType type);
void wsFrameSendToAll(const char* buff, int len, FrameType type);
int serverStart(const char* address, u_short port, const char* path);
void serverStop(void);

 

新建一個wsserver.c程式碼檔案,我們一步一步的來實現這些方法。

首先是serverStart方法,顧名思義,啟動ws服務,第一個引數是地址(一般傳本機IP),第二個引數是要監聽的埠,第三個引數是路徑,完整程式碼如下

#include "wsserver.h"
#include <time.h>
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS

typedef enum
{
    socketProtocol,
    websocketProtocol
} Protocol;

typedef struct
{
    Protocol protocol;
    SOCKET socket;
    WsFrame wsFrame;
} Client;

#define MAX_CLIENT_NUM FD_SETSIZE
static struct
{
    int    total;
    Client clients[MAX_CLIENT_NUM];
} clientSockets;

static SOCKET serverSocket;

// 列印日誌
void printLog(const char* type, const char* format, ...)
{
    char buff[512] = { 0 };
    va_list arg;
    va_start(arg, format);
    vsnprintf(buff, sizeof(buff) - 1, format, arg);
    va_end(arg);
    char rbuf[512] = { 0 };
    time_t log_time = time(NULL);
    struct tm* tm_log = localtime(&log_time);
    printf("[%04d-%02d-%02d %02d:%02d:%02d] ", tm_log->tm_year + 1900, tm_log->tm_mon + 1, tm_log->tm_mday, tm_log->tm_hour, tm_log->tm_min, tm_log->tm_sec);
    snprintf(rbuf, 512, "%s->%s\n", type, buff);
    printf(rbuf);
}
char* UTF8ToGBK(const char* str)
{
    // GB18030內碼表
    const int CODE_PAGE = 54936;
    int n = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
    wchar_t u16str[10000];
    MultiByteToWideChar(CP_UTF8, 0, str, -1, u16str, n);

    n = WideCharToMultiByte(CODE_PAGE, 0, u16str, -1, NULL, 0, NULL, NULL);
    char* gbstr = malloc(n + 1);
    WideCharToMultiByte(CODE_PAGE, 0, u16str, -1, gbstr, n, NULL, NULL);
    return gbstr;
}
char* GBKToUTF8(const char* str)
{
    const int CODE_PAGE = 54936;
    int n = MultiByteToWideChar(CODE_PAGE, 0, str, -1, NULL, 0);
    wchar_t u16str[10000];
    MultiByteToWideChar(CODE_PAGE, 0, str, -1, u16str, n);

    n = WideCharToMultiByte(CP_UTF8, 0, u16str, -1, NULL, 0, NULL, NULL);
    char* u8str = malloc(n + 1);
    WideCharToMultiByte(CP_UTF8, 0, u16str, -1, u8str, n, NULL, NULL);
    return u8str;
}


int serverStart(const char* address, u_short port, const char* path)
{
    // 呼叫 WSAStartup() 函式進行初始化,並指明要使用的版本號。
    WSADATA wsaData;
    // WSAStartup 函式啟動程序使用 Winsock DLL。
    int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0)
    {
        printLog("ServerStart", "WSAStartup failed");
        return -1;
    }

    struct sockaddr_in sockAddr;
    // ZeroMemory 巨集 等價於 memset((buf),0,(BUF_SIZE))
    ZeroMemory(&sockAddr, sizeof(sockAddr));
    sockAddr.sin_family = PF_INET; // 等價於 AF_INET  TCP UDP etc..
    sockAddr.sin_addr.s_addr = inet_addr(address);
    sockAddr.sin_port = htons(port);
    // 構建一個socket物件
    serverSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

    if (serverSocket == INVALID_SOCKET)
    {
        printLog("ServerStart","Error at socket(): %d", WSAGetLastError());
        WSACleanup();
        return -1;
    }
    // 給socket繫結地址
    if (bind(serverSocket, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR)) == SOCKET_ERROR)
    {
        printLog("ServerStart", "Bind failed with error: %d", WSAGetLastError());
        closesocket(serverSocket);
        WSACleanup();
        return -1;
    }
    // 開始啟動監聽
    if (listen(serverSocket, SOMAXCONN) == SOCKET_ERROR)
    {
        printLog("ServerStart", "Listen failed with error: %d", WSAGetLastError());
        closesocket(serverSocket);
        WSACleanup();
        return -1;
    }

    clientSockets.total = 0;

    DWORD dwThreadId;
    // 建立執行緒開始接收socket資料
    HANDLE hHandle = CreateThread(NULL, 0, (void*)receiveComingData, (PVOID)path, 0, &dwThreadId);

    return 0;
}

serverStart方法中最後建立執行緒開始接收socket資料的方法receiveComingData程式碼

void receiveComingData(const char* path)
{

#define RECV_BUFLEN 0X40000
    char recvbuf[RECV_BUFLEN];
    int iResult;

    int ret;
    fd_set fdread;
    struct timeval tv = { 1, 0 };

receivingDataLoop:

    FD_ZERO(&fdread); // 清空socket集合
    FD_SET(serverSocket, &fdread); // 設定socket資料讀取集合
    for (int i = 0; i < clientSockets.total; i++)
    {
        FD_SET(clientSockets.clients[i].socket, &fdread);
    }
    // 檢查socket是否有資料可讀
    ret = select(0, &fdread, NULL, NULL, &tv);

    if (ret == 0)
    {
        goto receivingDataLoop;     // select的等待時間到達,開始下一輪等待 
    }
    // 檢查socket是否在這個集合裡
    if (FD_ISSET(serverSocket, &fdread))
    {
        acceptConnect(); // 處理socket連線
    }

    for (int i = 0; i < clientSockets.total; i++)
    {
        Client* client = &clientSockets.clients[i];
        if (!FD_ISSET(client->socket, &fdread))
        {
            continue;
        }
        // 接收資料
        iResult = recv(client->socket, recvbuf, RECV_BUFLEN, 0);

        if (iResult > 0)
        {
            printLog("receiveComingData", "Bytes received: %d", iResult);
            // 協議升級
            if (client->protocol == socketProtocol)
            {
                int result = wsShakeHands(recvbuf, iResult, client->socket, path);
                if (result != 0)
                {
                    removeClient(i--);
                }
                else
                {
                    client->protocol = websocketProtocol;
                    initWsFrameStruct(&client->wsFrame); // 初始化ws幀結構
                }
            }
            // WebSocket通訊
            else if (client->protocol == websocketProtocol)
            {
                int result = wsClientDataHandle(recvbuf, iResult, client);
                if (result == -1)
                {
                    removeClient(i--);
                }
            }
        }
        else
        {
            if (iResult == 0)
            {
                // 客戶端禮貌的關閉連線 
                printLog("receiveComingData", "Connection closing...");
            }
            else
            {
                // 客戶端異常關閉連線等情況
                printLog("receiveComingData", "Recv failed: %d", WSAGetLastError());
            }
            removeClient(i--);
        }
    }
    goto receivingDataLoop;
}

receiveComingData方法裡處理socket協議升級的程式碼

// 不區分大小寫的比較字串,相等返回true
bool stricasecmp(const char* a, const char* b)
{
    do
    {
        if (*a == '\0' && *b == '\0')
            return true;
    } while (tolower(*a++) == tolower(*b++));

    return false;
}

// 不區分大小寫的比較字串,n個字元內(包括n)相等返回true
bool strnicasecmp(const char* a, const char* b, unsigned n)
{
    do
    {
        if (n-- == 0 || (*a == '\0' && *b == '\0'))
            return true;
    } while (tolower(*a++) == tolower(*b++));

    return false;
}
int getSecWebSocketAcceptKey(const char* key, char* b64buff, int len)
{
    SHA1_CTX ctx;
    unsigned char hash[20], buff[512];
    if (strlen(key) > 256)
    {
        return -1;
    }
    sprintf(buff, "%s%s", key, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
    SHA1Init(&ctx);
    SHA1Update(&ctx, buff, strlen(buff));
    SHA1Final(hash, &ctx);

    const char* base64 = b64_encode(hash, sizeof(hash));
    strncpy(b64buff, base64, len - 1);
    b64buff[len - 1] = '\0';
    free((void*)base64);

    return 0;
}
// 校驗WebSocket握手的HTTP頭,失敗返回NULL,校驗成功順帶返回Sec-WebSocket-Key,記得free
char* verifyHandshakeHeaders(const char* str, size_t len)
{
    char* secKey = NULL;
    char a[1024], b[1024];
    bool connection, upgrade, version, key;
    connection = upgrade = version = key = false;

    if (strcmp(str + len - 4, "\r\n\r\n") != 0)
    {
        printLog("verifyHandshakeHeaders","HTTP header does not end with '\\r\\n\\r\\n'");
        return NULL;
    }

    const char* cur1 = strstr(str, "\r\n") + 2;
    const char* cur2;

    while ((cur2 = strstr(cur1, "\r\n")) != cur1)
    {
        cur2 += 2;  // 跳過\r\n
        const char* colon = strchr(cur1, ':');
        if (colon == NULL || colon >= cur2)
        {
            printLog("verifyHandshakeHeaders", "Unexpected HTTP header");
            break;
        }

        if (sscanf(cur1, "%[^:]:%s", a, b) != 2)
        {
            printLog("verifyHandshakeHeaders", "HTTP header parsing failed");
            break;
        }

        if (stricasecmp(a, "connection"))
        {
            connection = true;
        }
        else if (stricasecmp(a, "upgrade"))
        {
            if (!stricasecmp(b, "websocket"))
            {
                printLog("verifyHandshakeHeaders", "Unexpected value '%s' of Upgrade filed", b);
                break;
            }
            upgrade = true;

        }
        else if (stricasecmp(a, "Sec-WebSocket-Version"))
        {
            if (!stricasecmp(b, "13"))
            {
                printLog("verifyHandshakeHeaders","Unexpected value '%s' of Sec-WebSocket-Version filed", b);
                break;
            }
            version = true;
        }
        else if (stricasecmp(a, "Sec-WebSocket-Key"))
        {
            if (!key)
            {
                key = true;
                secKey = malloc(strlen(b) + 1);
                strcpy(secKey, b);
            }
        }
        cur1 = cur2;
    }

    if (!(connection && upgrade && version && key))
    {
        printLog("verifyHandshakeHeaders", "Missing necessary fields");
        if (key) free((void*)secKey); // 釋放申請的記憶體
        return NULL;
    }
    return secKey;
}

int wsShakeHands(const char* recvBuff, int recvLen, SOCKET socket, const char* path)
{
#define RECV_BUFLEN 0X40001
#define HTTP_MAXLEN 1536
#define HTTP_400 "HTTP/1.1 400 Bad Request\r\n\r\n"

    // HTTP握手包太長
    if (recvLen > HTTP_MAXLEN)
    {
        send(socket, HTTP_400, strlen(HTTP_400), 0);
        printLog("wsShakeHands","Request too long");
        return -1;
    }

    // 注:recvBuff不以'\0'結尾
    char resText[HTTP_MAXLEN + 1];
    memcpy(resText, recvBuff, recvLen);
    resText[recvLen] = '\0';

    char requestLine[512];
    sprintf(requestLine, "GET %s%s HTTP/1.1\r\n", (strlen(path) == 0 || path[0] != '/') ? "/" : "", path);

    // 注:路徑部分也被不區分大小寫的比較
    if (!strnicasecmp(resText, requestLine, strlen(requestLine)))
    {
        send(socket, HTTP_400, strlen(HTTP_400), 0);
        printLog("wsShakeHands","Unexpected request line");
        printLog("wsShakeHands", resText);
        return -1;
    }

    const char* secKey = verifyHandshakeHeaders(resText, recvLen);

    if (!secKey)
    {
        send(socket, HTTP_400, strlen(HTTP_400), 0);
        return -1;
    }

    // 獲取Sec-WebSocket-Accept
    char acptBuff[128];
    getSecWebSocketAcceptKey(secKey, acptBuff, sizeof(acptBuff));
    printLog("wsShakeHands","Sec-WebSocket-Key is '%s'", secKey);
    printLog("wsShakeHands", "Sec-WebSocket-Accept is '%s'", acptBuff);
    free((void*)secKey);   // 釋放secKey

    // 協議升級
    char resBuff[256];

    // 注:當前的CORS設定可能會導致安全問題
    // 注:響應中沒有包含Sec-Websocket-Protocol頭,代表不接受任何客戶端請求的ws擴充套件
    const char resHeader[] =
        "HTTP/1.1 101 ojbk\r\n"
        "Connection: Upgrade\r\n"
        "Upgrade: websocket\r\n"
        "Sec-WebSocket-Accept: %s\r\n"
        "Access-Control-Allow-Origin: *\r\n"
        "\r\n"
        ;

    int resLen = sprintf(resBuff, resHeader, acptBuff);

    // Send data to the client
    int iSendResult = send(socket, resBuff, resLen, 0);

    if (iSendResult == SOCKET_ERROR)
    {
        return -1;
    }

    printLog("wsShakeHands","Bytes sent: %d", iSendResult);
    printLog("wsShakeHands","WebSocket handshake succeeded");

    return 0;
}

receiveComingData方法裡處理websocket資料的方法wsClientDataHandle程式碼

int readWebSocketFrameStream(WsFrame* wsFrame, const char* buff, int len)
{
  if (wsFrame->buff == NULL)
  {
    wsFrame->buff = malloc(len);
    wsFrame->buffSize = len;
    memcpy(wsFrame->buff, buff, len);
  }
  else
  {
    char* copyStartAddr;
    int requiedLen = wsFrame->buffSize + len;
    wsFrame->buff = realloc((void*)wsFrame->buff, requiedLen);
    copyStartAddr = wsFrame->buff + wsFrame->buffSize;
    wsFrame->buffSize = requiedLen;
    memcpy(copyStartAddr, buff, len);
  }

  // 消耗的資料量
  int consumed = 0;

stateTransitionBegin:

  switch (wsFrame->state)
  {
  case frameState_init:

    if (wsFrame->buffSize < 1)
    {
      return consumed;
    }

    wsFrame->FIN = !!(wsFrame->buff[0] & 0X80);

    // RSV位不全為0,存在擴充套件協議,伺服器不處理擴充套件協議
    if ((wsFrame->buff[0] & 0X70) != 0)
    {
      wsFrame->state = frameState_failure;
      break;
    }

    int opcode = wsFrame->buff[0] & 0X0F;

    if (opcode == 0X0)
    {
      wsFrame->frameType = frameType_continuation;
    }
    else if (opcode == 0X1)
    {
      wsFrame->frameType = frameType_text;
    }
    else if (opcode == 0X2)
    {
      wsFrame->frameType = frameType_binary;
    }
    else if (opcode == 0X8)
    {
      wsFrame->frameType = frameType_connectionClose;
    }
    else if (opcode == 0X9)
    {
      wsFrame->frameType = frameType_ping;
    }
    else if (opcode == 0XA)
    {
      wsFrame->frameType = frameType_pong;
    }
    else
    {
      wsFrame->state = frameState_failure;
      break;
    }

    consumed += 1;
    wsFrame->handledLen += 1;
    wsFrame->headerLen += 1;
    wsFrame->state = frameState_firstByte;

    break;

  case frameState_firstByte:

    if (wsFrame->buffSize < 2)
    {
      return consumed;
    }

    // 標準規定客戶端傳入幀的掩碼位必須不為0
    if ((wsFrame->buff[1] & 0X80) == 0)
    {
      wsFrame->state = frameState_failure;
    }

    wsFrame->state = frameState_mask;

    break;

  case frameState_mask:

    if (wsFrame->buffSize < 2)
    {
      return consumed;
    }

    uint8_t payloadLen = wsFrame->buff[1] & 0X7F;

    // frame-payload-length-7
    if (payloadLen < 126)
    {
      wsFrame->payloadLen = payloadLen;
      wsFrame->state = frameState_7bitLength;
    }
    else if (payloadLen == 126)
    {
      wsFrame->state = frameState_16bitLengthWait;
    }
    else if (payloadLen == 127)
    {
      wsFrame->state = frameState_63bitLengthWait;
    }

    consumed += 1;
    wsFrame->headerLen += 1;
    wsFrame->handledLen += 1;

    break;

  case frameState_7bitLength:

    // 2位元組共有欄位 + 0位元組附加長度欄位 + 4位元組掩碼
    if (wsFrame->buffSize < 6)
    {
      return consumed;
    }

    for (int i = 0; i < 4; i++)
    {
      wsFrame->mask[i] = wsFrame->buff[i + 2];
    }

    consumed += 4;
    wsFrame->headerLen += 4;
    wsFrame->handledLen += 4;
    wsFrame->state = frameState_maskingKey;
    break;

  case frameState_16bitLengthWait:

    if (wsFrame->buffSize < 4)
    {
      return consumed;
    }

    wsFrame->payloadLen = ((uint16_t)wsFrame->buff[2] << 8) + (uint16_t)wsFrame->buff[3];
    consumed += 2;
    wsFrame->headerLen += 2;
    wsFrame->handledLen += 2;
    wsFrame->state = frameState_16bitLength;

    break;

  case frameState_63bitLengthWait:

    if (wsFrame->buffSize < 10)
    {
      return consumed;
    }
    unsigned char* recvBuff = wsFrame->buff;

    // 注:標準規定64位時最高bit必須為0,這裡未作處理                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
    wsFrame->payloadLen =
      ((uint64_t)recvBuff[2] << (8 * 7)) +
      ((uint64_t)recvBuff[3] << (8 * 6)) +
      ((uint64_t)recvBuff[4] << (8 * 5)) +
      ((uint64_t)recvBuff[5] << (8 * 4)) +
      ((uint64_t)recvBuff[6] << (8 * 3)) +
      ((uint64_t)recvBuff[7] << (8 * 2)) +
      ((uint64_t)recvBuff[8] << (8 * 1)) +
      ((uint64_t)recvBuff[9] << (8 * 0));

    consumed += 8;
    wsFrame->headerLen += 8;
    wsFrame->handledLen += 8;
    wsFrame->state = frameState_63bitLength;

    break;

  case frameState_16bitLength:

    // 2位元組共有欄位 + 2位元組附加長度欄位 + 4位元組掩碼

    if (wsFrame->buffSize < 8)
    {
      return consumed;
    }

    for (int i = 0; i < 4; i++)
    {
      wsFrame->mask[i] = wsFrame->buff[i + 4];
    }

    consumed += 4;
    wsFrame->headerLen += 4;
    wsFrame->handledLen += 4;

    wsFrame->state = frameState_maskingKey;

    break;

  case frameState_63bitLength:

    // 2位元組共有欄位 + 8位元組附加長度欄位 + 4位元組掩碼

    if (wsFrame->buffSize < 14)
    {
      return consumed;
    }

    for (int i = 0; i < 4; i++)
    {
      wsFrame->mask[i] = wsFrame->buff[i + 10];
    }

    consumed += 4;
    wsFrame->headerLen += 4;
    wsFrame->handledLen += 4;

    wsFrame->state = frameState_maskingKey;

    break;

  case frameState_maskingKey:

    wsFrame->state = frameState_readingData;

    break;

  case frameState_readingData:

    ;    // case第一個語句不能是變數宣告
    uint64_t total = wsFrame->payloadLen + wsFrame->headerLen;

    // 注意 buff的長度可能大於幀總長度
    // 因為TCP是面向位元組流的,buff中有可能包含下一幀的資料
    // 所以讀取時要根據幀頭和載荷長度來判斷最多讀多少資料
    if (wsFrame->buffSize >= total)
    {
      consumed += total - wsFrame->handledLen;
      wsFrame->handledLen = total;
      wsFrame->state = frameState_success;
    }
    else
    {
      consumed += wsFrame->buffSize - wsFrame->handledLen;
      wsFrame->handledLen = wsFrame->buffSize;
      return consumed;
    }

    break;

  case frameState_success:

    return consumed;

    break;
  case frameState_failure:

    return consumed;
    break;
  }

  goto stateTransitionBegin;

  return consumed;
}

int wsFrameSend(SOCKET socket, const char* buff, int len, FrameType type)
{
  int newLen;
  const char* frame = convertToWebSocketFrame(buff, type, len, &newLen);

  int iSendResult = send(socket, frame, newLen, 0);

  if (iSendResult == SOCKET_ERROR)
  {
    printLog("wsFrameSend", 1, "Send failed: %d", WSAGetLastError());
    goto wsFrameSendEnd;
  }


wsFrameSendEnd:
  free((void*)frame);
  return iSendResult;
}

// 處理WebSocket幀資料,返回-1代表需要關閉連線
int wsClientDataHandle(const char* recvBuff, int recvLen, Client* client)
{
  WsFrame* wsFrame = &client->wsFrame;
  if (recvLen == 0)
  {
    return 0;
  }

  int consume = readWebSocketFrameStream(wsFrame, recvBuff, recvLen);

  if (wsFrame->state == frameState_success)
  {
    // 暫時不處理多幀資料,遇到多幀資料關閉連線
    if (wsFrame->FIN == 0)
    {
      return -1;
    }

    // 客戶端希望關閉連線
    if (wsFrame->frameType == frameType_connectionClose)
    {
      return -1;
    }

    // 遇到意料之外的幀型別
    if (wsFrame->frameType == frameType_binary ||
      wsFrame->frameType == frameType_pong ||
      wsFrame->frameType == frameType_continuation
      )
    {
      return -1;
    }

    uint64_t payloadLen = wsFrame->payloadLen;
    u_char* payload = wsFrame->buff + wsFrame->headerLen;

    // 解碼載荷
    for (uint64_t j = 0; j < payloadLen; j++)
    {
      payload[j] = payload[j] ^ wsFrame->mask[j % 4];
    }

    int iSendResult = 0;

    // 心跳
    if (wsFrame->frameType == frameType_ping)
    {
      wsFrameSend(client->socket, payload, payloadLen, frameType_pong);
    }

    // 處理文字資料
    if (wsFrame->frameType == frameType_text)
    {
      wsClientTextDataHandle(payload, payloadLen, client->socket);
    }
  }

  // 一個幀接收完成並處理完畢後釋放記憶體
  if (wsFrame->state == frameState_success)
  {
    freeWebSocketFrame(wsFrame);
  }

  // 解析ws幀出錯,釋放記憶體並通知關閉連線
  if (wsFrame->state == frameState_failure)
  {
    freeWebSocketFrame(wsFrame);
    return -1;
  }

  // 傳入的資料不止包含當前幀,包含下一幀的資料
  if (consume != recvLen)
  {
    return wsClientDataHandle(recvBuff + consume, recvLen - consume, client);
  }

  return 0;
}

處理文字資料的方法wsClientTextDataHandle程式碼

// 處理資料,也可以寫在別處做回撥函式
void wsClientTextDataHandle(const char* payload, uint64_t payloadLen, SOCKET socket)
{
    const char* parseEnd;
    cJSON* json = cJSON_ParseWithOpts(payload, &parseEnd, 0);

    if (json == NULL)
    {
        const char* error_ptr = cJSON_GetErrorPtr();
        if (error_ptr != NULL)
        {
            printLog("jsonParse",  "Error before: %d", error_ptr - payload);
        }
        return;
    }

    const cJSON* j_msg = cJSON_GetObjectItemCaseSensitive(json, "msg");
    const cJSON_bool e_msg = cJSON_IsString(j_msg);
    const char* v_msg = e_msg ? j_msg->valuestring : NULL;

    char* gbkText = UTF8ToGBK(v_msg);
    sendJSON(socket, "send", GBKToUTF8(gbkText));
    free((void*)gbkText);

}

給客戶端傳送訊息的程式碼

int wsFrameSend(SOCKET socket, const char* buff, int len, FrameType type)
{
    int newLen;
    const char* frame = convertToWebSocketFrame(buff, type, len, &newLen);

    int iSendResult = send(socket, frame, newLen, 0);

    if (iSendResult == SOCKET_ERROR)
    {
        printLog("wsFrameSend", 1, "Send failed: %d", WSAGetLastError());
        goto wsFrameSendEnd;
    }

    printLog("wsFrameSend","Bytes sent: %d", iSendResult);

wsFrameSendEnd:
    free((void*)frame);
    return iSendResult;
}
void sendJSON(SOCKET socket, const char* event, const char* data)
{
    cJSON* root = cJSON_CreateObject();
    cJSON_AddItemToObject(root, "event", cJSON_CreateString(event));
    cJSON_AddItemToObject(root, "data", cJSON_CreateString(data));

    const char* jsonStr = cJSON_PrintUnformatted(root);
    wsFrameSend(socket, jsonStr, strlen(jsonStr), frameType_text);

    cJSON_Delete(root);
    free((void*)jsonStr);
}

到這裡主要的核心程式碼就寫完了,接下來測試一下

int main()
{
    int result = serverStart("127.0.0.1", 1024, "/");

    if (result != 0)
    {
        MessageBoxA(NULL, "wsserver start failed", "WebSocket Plugin", MB_OK | MB_ICONERROR);
    }
    else
    {
        //fileLog("websocket server startup success");
    }
    system("pause");
    return 0;
}

測試成功 如圖所示