Lập trình cho phép Excel kết nối với máy chủ WebSocket bằng Visual C++ (1 người xem)

Người dùng đang xem chủ đề này

Tôi tuân thủ nội quy khi đăng bài

nguyendang95

Thành viên hoạt động
Tham gia
25/5/22
Bài viết
172
Được thích
161
Mặc dù ngôn ngữ lập trình VBA tích hợp trong Excel cho phép người dùng bổ sung thêm tính năng cho Excel, tuy vậy những hạn chế của ngôn ngữ lập trình này khiến cho khả năng hiện thực hóa ý tưởng của người dùng bị thu hẹp lại đáng kể. WebSocket là giao thức kết nối thời gian thực cho phép thiết lập cơ chế trao đổi dữ liệu hai chiều giữa máy chủ và máy khách, tuy nhiên với việc VBA chỉ hỗ trợ lập trình đơn luồng (STA), việc viết code trở nên bất khả thi, cho nên người dùng sẽ cần phải tìm đến ngôn ngữ lập trình khác để giải quyết vấn đề này.
Trong bài viết này sử dụng ngôn ngữ lập trình Visual C++ để giải quyết bài toán trên, Excel sẽ làm việc thông qua COM add-in mà người dùng thiết kế. Để kết nối với máy chủ WebSocket, người dùng có thể sử dụng nhiều thư viện khác nhau, tuy nhiên trong bài viết này sử dụng WinHTTP API bởi vì bộ API này có sẵn trong Windows (từ Windows 8 trở đi), gọn nhẹ dễ sử dụng.
Để tiện cho việc trình bày, bài viết này sẽ trình bày code tạo một ứng dụng dạng console, trong đó có sử dụng WinHTTP API để kết nối đến máy chủ WebSocket trong chế độ đồng bộ (synchronous mode) và chế độ bất đồng bộ (asynchronous mode).
Chế độ đồng bộ:
Trong chế độ này, chương trình sẽ xử lý code lần lượt từ trên xuống dưới, khi gọi các hàm nhận/gửi, những hàm này chỉ trả về khi chương trình nhận được dữ liệu từ máy chủ/hàm gửi đã gửi xong dữ liệu đến máy chủ, với nhược điểm này thread chạy code sẽ bị chặn cho đến khi hàm được gọi trả về, tệ hơn nữa khi máy chủ WebSocket vì một lý do nào đó (đường truyền gặp sự cố hoặc máy chủ có vấn đề, v.v.,) khiến cho máy chủ không thể gửi yêu cầu đóng kết nối khiến cho chương trình vẫn lầm tưởng kết nối vẫn đang được duy trì khiến cho hàm gọi bị treo vĩnh viễn không thể trả về, khiến cho chương trình bị treo hoàn toàn.
C++:
#include <stdio.h>
#include <Windows.h>
#include <winhttp.h>
#pragma comment(lib, "winhttp.lib")
#include <string>
#include <iostream>

void ConnectWebSocketSynchronously();
DWORD WINAPI WebSocketSendThreadProc(LPVOID lpParam);

typedef struct tagWEBSOCKETDATA {
    HINTERNET hRequest = NULL;
    HINTERNET hWebSocket = NULL;
    BOOL bConnected = FALSE;
    BYTE buffer[8192]{};
    HANDLE hEvent = NULL;
    CRITICAL_SECTION cs;
    DWORD dwBytesRead = 0;
    DWORD dwTotalBytesRead = 0;
    DWORD dwBufferLength = 8192;
    BYTE* pbCurrentBufferPointer = 0;
}WEBSOCKETDATA, *LPWEBSOCKETDATA;

int main()
{
    //ConnectWebSocketAsynchronously();
    ConnectWebSocketSynchronously();
    return 0;
}

void ConnectWebSocketSynchronously() {
    //Khởi tạo phiên làm việc WinHTTP, không kết nối qua máy chủ proxy, chạy đồng bộ
    HINTERNET hSession = WinHttpOpen(L"WinHttpWebSocket", WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
    DWORD dwError = NO_ERROR;
    if (!hSession) {
        dwError = GetLastError();
        wprintf_s(L"WinHttpOpen failed with error %u", dwError);
        return;
    }
    //Chuẩn bị thiết lập kết nối
    HINTERNET hConnect = WinHttpConnect(hSession, L"echo.websocket.org", INTERNET_DEFAULT_HTTPS_PORT, 0);
    if (!hConnect) {
        dwError = GetLastError();
        wprintf_s(L"WinHttpConnect failed with error %u", dwError);
        WinHttpCloseHandle(hSession);
        return;
    }
    //Chuẩn bị những tham số kết nối cần thiết
    HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", L"/", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
    if (!hRequest) {
        dwError = GetLastError();
        wprintf_s(L"WinHttpOpenRequest failed with error %u", dwError);
        WinHttpCloseHandle(hSession);
        WinHttpCloseHandle(hConnect);
        return;
    }
    //Thiết lập tùy chọn nâng cấp kết nối HTTP thành kết nối WebSocket
    if (!WinHttpSetOption(hRequest, WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, NULL, 0)) {
        dwError = GetLastError();
        wprintf_s(L"WinHttpSetOption failed with error %u", dwError);
        WinHttpCloseHandle(hSession);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hRequest);
        return;
    }
    //Gửi yêu cầu đến máy chủ
    if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, NULL)) {
        dwError = GetLastError();
        wprintf_s(L"WinHttpSendRequest failed with error %u", dwError);
        WinHttpCloseHandle(hSession);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hRequest);
        return;
    }
    //Nhận phản hồi từ máy chủ
    if (!WinHttpReceiveResponse(hRequest, NULL)) {
        dwError = GetLastError();
        wprintf_s(L"WinHttpReceiveResponse failed with error %u", dwError);
        WinHttpCloseHandle(hSession);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hRequest);
        return;
    }
    //Lấy mã status
    DWORD dwStatusCode = 0, dwStatusCodeLength = sizeof(DWORD);
    if (!WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &dwStatusCode, &dwStatusCodeLength, WINHTTP_NO_HEADER_INDEX)) {
        dwError = GetLastError();
        wprintf_s(L"WinHttpQueryHeaders failed with error %u", dwError);
        WinHttpCloseHandle(hSession);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hRequest);
        return;
    }
    //Nếu mã status trả về khác 101 thì nghĩa là máy chủ không hỗ trợ kết nối WebSocket
    if (dwStatusCode != 101) {
        wprintf_s(L"%s", L"This server does not support WebSocket connection");
        WinHttpCloseHandle(hSession);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hRequest);
        return;
    }
    //Hoàn tất thiết lập kết nối WebSocket
    HINTERNET hWebSocket = NULL;
    if (!(hWebSocket = WinHttpWebSocketCompleteUpgrade(hRequest, NULL))) {
        dwError = GetLastError();
        wprintf_s(L"WinHttpWebSocketCompleteUpgrade failed with error %u", dwError);
        WinHttpCloseHandle(hSession);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hRequest);
        return;
    }
    //Handle quản lý việc gửi yêu cầu HTTP không còn cần thiết nữa, nên đóng lại để giải phóng bộ nhớ
    WinHttpCloseHandle(hRequest);
    //Chuẩn bị các dữ liệu cần thiết để làm việc với kết nối WebSocket
    WEBSOCKETDATA wsData = { 0 };
    wsData.hWebSocket = hWebSocket;
    wsData.bConnected = TRUE;
    wprintf_s(L"%s\n", L"Successfully connected to the server");
    //Tạo một thread mới dùng để nhận nhập liệu từ người dùng và gửi dữ liệu đến máy chủ WebSocket
    HANDLE hThread = CreateThread(NULL, 0, WebSocketSendThreadProc, (LPVOID)&wsData, 0, NULL);
    if (!hThread) {
        wprintf_s(L"CreateThread failed with error %u", GetLastError());
        WinHttpCloseHandle(hSession);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hWebSocket);
        return;
    }
    DWORD dwBufferLength = 8192;
    /*
        Liên tục nhận dữ liệu từ máy chủ cho đến khi máy chủ đóng kết nối
        Thiết lập bộ đệm (buffer) đủ để chứa phản hồi từ máy chủ
    */
    
    while (wsData.bConnected) {
        DWORD dwBytesRead = 0;
        WINHTTP_WEB_SOCKET_BUFFER_TYPE bufferType;
        wsData.dwTotalBytesRead = 0;
        wsData.dwBytesRead = 0;
        wsData.pbCurrentBufferPointer = wsData.buffer;
        wsData.dwBufferLength = 8192;
        memset(wsData.buffer, 0, wsData.dwBufferLength);
        do {
            //Nhận phản hồi từ máy chủ
            dwError = WinHttpWebSocketReceive(hWebSocket, wsData.pbCurrentBufferPointer, wsData.dwBufferLength, &(wsData.dwBytesRead), &bufferType);
            if (dwError != NO_ERROR) {
                wsData.bConnected = FALSE;
                WinHttpWebSocketClose(hWebSocket, WINHTTP_WEB_SOCKET_ABORTED_CLOSE_STATUS, NULL, 0);
                wprintf_s(L"WinHttpWebSocketReceive failed with error %u\n", dwError);
                break;
            }
            wsData.pbCurrentBufferPointer += dwBytesRead;
            wsData.dwTotalBytesRead += dwBytesRead;
            wsData.dwBufferLength -= dwBytesRead;
            //Kiểm tra nội dung kiểu bộ đệm nhận được từ máy chủ
            switch (bufferType) {
            /*
                Trường hợp bộ đệm mang thông điệp mã hóa UTF-8
                Kiểu bộ đệm này nghĩa là máy chủ gửi thông điệp hoàn chỉnh
                Hoặc, đây là phần thông điệp cuối cùng tạo thành thông điệp hoàn chỉnh
            */
            case WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE:
            {
                //Cấp phát bộ nhớ động để chứa nội dung hữu ích từ bộ đệm
                char* pszResponse = new char[wsData.dwTotalBytesRead + 1];
                if (!pszResponse) {
                    wsData.bConnected = FALSE;
                    WinHttpWebSocketClose(hWebSocket, WINHTTP_WEB_SOCKET_ABORTED_CLOSE_STATUS, NULL, 0);
                    wprintf_s(L"%s\n", L"Out of memory");
                    break;
                }
                //Sao chép nội dung hữu ích từ bộ đệm sang vùng nhớ vừa cấp phát động
                strcpy_s(pszResponse, static_cast<rsize_t>(wsData.dwTotalBytesRead) + 1, (char*)wsData.buffer);
                //Xóa thông tin trong bộ đệm
                memset(wsData.buffer, 0, wsData.dwBufferLength);
                printf_s("Response from the server: %s\n", pszResponse);
                //Dọn dẹp bộ nhớ sau khi sử dụng xong
                delete[] pszResponse;
                break;
            }
            //Trường hợp bộ đệm chứa thông điệp đóng kết nối
            case WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE:
            {
                //Gửi yêu cầu đóng kết nối
                dwError = WinHttpWebSocketClose(hWebSocket, WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS, NULL, 0);
                //Thiết lập điều kiện để thoát khỏi vòng lặp
                wsData.bConnected = FALSE;
                if (dwError != NO_ERROR) {
                    wprintf_s(L"WinHttpWebSocketClose failed with error %u\n", dwError);
                    break;
                }
                wprintf_s(L"%s\n", L"Disconnected from the server");
                break;
            }
            if (!wsData.dwBufferLength) {
                WinHttpWebSocketClose(hWebSocket, WINHTTP_WEB_SOCKET_MESSAGE_TOO_BIG_CLOSE_STATUS, NULL, 0);
                wsData.bConnected = FALSE;
                break;
            }
            }
            /*
                Khi gặp kiểu bộ đệm này, tức là máy chủ chia thông điệp thành nhiều phần rồi gửi
                Nên cần tiếp tục nhận phản hồi từ máy chủ cho đến khi kiểu bộ đệm trả về WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE
            */
        } while (bufferType == WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE);
    }
    //Chờ cho đến khi thread kết thúc
    WaitForSingleObject(hThread, INFINITE);
    //Dọn dẹp bộ nhớ
    CloseHandle(hThread);
    WinHttpCloseHandle(hSession);
    WinHttpCloseHandle(hConnect);
    WinHttpCloseHandle(hWebSocket);
};

//Thread chịu trách nhiệm nhận nhập liệu từ người dùng và gửi dữ liệu đến máy chủ
DWORD WINAPI WebSocketSendThreadProc(LPVOID lpParam) {
    if (!lpParam) return 1;
    LPWEBSOCKETDATA pWsData = (LPWEBSOCKETDATA)lpParam;
    HINTERNET hWebSocket = pWsData->hWebSocket;
    std::string strRequest{};
    DWORD dwError = NO_ERROR;
    //Lập lại hành động cho đến khi đóng kết nối
    while (pWsData->bConnected) {
        //Nhận nhập liệu từ người dùng
        std::cin >> strRequest;
        if (!strRequest.length()) continue;
        //Nếu nhận được "exit" thì gửi yêu cầu đóng kết nối và kết thúc thread
        else if (!strcmp("exit", strRequest.c_str())) {
            DWORD dwError = WinHttpWebSocketClose(hWebSocket, WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS, NULL, 0);
            pWsData->bConnected = FALSE;
            if (dwError != NO_ERROR) {
                wprintf_s(L"WinHttpWebSocketClose failed with error %u\n", dwError);
                return 1;
            }
            wprintf_s(L"%s\n", L"Disconnected from the server");
            break;
        }
        //Gửi thông tin đến máy chủ
        dwError = WinHttpWebSocketSend(hWebSocket, WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE, (LPVOID)strRequest.c_str(), static_cast<DWORD>(strRequest.size()));
        if (dwError != NO_ERROR) {
            wprintf_s(L"Failed to send message to the server with error %u\n", dwError);
            continue;
        }
    }
    return 0;
}

1760419019953.png
 
Lần chỉnh sửa cuối:
Chế độ bất đồng bộ (Asynchronous Mode):
Trong chế độ này, mỗi lần chạy hàm thay vì phải chờ hàm trả về kết quả để tiếp tục luồng công việc như trong chế độ đồng bộ, chế độ bất đồng bộ cho phép hàm trả về ngay và sẽ gửi thông báo cho chương trình ngay khi nhận được phản hồi (sự kiện nhận, gửi, đóng kết nối, v.v.,). Chế độ này hoạt động thông qua hàm gọi lại (callback), trong đó mỗi khi có thông báo mới thì hàm gọi lại sẽ được kích hoạt, trong hàm này người dùng có thể xử lý các sự kiện liên quan đến việc nhận, gửi dữ liệu từ máy chủ. Hàm gọi lại chạy trên thread của thread pool cho nên thread chính của chương trình sẽ có thời gian để làm những việc khác (vd: Cập nhật giao diện người dùng UI chẳng hạn) trong quá trình chờ nhận thông báo.
C++:
#include <stdio.h>
#include <Windows.h>
#include <winhttp.h>
#pragma comment(lib, "winhttp.lib")
#include <string>
#include <iostream>

void ConnectWebSocketAsynchronously();
VOID CALLBACK HttpCallbackProc(HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength);
VOID CALLBACK WebSocketCallbackProc(HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength);

typedef struct tagWEBSOCKETDATA {
    HINTERNET hRequest = NULL;
    HINTERNET hWebSocket = NULL;
    BOOL bConnected = FALSE;
    BYTE buffer[8192]{};
    HANDLE hEvent = NULL;
    CRITICAL_SECTION cs;
    DWORD dwBytesRead = 0;
    DWORD dwTotalBytesRead = 0;
    DWORD dwBufferLength = 8192;
    BYTE* pbCurrentBufferPointer = 0;
}WEBSOCKETDATA, *LPWEBSOCKETDATA;

int main()
{
    ConnectWebSocketAsynchronously();
    return 0;
}

void ConnectWebSocketAsynchronously() {
    //Thiết lập phiên làm việc WinHTTP, hoạt động bất đồng bộ
    HINTERNET hSession = WinHttpOpen(L"WinHttp WebSocket", WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC);
    DWORD dwError = NO_ERROR;
    if (!hSession) {
        dwError = GetLastError();
        wprintf_s(L"WinHttpOpen failed with error %u", dwError);
        return;
    }
    //Cài đặt hàm gọi lại (callback) để xử lý các sự kiện
    if (WinHttpSetStatusCallback(hSession, HttpCallbackProc, WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS, NULL) == WINHTTP_INVALID_STATUS_CALLBACK) {
        dwError = GetLastError();
        wprintf_s(L"WinHttpOpen failed with error %u", dwError);
        WinHttpCloseHandle(hSession);
        return;
    }
    //Thiết lập tham số kết nối
    HINTERNET hConnect = WinHttpConnect(hSession, L"push.coinmarketcap.com", INTERNET_DEFAULT_HTTPS_PORT, 0);
    if (!hConnect) {
        dwError = GetLastError();
        wprintf_s(L"WinHttpConnect failed with error %u", dwError);
        WinHttpCloseHandle(hSession);
        return;
    }
    //Thiết lập tham số kết nối
    HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", L"/ws?device=web&client_source=coin_detail_page", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
    if (!hRequest) {
        dwError = GetLastError();
        wprintf_s(L"WinHttpOpenRequest failed with error %u", dwError);
        WinHttpCloseHandle(hSession);
        WinHttpCloseHandle(hConnect);
        return;
    }
    //Thêm header trước khi gửi yêu cầu
    wchar_t pwszOrigin[] = L"Origin: https://coinmarketcap.com";
    size_t len = wcslen(pwszOrigin);
    if (!WinHttpAddRequestHeaders(hRequest, L"Origin: https://coinmarketcap.com", static_cast<DWORD>(len), WINHTTP_ADDREQ_FLAG_ADD)) {
        dwError = GetLastError();
        wprintf_s(L"WinHttpAddRequestHeaders failed with error %u", dwError);
        WinHttpCloseHandle(hSession);
        WinHttpCloseHandle(hConnect);
       WinHttpCloseHandle(hRequest);
        return;
    }
    //Thiết lập tùy chọn nâng cấp giao thức thành giao thức WebSocket
    if (!WinHttpSetOption(hRequest, WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, NULL, 0)) {
        dwError = GetLastError();
        wprintf_s(L"WinHttpSetOption failed with error %u", dwError);
        WinHttpCloseHandle(hSession);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hRequest);
        return;
    }
    //Chuẩn bị dữ liệu cần thiết cho việc kết nối WebSocket
    WEBSOCKETDATA wsData = { 0 };
    wsData.hRequest = hRequest;
    InitializeCriticalSection(&(wsData.cs));
    if (!(wsData.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) {
        wprintf_s(L"%s", L"Failed to create event");
        WinHttpCloseHandle(hSession);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hRequest);
        DeleteCriticalSection(&(wsData.cs));
        return;
    }
    //Gửi yêu cầu HTTP
    if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, (DWORD_PTR)&wsData)) {
        dwError = GetLastError();
        wprintf_s(L"WinHttpSendRequest failed with error %u", dwError);
        WinHttpCloseHandle(hSession);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hRequest);
        CloseHandle(wsData.hEvent);
        DeleteCriticalSection(&(wsData.cs));
        return;
    }
    //Chờ yêu cầu nâng cấp giao thức WebSocket hoàn thành
    if (WaitForSingleObject(wsData.hEvent, INFINITE) != WAIT_OBJECT_0) {
        dwError = GetLastError();
        wprintf_s(L"WaitForSingleObject failed with error %u", dwError);
        WinHttpCloseHandle(hSession);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hRequest);
        CloseHandle(wsData.hEvent);
        DeleteCriticalSection(&(wsData.cs));
        return;
    }
    //Việc gửi yêu cầu HTTP đã xong, nên thiết lập không nhận thông báo nữa
    WinHttpSetStatusCallback(hSession, NULL, WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS, NULL);
    if (!wsData.bConnected) {
        wprintf_s(L"%s\n", L"Failed to establish WebSocket connection");
        WinHttpCloseHandle(hSession);
        WinHttpCloseHandle(hConnect);
        CloseHandle(wsData.hEvent);
        DeleteCriticalSection(&(wsData.cs));
        return;
    }
    //Thiết lập lại trạng thái của Event về ban đầu để chuẩn bị làm việc với kết nối WebSocket
    if (!ResetEvent(wsData.hEvent)) {
        dwError = GetLastError();
        wprintf_s(L"WinHttpSetStatusCallback failed with error %u", dwError);
        WinHttpCloseHandle(hSession);
        WinHttpCloseHandle(hConnect);
        CloseHandle(wsData.hEvent);
        DeleteCriticalSection(&(wsData.cs));
        return;
    }
    //Cài đặt hàm gọi lại (callback) để xử lý những sự kiện liên quan đến kết nối WebSocket
    if (WinHttpSetStatusCallback(wsData.hWebSocket, WebSocketCallbackProc, WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS, NULL) == WINHTTP_INVALID_STATUS_CALLBACK) {
        dwError = GetLastError();
        wprintf_s(L"WinHttpSetStatusCallback failed with error %u", dwError);
        WinHttpCloseHandle(hSession);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(wsData.hWebSocket);
        CloseHandle(wsData.hEvent);
        DeleteCriticalSection(&(wsData.cs));
        return;
    }
    //Chuẩn bị tham số để gửi đến máy chủ
    char pszPayload[] = R"({"method":"RSUBSCRIPTION","params":["main-site@crypto_price_15s@{}@detail","1,1027,2010,1839"]})";
    len = strlen(pszPayload);
    if ((dwError = WinHttpWebSocketSend(wsData.hWebSocket, WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE, pszPayload, static_cast<DWORD>(len))) != NO_ERROR) {
        dwError = GetLastError();
        wprintf_s(L"WinHttpWebSocketSend failed with error %u", dwError);
        WinHttpCloseHandle(hSession);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(wsData.hWebSocket);
        CloseHandle(wsData.hEvent);
        DeleteCriticalSection(&(wsData.cs));
        return;
    }
    //Nhận dữ liệu từ máy chủ
    if ((dwError = WinHttpWebSocketReceive(wsData.hWebSocket, wsData.pbCurrentBufferPointer, wsData.dwBufferLength, NULL, NULL)) != NO_ERROR) {
        dwError = GetLastError();
        wprintf_s(L"WinHttpSetStatusCallback failed with error %u", dwError);
        WinHttpCloseHandle(hSession);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(wsData.hWebSocket);
        CloseHandle(wsData.hEvent);
        DeleteCriticalSection(&(wsData.cs));
        return;
    }
    //Chờ cho đến khi ngắt kết nối WebSocket
    if (WaitForSingleObject(wsData.hEvent, INFINITE) != WAIT_OBJECT_0) {
        dwError = GetLastError();
        wprintf_s(L"WaitForSingleObject failed with error %u", dwError);
        WinHttpCloseHandle(hSession);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(wsData.hWebSocket);
        CloseHandle(wsData.hEvent);
        DeleteCriticalSection(&(wsData.cs));
        return;
    }
    //Thiết lập không nhận thông báo nữa
    WinHttpSetStatusCallback(wsData.hWebSocket, NULL, WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS, NULL);
    //Dọn dẹp bộ nhớ
    CloseHandle(wsData.hEvent);
    DeleteCriticalSection(&(wsData.cs));
    WinHttpCloseHandle(hSession);
    WinHttpCloseHandle(hConnect);
    WinHttpCloseHandle(wsData.hWebSocket);
}

//Hàm gọi lại này chịu trách nhiệm xử lý những sự kiện phát sinh từ việc gửi yêu cầu nâng cấp giao thức thành giao thức WebSocket
VOID CALLBACK HttpCallbackProc(HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength) {
    if (!dwContext) return;
    LPWEBSOCKETDATA pData = (LPWEBSOCKETDATA)dwContext;
    if (dwInternetStatus != WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING) {
        switch (dwInternetStatus) {
            //Trường hợp gửi yêu cầu đến máy chủ thành công
            case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
            {
                //Nhận phản hồi từ máy chủ
                EnterCriticalSection(&(pData->cs));
                if (!WinHttpReceiveResponse(pData->hRequest, NULL)) {
                    wprintf_s(L"WinHttpReceiveResponse failed with error %u\n", GetLastError());
                    LeaveCriticalSection(&(pData->cs));
                    //Bắn tín hiệu cho thread chính để tiếp tục luồng công việc
                    SetEvent(pData->hEvent);
                    break;
                }
                wprintf_s(L"%s\n", L"Request sent to the server");
                LeaveCriticalSection(&(pData->cs));
            }
            //Trường hợp header trong phản hồi từ máy chủ đã sẵn sàng để truy vấn
            case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
            {
                EnterCriticalSection(&(pData->cs));
                //Chỉ thực hiện việc truy vấn mã status khi chưa nâng cấp thành giao thức WebSocket
                if (!pData->bConnected) {
                    DWORD dwStatusCode = 0, dwStatusCodeLen = sizeof(DWORD);
                    //Truy vấn mã status
                    if (!WinHttpQueryHeaders(pData->hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &dwStatusCode, &dwStatusCodeLen, WINHTTP_NO_HEADER_INDEX)) {
                        wprintf_s(L"WinHttpQueryHeaders failed with error %u\n", GetLastError());
                        //Bắn tín hiệu cho thread chính để tiếp tục luồng công việc
                        SetEvent(pData->hEvent);
                        LeaveCriticalSection(&(pData->cs));
                        break;
                    }
                    //Nếu mã status trả về giá trị khác 101, tức là máy chủ không hỗ trợ kết nối WebSocket
                    if (dwStatusCode != 101) {
                        wprintf_s(L"%s\n", L"The server does not support WebSocket connection");
                        //Bắn tín hiệu cho thread chính để tiếp tục luồng công việc
                        SetEvent(pData->hEvent);
                        LeaveCriticalSection(&(pData->cs));
                        break;
                    }
                    wprintf_s(L"Received status code %u\n", dwStatusCode);
                    pData->pbCurrentBufferPointer = pData->buffer;
                    //Nâng cấp giao thức thành giao thức WebSocket
                    pData->hWebSocket = WinHttpWebSocketCompleteUpgrade(pData->hRequest, (DWORD_PTR)pData);
                    WinHttpCloseHandle(pData->hRequest);
                    pData->hRequest = NULL;
                    if (!pData->hWebSocket) {
                        wprintf_s(L"WinHttpWebSocketCompleteUpgrade failed with error %u\n", GetLastError());
                        //Bắn tín hiệu cho thread chính để tiếp tục luồng công việc
                        SetEvent(pData->hEvent);
                        LeaveCriticalSection(&(pData->cs));
                        break;
                    }
                    //Thông báo rằng đã thiết lập kết nối WebSocket thành công
                    pData->bConnected = TRUE;
                    wprintf_s(L"%s\n", L"Successfully upgraded protocol to WebSocket");
                    //Bắn tín hiệu cho thread chính để tiếp tục luồng công việc
                    SetEvent(pData->hEvent);
                }
                LeaveCriticalSection(&(pData->cs));
                break;
            }
        }
    }
    //Trường hợp có lỗi phát sinh trong quá trình gửi/nhận yêu cầu
    else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR) {
        WINHTTP_ASYNC_RESULT* pResult = (WINHTTP_ASYNC_RESULT*)dwContext;
        wprintf_s(L"An error occurred while sending request to the server with error %u\n", pResult->dwError);
    }
}

//Hàm gọi lại này chịu trách nhiệm xử lý những sự kiện phát sinh trong quá trình làm việc với kết nối WebSocket
VOID CALLBACK WebSocketCallbackProc(HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength) {
    if (!dwContext) return;
    LPWEBSOCKETDATA pData = (LPWEBSOCKETDATA)dwContext;
    if (dwInternetStatus != WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING) {
        switch (dwInternetStatus) {
            //Trường hợp nhận được phản hồi từ máy chủ
            case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
            {
                //Xác định những thông tin cần thiết về kiểu bộ đệm mà máy chủ trả về
                EnterCriticalSection(&(pData->cs));
                WINHTTP_WEB_SOCKET_STATUS* pWebSocketStatus = (WINHTTP_WEB_SOCKET_STATUS*)lpvStatusInformation;
                DWORD dwError = NO_ERROR;
                //Xem xét kiểu bộ đệm
                switch (pWebSocketStatus->eBufferType) {
                    //Trường hợp bộ đệm chứa thông điệp yêu cầu đóng kết nối từ máy chủ
                    case WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS:
                    {
                        //Gửi yêu cầu đóng kết nối đến máy chủ
                        if ((dwError = WinHttpWebSocketClose(hInternet, WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS, NULL, 0)) != NO_ERROR) {
                            wprintf_s(L"WinHttpWebSocketClose failed with error %u\n", GetLastError());
                            SetEvent(pData->hEvent);
                            pData->bConnected = FALSE;
                            LeaveCriticalSection(&(pData->cs));
                            return;
                        }
                        //Thông báo rằng kết nối đã đóng
                        pData->bConnected = FALSE;
                        wprintf_s(L"%s\n", L"The server closed the connection");
                        break;
                    }
                    //Trường hợp máy chủ gửi thông điệp hoàn chỉnh hoặc phần cuối cùng của thông điệp
                    case WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE:
                    {
                        //Đọc bộ đệm
                        pData->pbCurrentBufferPointer += pWebSocketStatus->dwBytesTransferred;
                        pData->dwBufferLength -= pWebSocketStatus->dwBytesTransferred;
                        pData->dwTotalBytesRead += pWebSocketStatus->dwBytesTransferred;
                        //Cấp phát bộ nhớ đệm để chứa nội dung hữu ích từ bộ đệm
                        char* pszResponse = new char[pData->dwTotalBytesRead + 1];
                        if (!pszResponse) {
                            wprintf_s(L"%s\n", L"Error: Out of memory");
                            LeaveCriticalSection(&(pData->cs));
                            return;
                        }
                        //Sao chép nội dung bộ đệm
                        strcpy_s(pszResponse, static_cast<rsize_t>(pData->dwTotalBytesRead) + 1, (char*)pData->buffer);
                        //Sử dụng thông tin nhận được
                        printf_s("Response from the server: %s\n", pszResponse);
                        //Dọn dẹp bộ nhớ sau khi sử dụng xong
                        delete[] pszResponse;
                        //Xóa trắng bộ đệm
                        memset(pData->buffer, 0, pData->dwBufferLength);
                        //Thiết lập lại từ đầu các tham số để chuẩn bị cho việc nhận thông điệp mới
                        pData->pbCurrentBufferPointer = pData->buffer;
                        pData->dwTotalBytesRead = 0;
                        pData->dwBufferLength = 8192;
                        //Tiếp tục chờ nhận dữ liệu mới từ máy chủ
                        if ((dwError = WinHttpWebSocketReceive(hInternet, pData->pbCurrentBufferPointer, pData->dwBufferLength, NULL, NULL)) != NO_ERROR) {
                            wprintf_s(L"WinHttpWebSocketReceive failed with error %u\n", GetLastError());
                            LeaveCriticalSection(&(pData->cs));
                            return;
                        }
                        break;
                    }
                    //Trường hợp chỉ nhận được một phần nhỏ của thông điệp
                    case WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE:
                    {
                        pData->pbCurrentBufferPointer += pWebSocketStatus->dwBytesTransferred;
                        pData->dwBufferLength -= pWebSocketStatus->dwBytesTransferred;
                        pData->dwTotalBytesRead += pWebSocketStatus->dwBytesTransferred;
                        //Nếu đã đầy bộ đệm, yêu cầu đóng kết nối
                        if (!pData->dwBufferLength) {
                            WinHttpWebSocketClose(pData->hWebSocket, WINHTTP_WEB_SOCKET_MESSAGE_TOO_BIG_CLOSE_STATUS, NULL, 0);
                            pData->bConnected = FALSE;
                            break;
                        }
                        //Tiếp tục chờ nhận thêm những phần còn lại của thông điệp
                        if ((dwError = WinHttpWebSocketReceive(hInternet, pData->pbCurrentBufferPointer, pData->dwBufferLength, NULL, NULL)) != NO_ERROR) {
                            wprintf_s(L"WinHttpWebSocketReceive failed with error %u\n", GetLastError());
                            WinHttpWebSocketClose(pData->hWebSocket, WINHTTP_WEB_SOCKET_MESSAGE_TOO_BIG_CLOSE_STATUS, NULL, 0);
                            pData->bConnected = FALSE;
                            break;
                        }
                        wprintf_s(L"%s\n", L"Receiving message from the server...");
                        break;
                    }
                }
                LeaveCriticalSection(&(pData->cs));
                break;
            }
            //Trường hợp việc gửi dữ liệu đến máy chủ đã thành công
            case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE:
            {
                EnterCriticalSection(&(pData->cs));
                wprintf_s(L"Message sent to the server\n");
                LeaveCriticalSection(&(pData->cs));
                break;
            }
            //Trường hợp yêu cầu đóng kết nối đã hoàn tất
            case WINHTTP_CALLBACK_STATUS_CLOSE_COMPLETE:
            {
                EnterCriticalSection(&(pData->cs));
                wprintf_s(L"Disconnected from the server\n");
                SetEvent(pData->hEvent);
                LeaveCriticalSection(&(pData->cs));
                break;
            }
        }
    }
    //Trường hợp có lỗi xảy ra trong quá trình nhận/gửi dữ liệu
    else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR) {
        WINHTTP_ASYNC_RESULT* pResult = (WINHTTP_ASYNC_RESULT*)dwContext;
        wprintf_s(L"An error occurred while sending request to the server with error %u\n", pResult->dwError);
    }
}

1760446848917.png
 
Lần chỉnh sửa cuối:

Bài viết mới nhất

Back
Top Bottom