C++实现websocket协议通讯
  在获取服务器的数据时,我们传统的做法是通过前端进⾏请求服务器返回数据。这样如果我们要获取的数据不是连续的,或者服务器想想前端推送数据只能通过ajax等轮询请求了。html5以后我们可以通过websocket和服务器进⾏通信,前端和服务连接后就可以进⾏双⼯连接了。服务器有数据就能实时的给前端推送,⽽不需要我定时的去请求。
  websocket简单来说就是先前端向服务器发送http的请求,服务端通过前端请求的http的key值进过sha1和base64加密返回给前端,这⼀过程称为“握⼿”,如果验证成功就能进⾏websocket通讯了。握⼿成功后前端和后端通讯都要符合webscoket的协议才能通讯。关于websocket的协议⼤家可以⽹上查,这⾥不作讨论。
  最近在要实现⼀个后端将设配读到数据主动发前端的功能,⽹上查了⼀些⼤佬写好程序进⾏参考更改。这⾥谢谢他们,希望没有侵权。下⾯是主要代码。
1 #ifndef __WebSocketProtocol_H__
2#define __WebSocketProtocol_H__
3
4 #include <string>
5
6using std::string;
7
8class CWebSocketProtocol
9 {
10public:
11enum WS_Status
12    {
13        WS_STATUS_CONNECT = 0,
14        WS_STATUS_UNCONNECT = 1,
15    };
16
17enum WS_FrameType
18    {
19        WS_EMPTY_FRAME = 0xF0,
20        WS_ERROR_FRAME = 0xF1,
21        WS_TEXT_FRAME = 0x01,
22        WS_BINARY_FRAME = 0x02,
23        WS_PING_FRAME = 0x09,
24        WS_PONG_FRAME = 0x0A,
25        WS_OPENING_FRAME = 0xF3,
26        WS_CLOSING_FRAME = 0x08
27    };
28
29static CWebSocketProtocol * getInstance();
30
31int getResponseHttp(string &request, string &response);
32int wsDecodeFrame(string inFrame, string &outMessage);    //解码帧
33int wsEncodeFrame(string inMessage, string &outFrame, enum WS_FrameType frameType);    //编码帧打包
34
35private:
36    CWebSocketProtocol();
37    ~CWebSocketProtocol();
38
39class CGrabo
40    {
41public:
42        ~CGrabo()
43        {
44if (m_inst != 0)
45            {
46delete m_inst;
47                m_inst = 0;
48            }
49        }
50    };
51
52static CGrabo m_grabo;
53static    CWebSocketProtocol * m_inst;
54 };
55
56#endif
1 #include "WebSocketProtocol.h"
2 #include <iostream>
3 #include <sstream>
4 #include <string.h>
5 #include <arpa/inet.h>
6 #include "sha1.h"
7 #include "base64.h"
8
9const char * MAGIC_KEY = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
10
11 CWebSocketProtocol::CGrabo CWebSocketProtocol::m_grabo;
12 CWebSocketProtocol * CWebSocketProtocol::m_inst = 0;
13
14 CWebSocketProtocol::CWebSocketProtocol()
15 {
16 }
17
18
19 CWebSocketProtocol::~CWebSocketProtocol()
20 {
21 }
22
23
24
25 CWebSocketProtocol * CWebSocketProtocol::getInstance()
26 {
27if (m_inst == 0)
28    {
29        m_inst = new CWebSocketProtocol;
30    }
31
32return m_inst;
33 }
34
35int CWebSocketProtocol::getResponseHttp(string &request, string &response)
36 {
37// 解析http请求头信息
38int ret = WS_STATUS_UNCONNECT;
39    std::istringstream stream(request.c_str());
40    std::string reqType;
41    std::getline(stream, reqType);
42if (reqType.substr(0, 4) != "GET ")
43    {
44return ret;
45    }
46
47    std::string header;
48    std::string::size_type pos = 0;
49    std::string websocketKey;
50while (std::getline(stream, header) && header != "\r")
51    {
52        d() - 1);
53        pos = header.find(": ", 0);
54if (pos != std::string::npos)
55        {
56            std::string key = header.substr(0, pos);
57            std::string value = header.substr(pos + 2);
58if (key == "Sec-WebSocket-Key")
59            {
60                ret = WS_STATUS_CONNECT;
61                websocketKey = value;
62break;
63            }
64        }
65    }
66
67if (ret != WS_STATUS_CONNECT)
68    {
69return ret;
70    }
71
72// 填充http响应头信息
73    response = "HTTP/1.1 101 Switching Protocols\r\n";
74    response += "Connection: upgrade\r\n";
75    response += "Sec-WebSocket-Accept: ";
76
77    std::string serverKey = websocketKey + MAGIC_KEY;
78
79    SHA1 sha;
80    unsigned int message_digest[5];
81    sha.Reset();
82    sha << serverKey.c_str();
83
84    sha.Result(message_digest);
85for (int i = 0; i < 5; i++) {
86        message_digest[i] = htonl(message_digest[i]);
87    }
88    serverKey = base64_encode(reinterpret_cast<const unsigned char*>(message_digest), 20);
89    response += serverKey;
90    response += "\r\n";
91    response += "Upgrade: websocket\r\n\r\n";
92
93return ret;
94 }
95
96int CWebSocketProtocol::wsDecodeFrame(string inFrame, string &outMessage)
97 {
98int ret = WS_OPENING_FRAME;
99const char *frameData = inFrame.c_str();
100const int frameLength = inFrame.size();
101if (frameLength < 2)
102    {
103        ret = WS_ERROR_FRAME;
104    }
105
106// 检查扩展位并忽略
107if ((frameData[0] & 0x70) != 0x0)
108    {
109        ret = WS_ERROR_FRAME;
110    }
111
112// fin位: 为1表⽰已接收完整报⽂, 为0表⽰继续监听后续报⽂
113    ret = (frameData[0] & 0x80);
114if ((frameData[0] & 0x80) != 0x80)
115    {
116        ret = WS_ERROR_FRAME;
117    }
118
119// mask位, 为1表⽰数据被加密
120if ((frameData[1] & 0x80) != 0x80)
121    {
122        ret = WS_ERROR_FRAME;
123    }
124
125// 操作码
126    uint16_t payloadLength = 0;
127    uint8_t payloadFieldExtraBytes = 0;
128    uint8_t opcode = static_cast<uint8_t>(frameData[0] & 0x0f);
129if (opcode == WS_TEXT_FRAME)
130    {
131// 处理utf-8编码的⽂本帧
132        payloadLength = static_cast<uint16_t>(frameData[1] & 0x7f);
133if (payloadLength == 0x7e)
134        {
135            uint16_t payloadLength16b = 0;
136            payloadFieldExtraBytes = 2;
137            memcpy(&payloadLength16b, &frameData[2], payloadFieldExtraBytes);
138            payloadLength = ntohs(payloadLength16b);
139        }
140else if (payloadLength == 0x7f)
141        {
142// 数据过长,暂不⽀持
143            ret = WS_ERROR_FRAME;
144        }
145    }
146else if (opcode == WS_BINARY_FRAME || opcode == WS_PING_FRAME || opcode == WS_PONG_FRAME) 147    {
148// ⼆进制/ping/pong帧暂不处理
149    }
150else if (opcode == WS_CLOSING_FRAME)
151    {
152        ret = WS_CLOSING_FRAME;
153    }
154else
155    {
156        ret = WS_ERROR_FRAME;
157    }
158
159// 数据解码
160if ((ret != WS_ERROR_FRAME) && (payloadLength > 0))
161    {
162// header: 2字节, masking key: 4字节
163const char *maskingKey = &frameData[2 + payloadFieldExtraBytes];
164char *payloadData = new char[payloadLength + 1];
165        memset(payloadData, 0, payloadLength + 1);
166        memcpy(payloadData, &frameData[2 + payloadFieldExtraBytes + 4], payloadLength);
167for (int i = 0; i < payloadLength; i++)
168        {
169            payloadData[i] = payloadData[i] ^ maskingKey[i % 4];
170        }
171
172        outMessage = payloadData;
173delete[] payloadData;
174    }
175
176return ret;
177 }
178
179int CWebSocketProtocol::wsEncodeFrame(string inMessage, string &outFrame, enum WS_FrameType frameType) 180 {
181int ret = WS_EMPTY_FRAME;
182const uint32_t messageLength = inMessage.size();
183if (messageLength > 32767)
184    {
185// 暂不⽀持这么长的数据
186        std::cout << "暂不⽀持这么长的数据" << std::endl;
187
188return WS_ERROR_FRAME;
189    }
190
191    uint8_t payloadFieldExtraBytes = (messageLength <= 0x7d) ? 0 : 2;前端websocket怎么用
192// header: 2字节, mask位设置为0(不加密), 则后⾯的masking key⽆须填写, 省略4字节
193    uint8_t frameHeaderSize = 2 + payloadFieldExtraBytes;
194    uint8_t *frameHeader = new uint8_t[frameHeaderSize];
195    memset(frameHeader, 0, frameHeaderSize);
196// fin位为1, 扩展位为0, 操作位为frameType
197    frameHeader[0] = static_cast<uint8_t>(0x80 | frameType);
198
199// 填充数据长度
200if (messageLength <= 0x7d)
201    {
202        frameHeader[1] = static_cast<uint8_t>(messageLength);
203    }
204else
205    {
206        frameHeader[1] = 0x7e;
207        uint16_t len = htons(messageLength);
208        memcpy(&frameHeader[2], &len, payloadFieldExtraBytes);
209    }
210
211// 填充数据
212    uint32_t frameSize = frameHeaderSize + messageLength;
213char *frame = new char[frameSize + 1];
214    memcpy(frame, frameHeader, frameHeaderSize);
215    memcpy(frame + frameHeaderSize, inMessage.c_str(), messageLength);
216    frame[frameSize] = '\0';
217    outFrame = frame;
218
219delete[] frame;
220delete[] frameHeader;
221return ret;
222 }