開源SignalR-Client-CPP使用總結

一、使用背景

??在使用C++對接項目平臺過程中需要使用SignalRClient接收平臺的事件信息。C++版本的SignalRClient使用不是很多,國內(nèi)網(wǎng)站也沒什么資料可供參考。經(jīng)過調(diào)研,項目中決定使用SignalR-Client-CPP開源代碼(https://github.com/SignalR/SignalR-Client-Cpp)。

二、SignalR簡介

??ASP .NET SignalR 是一個ASP .NET 下的類庫,可以在ASP .NET 的Web項目中實現(xiàn)實時通信。什么是實時通信的Web呢?就是讓客戶端(Web頁面)和服務器端可以互相通知消息及調(diào)用方法,當然這是實時操作的。通過SignalR技術服務器將內(nèi)容自動推送到已經(jīng)連接的客戶端,而不是服務器等待客戶端發(fā)起一個新的數(shù)據(jù)請求。

??Websocket是HTML5提供的新的API,可以在Web網(wǎng)頁與服務器端間建立Socket連接,當WebSockets可用時(即瀏覽器支持Html5)SignalR使用WebSockets,當不支持時SignalR將使用其它技術來保證達到相同效果。

??SignalR可以使用最新的WebSocket 傳輸,同時也能夠讓你回退到原有的傳輸方式。你可以直接使用SignalR 使用 WebSocket,因為SignalR 已經(jīng)替你封裝好許多你需要實現(xiàn)的方法。最重要的是你使用SignalR不用擔心為老的客戶端實現(xiàn)WebSocket而采用兩套不同的邏輯編碼方式。使用SignalR 實現(xiàn)WebSocket你不用擔心 WebSocket的更新而去修改代碼,SignalR會在傳輸方式上使用WebSocket最新的傳輸方式,同時提供了一連串的接口能夠讓你來支持不同版本的客戶端。

??SignalR-Client-CPP開源代碼,僅支持WebSocket環(huán)境以及要求SignalR服務端版本在2.0以上才可正常使用。

三、SignalRCPPClient使用

??SignalR客戶端和服務端通信可以有兩種方法HubConnection與PersistentConnection。其中,PersistentConnection方式更加偏向底層,編程模式和websocket類似,使用固定的發(fā)送和接收方法,通過此方法編碼過程可控性大,但是編碼繁瑣,而且基本都是在重復造輪子。而HubConnection方法相對來說是一個封裝好了的方法,另一優(yōu)勢就是hub連接可以在客戶端調(diào)用服務端的方法,或者服務端可以調(diào)用客戶端實現(xiàn)的方法。以下是SignalR-Client-CPP開源庫中兩種連接SignalR服務器的簡單實例(http)。

PersistentConnection :

void send_message(signalr::connection &connection, const utility::string_t& message)
{
    connection.send(message)
        // fire and forget but we need to observe exceptions
        .then([](pplx::task<void> send_task)
    {
       try
        {
            send_task.get();
        }
        catch (const std::exception &e)
        {

            ucout << U("Error while sending data: ") << e.what();
        }
    });
}
int main()
{
signalr::connection connection{ U("[http://localhost:34281/echo](http://localhost:34281/echo)") };
    connection.set_message_received([](const utility::string_t& m)
    {
        ucout << U("Message received:") << m
              << std::endl << U("Enter message: ");
    });
    connection.start()
        // fine to capture by reference - we are blocking
        // so it is guaranteed to be valid
        .then([&connection]()
        {
            for (;;)
            {
                utility::string_t message;
                std::getline(ucin, message);
                if (message == U(":q"))
                {
                    break;
                }

                send_message(connection, message);

            }
            return connection.stop();
        })
        .then([](pplx::task<void> stop_task)
        {
            try
            {
                stop_task.get();
                ucout << U("connection stopped successfully") << std::endl;
            }
            catch (const std::exception &e)
            {
                ucout << U("exception when starting or closing connection: ")
                      << e.what() << std::endl;
            }
        }).get();
    return 0;
}

??持久連接的API(表現(xiàn)在PersistentConnection 類上)給了開發(fā)人員低價訪問SignalR所暴露的通信協(xié)議的條件。我們使用set_message_received函數(shù)來設置一個方法,每當我們從服務器接收到一條消息時,它就會被調(diào)用對相關數(shù)據(jù)進行處理。然后通過使用connect.start()函數(shù)啟動連接。如果連接成功啟動,內(nèi)部就會運行一個循環(huán),從控制臺讀取消息。
HubConnection:

void send_message(signalr::hub_proxy proxy, const utility::string_t& name,
                  const utility::string_t& message)
{
    web::json::value args{};
    args[0] = web::json::value::string(name);
    args[1] = web::json::value(message);
    proxy.invoke<void>(U("send"), args)
        // fire and forget but we need to observe exceptions
        .then([](pplx::task<void> invoke_task)
    {
        try
        {
            invoke_task.get();
        }
        catch (const std::exception &e)
        {
            ucout << U("Error while sending data: ") << e.what();
        }
    });
}
void chat(const utility::string_t& name)
{
signalr::hub_connection connection{U("[http://localhost:34281](http://localhost:34281/)")};
    auto proxy = connection.create_hub_proxy(U("ChatHub"));
    proxy.on(U("broadcastMessage"), [](const web::json::value& m)
    {
ucout << std::endl << [m.at](http://m.at/)(0).as_string() << U(" wrote:")
<< [m.at](http://m.at/)(1).as_string() << std::endl << U("Enter your message: ");
    });
    connection.start()
        .then([proxy, name]()
        {
            ucout << U("Enter your message:");
            for (;;)
            {
                utility::string_t message;
                std::getline(ucin, message);
                if (message == U(":q"))
                {
                   break;
                }
                send_message(proxy, name, message);
            }
        })
        // fine to capture by reference - we are blocking
        // so it is guaranteed to be valid
        .then([&connection]()
        {
            return connection.stop();
        })
        .then([](pplx::task<void> stop_task)
        {
            try
            {
                stop_task.get();
                ucout << U("connection stopped successfully") << std::endl;
            }
            catch (const std::exception &e)
            {
                ucout << U("exception when starting or stopping connection: ")
                      << e.what() << std::endl;
            }
        }).get();
}

??其中hub_proxy的on方法可以實現(xiàn)服務端調(diào)用客戶端定義的函數(shù)方法,通過客戶端實現(xiàn)服務端定義的方法達到對數(shù)據(jù)處理的主動權。hub_proxy的invoke函數(shù)實現(xiàn)客戶端調(diào)用服務端的方法,函數(shù)定義在SignalR的服務端,客戶端通過invoke函數(shù)指定方法名稱及參數(shù)實現(xiàn)客戶端對服務端特定方法的調(diào)用。當服務端的代碼訪問一個客戶端的方法時,一個數(shù)據(jù)包被自動傳輸,數(shù)據(jù)包中包含了函數(shù)方法參數(shù)的名稱(如果是一個對象,那么這個對象會被序列化成JSON)??蛻舳巳缓蟾鶕?jù)客戶端的代碼匹配方法的名稱。如果找到相應的匹配方法,那么久調(diào)用相應的函數(shù)執(zhí)行反序列化的參數(shù)。

四、SignalRCPPClient使用過程中的問題及解決方法

??再使用過程中,連接方式采用HubConnection,服務端采用了自簽名單向認證的https方式進行通信,在開源的SignalRCpplient中使用https訪問服務器時無法正常通信。主要問題如下:
1、使用SignalR 對接服務器時一直報出“WinHttpSendRequest: 12175”問題。
2、cpprest 內(nèi)部爆出“set_fail_handler: 8: TLS handshake failed”錯誤。

??第一個問題:通過排查發(fā)現(xiàn)出現(xiàn)12175報錯問題主要是因為安全連接過程中出錯,網(wǎng)上搜出的方法基本都是和winhttp的使用相關,通過嘗試Stack Overflow網(wǎng)站以及GitHub開源庫issues各種可能的原因都無法解決這一問題。最后通過抓包發(fā)現(xiàn)在建立通信的過程中客戶端使用了TLS1.0嘗試建立安全連接,而對接的平臺不支持TLS1.0。TLS1.0于1999年發(fā)行,至今將近有20年。業(yè)內(nèi)都知道該版本易受各種攻擊(如BEAST和POODLE)已有多年,除此之外,支持較弱加密,對當今網(wǎng)絡連接的安全已失去應有的保護效力。

??分析SignalRCPPClient源碼后發(fā)現(xiàn)其主要是通過使用cpprestSDK(微軟的另一個開源庫)來完成http的交互。此處使用的cpprestSDK版本為2.9.0,此版本中未對TLS各個版本做好兼容,默認使用的為TLS1.0。通過升級依賴庫后抓包發(fā)現(xiàn)實現(xiàn)了TLS1.2的握手過程,但cpprest 內(nèi)部爆出“set_fail_handler: 8: TLS handshake failed”錯誤。

??第二個問題:自簽名證書的使用需要繞過證書的認證,在SignalRClient庫中通過設置websocket_client_config以及http_client_config的set_validate_certificates值為false繞過證書的認證以后便可以正常通信。

五、總結

??本文檔只涉及C++版SignalRClient的使用方法,未涉及SignalR服務端的開發(fā)與搭建。在使用過程中需要對各種異常情況進行捕獲處理。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 原文地址:http://www.ibm.com/developerworks/cn/java/j-lo-WebSo...
    敢夢敢當閱讀 9,027評論 0 50
  • WebSocket 機制 WebSocket 是 HTML5 一種新的協(xié)議。它實現(xiàn)了瀏覽器與服務器全雙工通信,能更...
    勇敢的_心_閱讀 2,373評論 0 4
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,621評論 1 32
  • 很多人人常常認為自己黑,一個勁的追求美白。但是殊不知,每個人都會有自己的美白上限。不是盲目的追求美白就一定能夠成功...
    梨花醬閱讀 551評論 1 1
  • 不管是誰叫我做事,我都說等一會等一會。是的,等一會我就忘記了,忘記了就可以拖一會了… 拖延癌,你好似很...
    胡敏宜閱讀 234評論 0 0

友情鏈接更多精彩內(nèi)容