服务端使用c++实现websocket协议解析及通信
2017-12-30 20:39
1641 查看
WebSocket 设计出来的目的就是要使客户端浏览器具备像 C/S 架构下桌面系统的实时通讯能力。
浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。因为 WebSocket 连接本质上就是一个 TCP 连接,所以在数据传输的稳定性和数据传输量的大小方面,和轮询以及
Comet 技术比较,具有很大的性能优势。下面是一个简单 Web 应用分别用轮询方式和 WebSocket 方式来实现,下面是测试结果图:
通过这张图可以清楚的看出,在流量和负载增大的情况下,WebSocket 方案相比传统的 Ajax 轮询方案有极大的性能优势。
好了不过多介绍WebSocket了,更多介绍大家可以点击参考资料引用的链接查看,还是回到解析协议及通信上来。解析协议这种事,就得耐着性子,一个字节一个字节解析,按步骤一点一点写程序。不过读懂了文档,知道了每个字节的属性意义后,解析起来还是挺简单的。按照协议说明,一旦完成数据解码,那么编码就稍微容易一些,差不多就是解码的逆向操作了。服务端使用c++完成WebSocket通信,主要需要完成以下三方面编程:
1.服务端与h5客户端发起的WebSocket连接握手:
2.完成握手后连接就建立了。然后就是接收h5客户端通过WebSocket发过来的数据帧并解码:
3.解码完数据帧,服务端做出相应处理后将结果按照WebSocket协议编码,然后发给h5客户端:
4.握手只需一次,随后反复执行第2步及第3步,就完成了服务端与h5客户端通信。这个只是c++版本的,可以很容易改成java版本的。下面是上述方法用到的一些枚举:
浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。因为 WebSocket 连接本质上就是一个 TCP 连接,所以在数据传输的稳定性和数据传输量的大小方面,和轮询以及
Comet 技术比较,具有很大的性能优势。下面是一个简单 Web 应用分别用轮询方式和 WebSocket 方式来实现,下面是测试结果图:
通过这张图可以清楚的看出,在流量和负载增大的情况下,WebSocket 方案相比传统的 Ajax 轮询方案有极大的性能优势。
好了不过多介绍WebSocket了,更多介绍大家可以点击参考资料引用的链接查看,还是回到解析协议及通信上来。解析协议这种事,就得耐着性子,一个字节一个字节解析,按步骤一点一点写程序。不过读懂了文档,知道了每个字节的属性意义后,解析起来还是挺简单的。按照协议说明,一旦完成数据解码,那么编码就稍微容易一些,差不多就是解码的逆向操作了。服务端使用c++完成WebSocket通信,主要需要完成以下三方面编程:
1.服务端与h5客户端发起的WebSocket连接握手:
int wsHandshake(std::string &request, std::string &response) { // 解析http请求头信息 int ret = WS_STATUS_UNCONNECT; std::istringstream stream(request.c_str()); std::string reqType; std::getline(stream, reqType); if (reqType.substr(0, 4) != "GET ") { return ret; } std::string header; std::string::size_type pos = 0; std::string websocketKey; while (std::getline(stream, header) && header != "\r") { header.erase(header.end() - 1); pos = header.find(": ", 0); if (pos != std::string::npos) { std::string key = header.substr(0, pos); std::string value = header.substr(pos + 2); if (key == "Sec-WebSocket-Key") { ret = WS_STATUS_CONNECT; websocketKey = value; break; } } } if (ret != WS_STATUS_CONNECT) { return ret; } // 填充http响应头信息 response = "HTTP/1.1 101 Switching Protocols\r\n"; response += "Upgrade: websocket\r\n"; response += "Connection: upgrade\r\n"; response += "Sec-WebSocket-Accept: "; const std::string magicKey("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); std::string serverKey = websocketKey + magicKey; char shaHash[32]; memset(shaHash, 0, sizeof(shaHash)); sha1::calc(serverKey.c_str(), serverKey.size(), (unsigned char *)shaHash); serverKey = base64::base64_encode(std::string(shaHash)) + "\r\n\r\n"; string strtmp(serverKey.c_str()); response += strtmp; return ret; }
2.完成握手后连接就建立了。然后就是接收h5客户端通过WebSocket发过来的数据帧并解码:
int wsDecodeFrame(std::string inFrame, std::string &outMessage) { int ret = WS_OPENING_FRAME; const char *frameData = inFrame.c_str(); const int frameLength = inFrame.size(); if (frameLength < 2) { ret = WS_ERROR_FRAME; } // 检查扩展位并忽略 if ((frameData[0] & 0x70) != 0x0) { ret = WS_ERROR_FRAME; } // fin位: 为1表示已接收完整报文, 为0表示继续监听后续报文 ret = (frameData[0] & 0x80); if ((frameData[0] & 0x80) != 0x80) { ret = WS_ERROR_FRAME; } // mask位, 为1表示数据被加密 if ((frameData[1] & 0x80) != 0x80) { ret = WS_ERROR_FRAME; } // 操作码 uint16_t payloadLength = 0; uint8_t payloadFieldExtraBytes = 0; uint8_t opcode = static_cast<uint8_t>(frameData[0] & 0x0f); if (opcode == WS_TEXT_FRAME) { // 处理utf-8编码的文本帧 payloadLength = static_cast<uint16_t>(frameData[1] & 0x7f); if (payloadLength == 0x7e) { uint16_t payloadLength16b = 0; payloadFieldExtraBytes = 2; memcpy(&payloadLength16b, &frameData[2], payloadFieldExtraBytes); payloadLength = ntohs(payloadLength16b); } else if (payloadLength == 0x7f) { // 数据过长,暂不支持 ret = WS_ERROR_FRAME; } } else if (opcode == WS_BINARY_FRAME || opcode == WS_PING_FRAME || opcode == WS_PONG_FRAME) { // 二进制/ping/pong帧暂不处理 } else if (opcode == WS_CLOSING_FRAME) { ret = WS_CLOSING_FRAME; } else { ret = WS_ERROR_FRAME; } // 数据解码 if ((ret != WS_ERROR_FRAME) && (payloadLength > 0)) { // header: 2字节, masking key: 4字节 const char *maskingKey = &frameData[2 + payloadFieldExtraBytes]; char *payloadData = new char[payloadLength + 1]; memset(payloadData, 0, payloadLength + 1); memcpy(payloadData, &frameData[2 + payloadFieldExtraBytes + 4], payloadLength); for (int i = 0; i < payloadLength; i++) { payloadData[i] = payloadData[i] ^ maskingKey[i % 4]; } outMessage = payloadData; delete[] payloadData; } return ret; }
3.解码完数据帧,服务端做出相应处理后将结果按照WebSocket协议编码,然后发给h5客户端:
int wsEncodeFrame(std::string inMessage, std::string &outFrame, enum WS_FrameType frameType) { int ret = WS_EMPTY_FRAME; const uint32_t messageLength = inMessage.size(); if (messageLength > 32767) { // 暂不支持这么长的数据 return WS_ERROR_FRAME; } uint8_t payloadFieldExtraBytes = (messageLength <= 0x7d) ? 0 : 2; // header: 2字节, mask位设置为0(不加密), 则后面的masking key无须填写, 省略4字节 uint8_t frameHeaderSize = 2 + payloadFieldExtraBytes; uint8_t *frameHeader = new uint8_t[frameHeaderSize]; memset(frameHeader, 0, frameHeaderSize); // fin位为1, 扩展位为0, 操作位为frameType frameHeader[0] = static_cast<uint8_t>(0x80 | frameType); // 填充数据长度 if (messageLength <= 0x7d) { frameHeader[1] = static_cast<uint8_t>(messageLength); } else { frameHeader[1] = 0x7e; uint16_t len = htons(messageLength); memcpy(&frameHeader[2], &len, payloadFieldExtraBytes); } // 填充数据 uint32_t frameSize = frameHeaderSize + messageLength; char *frame = new char[frameSize + 1]; memcpy(frame, frameHeader, frameHeaderSize); memcpy(frame + frameHeaderSize, inMessage.c_str(), messageLength); frame[frameSize] = '\0'; outFrame = frame; delete[] frame; delete[] frameHeader; return ret; }
4.握手只需一次,随后反复执行第2步及第3步,就完成了服务端与h5客户端通信。这个只是c++版本的,可以很容易改成java版本的。下面是上述方法用到的一些枚举:
enum WS_Status { WS_STATUS_CONNECT = 0, WS_STATUS_UNCONNECT = 1, };
enum WS_FrameType { WS_EMPTY_FRAME = 0xF0, WS_ERROR_FRAME = 0xF1, WS_TEXT_FRAME = 0x01, WS_BINARY_FRAME = 0x02, WS_PING_FRAME = 0x09, WS_PONG_FRAME = 0x0A, WS_OPENING_FRAME = 0xF3, WS_CLOSING_FRAME = 0x08 };
相关文章推荐
- 服务端使用c++实现websocket协议解析及通信
- 铁路列车之间的通信协议(MVB协议)分析软件C/C++实现(四) 数据解析
- 在linux上使用c++实现http/2协议进行通信
- WebSocket使用(C++环境)(三) --- 自己解析websocket协议及websocket聊天demo
- 在Android上使用LocalSocket实现上层Java和底层C++的通信
- 使用SuperSocket实现TLV自定义协议网络通信的Demo
- 邮件解析框架(JavaMail的C++实现)使用示例
- Flex使用BlazerDS实现客户端与服务端通信
- 使用管道实现linux C++ 线程通信
- 使用C++实现一套简单的状态机模型——原理解析
- SIP协议解析与实现(c和c++使用osip) 1
- 进程通信之一 使用WM_COPYDATA C++及C#实现
- 基础总结篇之四:Service完全解析——使用AIDL实现进程间的通信
- 一步一步学WebSocket(二) 使用SuperWebSocket实现自己的服务端
- Flex使用BlazerDS实现客户端与服务端通信
- 使用SuperSocket实现TLV自定义协议网络通信的Demo
- 基础总结篇之四:Service完全解析--使用AIDL实现进程间的通信之复杂类型传递
- SIP协议解析与实现(c和c++ 使用osip) 12
- 进程通信之一 使用WM_COPYDATA C++及C#实现 z
- 使用FDO封装XML&ADO实现与服务端数据通信