Visual Studio 2019使用C語言進行websocket編程
一直在寫C#程式碼好多年不寫C語言程式碼了,記錄一下之前某個項目里用C寫的一個websocket服務,用C的優勢是寫的東西體積小性能高,但是寫業務的話還得用C#、Java之類的語言,不然會折騰死人。。。
用Visual Studio新建一個C++(因為不能直接建C語言項目)項目,我演示就創建一個控制台項目。項目創建完後首先要添加socket編程需要的依賴庫ws2_32.lib,添加方式如下圖
也可以在程式碼文件里添加這句程式碼:#
添加完成後就可以開始寫程式碼了,說句題外話,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; }
測試成功 如圖所示