当前位置: 首页 > 工具软件 > PHP-CPP > 使用案例 >

开源SignalR-Client-CPP使用总结

祁雪峰
2023-12-01

一、使用背景

  在使用C++对接项目平台过程中需要使用SignalRClient接收平台的事件信息。C++版本的SignalRClient使用不是很多,国内网站也没什么资料可供参考。经过调研,项目中决定使用SignalR-Client-CPP开源代码(https://github.com/SignalR/SignalR-Client-Cpp

二、SignalR简介

  ASP .NET SignalR 是一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信。什么是实时通信的Web呢?就是让客户端(Web页面)和服务器端可以互相通知消息及调用方法,当然这是实时操作的。通过SignalR技术服务器将内容自动推送到已经连接的客户端,而不是服务器等待客户端发起一个新的数据请求。

  Websocket是HTML5提供的新的API,可以在Web网页与服务器端间建立Socket连接,当WebSockets可用时(即浏览器支持Html5)SignalR使用WebSockets,当不支持时SignalR将使用其它技术来保证达到相同效果。

  SignalR可以使用最新的WebSocket 传输,同时也能够让你回退到原有的传输方式。你可以直接使用SignalR 使用 WebSocket,因为SignalR 已经替你封装好许多你需要实现的方法。最重要的是你使用SignalR不用担心为老的客户端实现WebSocket而采用两套不同的逻辑编码方式。使用SignalR 实现WebSocket你不用担心 WebSocket的更新而去修改代码,SignalR会在传输方式上使用WebSocket最新的传输方式,同时提供了一连串的接口能够让你来支持不同版本的客户端。

  SignalR-Client-CPP开源代码,仅支持WebSocket环境以及要求SignalR服务端版本在2.0以上才可正常使用。

三、SignalRCPPClient使用

  SignalR客户端和服务端通信可以有两种方法HubConnection与PersistentConnection。其中,PersistentConnection方式更加偏向底层,编程模式和websocket类似,使用固定的发送和接收方法,通过此方法编码过程可控性大,但是编码繁琐,而且基本都是在重复造轮子。而HubConnection方法相对来说是一个封装好了的方法,另一优势就是hub连接可以在客户端调用服务端的方法,或者服务端可以调用客户端实现的方法。以下是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(表现在PersistentConnection 类上)给了开发人员低价访问SignalR所暴露的通信协议的条件。我们使用set_message_received函数来设置一个方法,每当我们从服务器接收到一条消息时,它就会被调用对相关数据进行处理。然后通过使用connect.start()函数启动连接。如果连接成功启动,内部就会运行一个循环,从控制台读取消息。
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方法可以实现服务端调用客户端定义的函数方法,通过客户端实现服务端定义的方法达到对数据处理的主动权。hub_proxy的invoke函数实现客户端调用服务端的方法,函数定义在SignalR的服务端,客户端通过invoke函数指定方法名称及参数实现客户端对服务端特定方法的调用。当服务端的代码访问一个客户端的方法时,一个数据包被自动传输,数据包中包含了函数方法参数的名称(如果是一个对象,那么这个对象会被序列化成JSON)。客户端然后根据客户端的代码匹配方法的名称。如果找到相应的匹配方法,那么久调用相应的函数执行反序列化的参数。

四、SignalRCPPClient使用过程中的问题及解决方法

  再使用过程中,连接方式采用HubConnection,服务端采用了自签名单向认证的https方式进行通信,在开源的SignalRCpplient中使用https访问服务器时无法正常通信。主要问题如下:
1、使用SignalR 对接服务器时一直报出“WinHttpSendRequest: 12175”问题。
2、cpprest 内部爆出“set_fail_handler: 8: TLS handshake failed”错误。

  第一个问题:通过排查发现出现12175报错问题主要是因为安全连接过程中出错,网上搜出的方法基本都是和winhttp的使用相关,通过尝试Stack Overflow网站以及GitHub开源库issues各种可能的原因都无法解决这一问题。最后通过抓包发现在建立通信的过程中客户端使用了TLS1.0尝试建立安全连接,而对接的平台不支持TLS1.0。TLS1.0于1999年发行,至今将近有20年。业内都知道该版本易受各种攻击(如BEAST和POODLE)已有多年,除此之外,支持较弱加密,对当今网络连接的安全已失去应有的保护效力。

  分析SignalRCPPClient源码后发现其主要是通过使用cpprestSDK(微软的另一个开源库)来完成http的交互。此处使用的cpprestSDK版本为2.9.0,此版本中未对TLS各个版本做好兼容,默认使用的为TLS1.0。通过升级依赖库后抓包发现实现了TLS1.2的握手过程,但cpprest 内部爆出“set_fail_handler: 8: TLS handshake failed”错误。

  第二个问题:自签名证书的使用需要绕过证书的认证,在SignalRClient库中通过设置websocket_client_config以及http_client_config的set_validate_certificates值为false绕过证书的认证以后便可以正常通信。

五、总结

  本文档只涉及C++版SignalRClient的使用方法,未涉及SignalR服务端的开发与搭建。在使用过程中需要对各种异常情况进行捕获处理。



作者:菜菜的家伙
链接:https://www.jianshu.com/p/d669c2f6b4e2
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 类似资料: