C++通讯websocket
https://www.cnblogs.com/lizhenghn/p/5155933.html 这篇文章详细解析了websocket协议的规范与其实现的原理如果需要可以简单看一下,我自己对这个通讯方式的理解大部分来源于这个文章。接下来我会对这种通讯方式结合自己的理解写一个简单的理解方式。
首先websocket不是真正意义上的socket协议,原生使用C++编写的socket无法完成与其的通讯。本质上来讲它还是http协议,只不过有一定的封装,原生来写这个东西需要C++进行一个httpserver监听,非常麻烦需要集成boost库,难度相当大,但是使用第三方库就会好很多,毕竟有了一层基础的封装。websocket机制算比较简单无非就是一个http头协议的请求。然后由这个头连接对象存在一个缓存,使用这个缓存地址可以完成向服务器发送http通讯,同时服务器记录浏览器的地址也向其发送http通讯,原生开发这种机制非常耗费时间同时每一次请求都会对资源进行消耗,所有对其进行一个协议的封装并取消每一次通讯的验证仅仅进行一次验证便可以完成类似tcpsocket的通讯方式,简单来说就是对http进行封装实现了网页对服务器或网页对网页的双向双工通讯。
通讯的建立就是一个http头协议的问题,h5标准中进行了一个封装所有前端写代码不会感觉到什么东西,但是面向服务器需要做的事情就来了,在js中使用WebSocket()函数就可以发起请求,如果我们排除常规http的情况那么这个请求头之中必须要包含四个东西分别为Sec-WebSocket-Extensions、Sec-WebSocket-Key、Sec-WebSocket-Protocol、Sec-WebSocket-Version。这四个东西会传给服务端然后等待服务端返回握手消息一旦服务端成功返回这个websocket边完成了连接。
websocket的http请求头,这里除开上面提到的四个东西其他与常规http请求头一致
Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cache-Control: no-cache Connection: Upgrade Host: 192.168.0.16:9547 Origin: http://xunshi.com Pragma: no-cache Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits Sec-WebSocket-Key: 6h6zarq2efMwn7t3mnT2+g== Sec-WebSocket-Protocol: xunshi-websocket Sec-WebSocket-Version: 13 Upgrade: websocket User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36
服务端返回头
Connection: Upgrade Sec-WebSocket-Accept: GOBa4WS7SvVVWWcGk87PshRNjoI= Sec-WebSocket-Protocol: xunshi-websocket Upgrade: WebSocket
这一次请求与一次返回便完成了websocket的握手这样连接就建立了。
首先是Sec-WebSocket-Extensions,这是websocket的拓展协议,应用于需要拓展的需求上面,我写的js并没有进行协议拓展这是自动生成的默认协议不需要管那么多。
接着是Sec-WebSocket-Key,这个是一个 Base64 encode 的值,这个是浏览器随机生成的,用户验证身份。
然后是Sec_WebSocket-Protocol,这是自定义协议,如果在同一个页面下有多个不同功能的socket连接那么它就可以区分具体使用的是哪一个协议。C++里面可以对不同自定义协议回调不同的函数,这里仅仅用于区分。
最后, Sec-WebSocket-Version 是告诉服务器所使用的协议版本,在最初的时候,Websocket协议有很多个不同版本,这里进行区分版本是什么,但是不同版本也仅仅是说早期版本,目前几乎稳定,基本上所有浏览器使用都是13这个版本。
服务器返回头中最重要的是身份验证的握手协议了,这里采用的是将浏览器发出的key进行sha1编码然后再进行base64编码所得的的值便是Sec-WebSocket-Accept。然后就是响应的自定义协议了,告诉浏览器我服务器使用的是头中包含的哪一个协议。
然后就是C++部分怎么进行对应的通讯了,这里有好几个坑首先是原生写开始使用库,尝试原生写,但是协议响应不了,不知道为什么而且http发送过来的头仅仅完成了握手后面所有的东西都不能做,我是采用MFC加原生socket模拟httpserver反正失败了,最好使用库,我将库功能进行了一层封装现在可以使用了没有发现什么问题,当然目前仅仅完成了功能性测试,稳定性测试目前没有项目环境不好测。首先是库的选择,出名的库有两个第一是websocket++的C++库,这个库是基于boost库开发的,库功能强悍但是体量较大,服务端程序用这种大体量的库还是很虚的所以选择了一个相对轻量级的库较libwebsocket,这个是一个C语言库依赖openssl,体量小了很多但是功能也比较全面我们用得上的功能其实更少只有几个。
第一步就是按照openssl库了,可以自己编译可以直接安装二进制库,一定要注意是的openssl使用版本必须是1.01,之前我cmake编译libwebsocket卡了一个多小时就是因为openssl版本的问题只有1.01版本可以使用,如果要自己编译一定要记住版本问题。还有就是必须使用32位版本之后会提为什么。
第二部是编译libwebsocket,这个东西有两个库千万不要去下载什么博客上的提供的连接,一定要去官网下载,那些连接的库大部分是老编译器支持的版本有很多错误,我卡在这里1个多小时就是下载的源码有问题。然后是编译器使用CMake进行编译但是编译器必须选择MSVC2015 X86一定要是X86不然各种报错这里我也卡了一个多小时,CMake会自动帮你链接Openssl库如果没有记住去手动指定,不要编译要GG。make之后进入VS直接批量编译ALLbuild的release和debug版本就可以了,建议两个版本都编译出来方便调试,之后再你就得到了dll文件lib文件和一大堆头文件。然后就可以新建项目链接库进入就OK了,记住在新项目里面必须包好openssl库与头,生成运行文件的动态库也要放进去(libwebsocket和openssl),搞定这些就可以开始写代码了。
第三部封装,我采用了C++的类封装方式,但是库是C语言了所以只能使用静态函数进行链接,这里的处理方式就是自包含比较简单,库的使用百度出来的东西都很闹心我找到的一个NB的文章是这个https://blog.csdn.net/qifengzou/article/details/50281545 写的也比较蛋疼,参照他这个同时结合库里面的头文件半猜半写基本上就可以了。最伤的是我找不到文档,所以高级的功能根本不知道怎么用所幸目前这些功能基本上够了。这个类对应上面写的html通讯效果还是可以,如果需要这个类可以编译为库供其他项目使用,集成第三方库只有openssl,之后往UE4里面集成也可以,QT更不是问题,现在主要拿来写MFC,结合完成端口可以完成端口可以轻松完成服务端程序。
头文件代码
#pragma once #define protoocol "xunshi-websocket" //定义协议 class Websocket_Server { public: Websocket_Server(int port); ~Websocket_Server(); void Start(); //开始监听服务 void Send(char *buffer); //对所有客户端发送消息 void Send(struct lws *client, char *buffer); //对一个客户端发送消息 void Receive(struct lws *client, char *buffer, int len); //接受消息的调用 void ServerWorkerThread(); //监听线程 private: static int http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len); //常规http请求的回调,这个必须写虽然没什么卵用 static int websocket(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len); //websocket的http请求回调,这里就是看协议是否存在了,对应有就执行 static int websocket_write(struct lws *wsi_in, char *str, int str_size_in); //向websocket中写入消息 struct lws_context_creation_info info; //上下文对象的信息 struct lws_context *context; //上下文对象指针 struct lws_protocols *lwsprotocols; //定义协议标准 static std::vector AllClient; //所有当前连接的客户端 static Websocket_Server *self; //this的静态调用,类似单例开发主要是为了在静态函数中使用类成员 const char *PROTOOCOL = protoocol; //定义协议 };
源文件代码如下
std::vector Websocket_Server::AllClient; Websocket_Server *Websocket_Server::self = NULL; UINT WorkThread(LPVOID lpPram) { if (((Websocket_Server *)lpPram)) { ((Websocket_Server *)lpPram)->ServerWorkerThread(); } return 0; } void Websocket_Server::ServerWorkerThread() { if (context != NULL && lwsprotocols != NULL) { while (true) { lws_callback_on_writable_all_protocol(context, lwsprotocols); //激活LWS_CALLBACK_SERVER_WRITEABLE,不这样声明发送消息就不能执行。 lws_service(context, 50); //启动ws服务 Sleep(1); //休眠减少核心占用 } } Sleep(10); //独立线程中进行循环启动ws服务为了可以回调 return; } Websocket_Server::Websocket_Server(int port) { AllClient.clear(); self = this; static struct lws_protocols protocols[] = {{ "http-only", http, 0, }, //http头中协议为http-only消息过来了便调用http这个函数 { PROTOOCOL, websocket, 10, }, //http头中协议为xunshi-websocket消息过来了便调用websocket这个函数 {NULL, NULL, 0, 0}}; //注册协议 lwsprotocols = &protocols[1]; int opts = 0; //本软件的额外功能选项 volatile int force_exit = 0; unsigned int ms, oldms = 0; int n = 0; memset(&info, 0, sizeof(info)); //申请info内存 info.port = port; info.iface = NULL; info.protocols = protocols; info.extensions = NULL; info.ssl_cert_filepath = NULL; info.ssl_private_key_filepath = NULL; info.ssl_ca_filepath = NULL; info.gid = -1; info.uid = -1; info.options = opts; info.ka_time = 0; info.ka_probes = 0; info.ka_interval = 0; //设置info,填充info信息体,为库提供参数 } void Websocket_Server::Start() { context = lws_create_context(&info); //创建上下文对面,管理ws if (context == NULL) { //错误log } else { CWinThread *Thread = AfxBeginThread(WorkThread, this); //开启工作线程,在这里进启动。 } return; } Websocket_Server::~Websocket_Server() { lws_context_destroy(context); } int Websocket_Server::http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { return 0; //http请求来了的话什么都不要管,我们只关心websocket协议请求即dumb-increment-protocol //其实http也可以通行,但是是单向的,只能结束并且在一定时间内发送会被当做是回调,服务端不能主动通讯 } int Websocket_Server::websocket_write(struct lws *wsi_in, char *str, int str_size_in) { if (str == NULL || wsi_in == NULL) { //错误log return -1; } int n; int len; unsigned char *out = NULL; if (str_size_in < 1) { len = strlen(str); } else { len = str_size_in; } out = (unsigned char *)malloc(sizeof(unsigned char) * (LWS_SEND_BUFFER_PRE_PADDING + len + LWS_SEND_BUFFER_POST_PADDING)); //设置缓冲区内存 memcpy(out + LWS_SEND_BUFFER_PRE_PADDING, str, len); //要发送的数据写入缓冲区 n = lws_write(wsi_in, out + LWS_SEND_BUFFER_PRE_PADDING, len, LWS_WRITE_TEXT); //调用库里面的lws的发送函数 free(out); //释放缓冲区 return n; } int Websocket_Server::websocket(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { switch (reason) { case LWS_CALLBACK_ESTABLISHED: //对应客户端连接到服务器 { AllClient.push_back(wsi); } break; case LWS_CALLBACK_RECEIVE: //对应客户端发送过来消息 { Websocket_Server::self->Receive(wsi, (char *)in, len); } break; case LWS_CALLBACK_CLOSED: //对应客户端关闭连接 { for (int i = 0; i < AllClient.size(); i++) { if (AllClient[i] == wsi) { AllClient.erase(AllClient.begin() + i); } } } break; //还有很多种情况参照libwebsockets.h但是目前用不上 default: break; } return 0; } void Websocket_Server::Receive(struct lws *client, char *buffer, int len) { //加入对消息的处理 } void Websocket_Server::Send(struct lws *client, char *buffer) { if (client != NULL && buffer != NULL) { websocket_write(client, buffer, sizeof(buffer)); } return; } void Websocket_Server::Send(char *buffer) { for (int i = 0; i < AllClient.size(); i++) { if (AllClient[i] != NULL && buffer != NULL) { websocket_write(AllClient[i], buffer, strlen(buffer)); } } return; }