前提:
本人最近做的項目,服務器端用的是C++寫的,而與客戶端交互用的是websocket,服務器端要想正常的使用數據,必須要對websocket協議進行解析。
解析握手協議見我上一章內容: C++實現websocket服務器握手協議
WebSocket數據格式
- FIN:表示這個數據是不是接收完畢,爲1表示收到的數據是完整的,佔1bit
- RSV1~3:用於擴展,通常都爲0,各佔1bit
- OPCODE:表示報文的類型,佔4bit
- 0x00:標識一箇中間數據包
- 0x01:標識一個text數據包
- 0x02:標識一個二進制數據包
- 0x03~07:保留
- 0x08:標識一個斷開連接數據包
- 0x09:標識一個ping數據包
- 0x0A:標識一個pong數據包
- 0x0B~F:保留
- MASK:用於表示數據是否經常掩碼處理,爲1時,Masking-key即存在,佔1bit
- Payload len:表示數據長度,即Payload Data的長度,當Payload len爲0~125時,表示的值就是Payload Data的真實長度;當Payload len爲126時,報文其後的2個字節形成的16bits無符號整型數的值是Payload Data的真實長度(網絡字節序,需轉換);當Payload len爲127時,報文其後的8個字節形成的64bits無符號整型數的值是Payload Data的真實長度(網絡字節序,需轉換);
- Masking-key:掩碼,當Mask爲1時存在,佔4字節32bit
- Payload Data:表示數據
C++對websocket協議處理
/**
* @brief getWSFrameData 解析websocket的協議包,不能解決粘包半包問題
* @param msg 待解析的數據
* @param msgLen 待解析的數據長度
* @param outBuf 解析完成數據
* @return
*/
int unPackingWSFrameData(char *msg,
int msgLen,
std::vector<char> &outBuf)
{
//報文長度一定大於2字節,對於小於的,做返回處理
if(msgLen < 2)
{
return -3;
}
uint8_t opcode_ = 0;
uint8_t mask_ = 0;
uint8_t masking_key_[4] = {0,0,0,0};
uint64_t payload_length_ = 0;
int pos = 0;
//Opcode
opcode_ = msg[pos] & 0x0f;
pos++;
//MASK
mask_ = (unsigned char)msg[pos] >> 7;
//Payload length
payload_length_ = msg[pos] & 0x7f;
pos++;
if(payload_length_ == 126)
{
uint16_t length = 0;
memcpy(&length, msg + pos, 2);
pos += 2;
payload_length_ = ntohs(length);
}
else if(payload_length_ == 127)
{
uint32_t length = 0;
memcpy(&length, msg + pos, 8);
pos += 8;
payload_length_ = ntohl(length);
}
//Masking-key
if(mask_ == 1)
{
for(int i = 0; i < 4; i++)
{
masking_key_[i] = msg[pos + i];
}
pos += 4;
}
//取出消息數據
if (msgLen >= pos + payload_length_ )
{
outBuf.clear();
if(mask_ != 1)
{
char* dataBegin = msg + pos;
outBuf.insert(outBuf.begin(), dataBegin, dataBegin+payload_length_);
}
else
{
for(uint i = 0; i < payload_length_; i++)
{
int j = i % 4;
outBuf.push_back(msg[pos + i] ^ masking_key_[j]);
}
}
}
else
{
//此時包長小於報文中記錄的包長
return -2;
}
//斷開連接類型數據包
if ((int)opcode_ == 0x8)
return -1;
return 0;
}
以上函數即實現了對收到websocket數據的解析,返回結果爲:vector<char>output;
通常會在函數外面對此進行轉換爲char*,方便我們使用,見下:
vector<char>output;
char* out = &output[0];
當然,現在的解析還不是完美的解決方法,因爲在實際的使用當中,會存在接收的包粘包,半包等等問題,而以上函數只能解決收到包正好是一個完整的包的情況;具體解決粘包半包問題,留待下次博客吧!
參考:
結尾:
只爲記錄,只爲分享! 願所寫能對你有所幫助。不忘記點個贊,謝謝~