介紹
目前公司服務器是c++ tcp的網絡架構,現在想用這套做h5游戲,所以要擴展支持websocket通信。
那么什么是websocket?它和tcp有什么區別?這些隨便一搜一大把,這里就不再科普達。通俗簡單點講websocket就是山寨版的tcp,它底層實現就是tcp,唯一的區別就是網絡傳輸時websocket協議前面多了個標志它的包頭信息。去掉前面這部分包頭剩下的就和普通tcp一樣了。
那么講到這里,在現有tcp上怎么擴展支持websocket?其實就很簡單呢!既然它和tcp協議上就只是多了個包頭部分,那么我們的任務其實主要就是怎么解析這個包頭信息了。
最后,還有一個需要注意的細節是websocket協議前后端建立連接前需要一次握手協議,觸發時機是client發起connet連接請求時,會向server發送這條握手的協議,server收到后要回復client,這樣就建立了連接了。
好~閑話少說,下面直接上實現代碼…
實現
1.握手。
client第一次connet連接會發起握手協議,server在recv接收處解析,判斷如果是websocket的握手協議,那么同樣組裝好特定格式包頭回復給client,建立連接。
- 判斷是不是websocket協議
bool isWSHandShake(std::string &request) { size_t i = request.find("GET"); if(i == std::string::npos){ return false; } return true; }
- 如果是,解析握手協議重新組裝准備send回復給client
bool wsHandshake(std::string &request, std::string &response) { //得到客戶端請求信息的key std::string tempKey = request; size_t i = tempKey.find("Sec-WebSocket-Key"); if(i == std::string::npos){ return false; } tempKey = tempKey.substr(i + 19, 24); //拼接協議返回給客戶端 response = "HTTP/1.1 101 Switching Protocols\r\n"; response += "Connection: upgrade\r\n"; response += "Sec-WebSocket-Accept: "; std::string realKey = tempKey; realKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; SHA1 sha; unsigned int message_digest[5]; sha.Reset(); sha << realKey.c_str(); sha.Result(message_digest); for (int i = 0; i < 5; i++) { message_digest[i] = htonl(message_digest[i]); } realKey = base64_encode(reinterpret_cast<const unsigned char*>(message_digest), 20); realKey += "\r\n"; response += realKey.c_str(); response += "Upgrade: websocket\r\n\r\n"; return true; }
2.接收client協議解析
- 首先解析包頭信息
bool wsReadHeader(const unsigned char* cData, WebSocketStreamHeader* header) { if (cData == NULL) return false; const unsigned char *buf = cData; header->fin = buf[0] & 0x80; header->masked = buf[1] & 0x80; unsigned char stream_size = buf[1] & 0x7F; header->opcode = buf[0] & 0x0F; if (header->opcode == WS_FrameType::WS_CONTINUATION_FRAME) { //連續幀 return false; } else if (header->opcode == WS_FrameType::WS_TEXT_FRAME) { //文本幀 } else if (header->opcode == WS_FrameType::WS_BINARY_FRAME) { //二進制幀 } else if (header->opcode == WS_FrameType::WS_CLOSING_FRAME) { //連接關閉消息 return true; } else if (header->opcode == WS_FrameType::WS_PING_FRAME) { // ping return false; } else if (header->opcode == WS_FrameType::WS_PONG_FRAME) { // pong return false; } else { //非法幀 return false; } if (stream_size <= 125) { // small stream header->header_size =6; header->payload_size = stream_size; header->mask_offset = 2; } else if (stream_size == 126) { // medium stream header->header_size = 8; unsigned short s = 0; memcpy(&s, (const char*)&buf[2], 2); header->payload_size = ntohs(s); header->mask_offset = 4; } else if (stream_size == 127) { unsigned long long l = 0; memcpy(&l, (const char*)&buf[2], 8); header->payload_size = l; header->mask_offset = 10; } else { //Couldnt decode stream size 非法大小數據包 return false; } /* if (header->payload_size > MAX_WEBSOCKET_BUFFER) { return false; } */ return true; }
- 然后根據包頭解析出真是數據
bool wsDecodeFrame(const WebSocketStreamHeader& header, unsigned char cbSrcData[], unsigned short wSrcLen, unsigned char cbTagData[]) { const unsigned char *final_buf = cbSrcData; if (wSrcLen < header.header_size + 1) { return false; } char masks[4]; memcpy(masks, final_buf + header.mask_offset, 4); memcpy(cbTagData, final_buf + header.mask_offset + 4, header.payload_size); for (INT_PTR i = 0; i < header.payload_size; ++i){ cbTagData[i] = (cbTagData[i] ^ masks[i % 4]); } //如果是文本包,在數據最后加一個結束字符“\0” if (header.opcode == WS_FrameType::WS_TEXT_FRAME) cbTagData[header.payload_size] = '\0'; return true; }
3.組裝server發給client協議
bool wsEncodeFrame(std::string inMessage, std::string &outFrame, enum WS_FrameType frameType) { const uint32_t messageLength = inMessage.size(); if (messageLength > 32767) { // 暫不支持這么長的數據 return false; } 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); outFrame = std::string(frame, frameSize); delete[] frame; delete[] frameHeader; return true; }
至此,tcp上擴展websocket所需要處理的3大塊就都完成了,即握手、接收解析、發送組裝。
