由于需要在項(xiàng)目中增加Websocket協(xié)議,與客戶端進(jìn)行通信,不想使用開源的庫(kù),比如WebSocketPP,就自己根據(jù)WebSocket協(xié)議實(shí)現(xiàn)一套函數(shù),完全使用C++實(shí)現(xiàn)。
代碼已經(jīng)實(shí)現(xiàn),放在個(gè)人github上面,下面進(jìn)行解釋說明:
一、原理
Websocket協(xié)議解析,可以參考博客http://www.cnblogs.com/jice1990/p/5435419.html,這里就不詳細(xì)細(xì)說。
服務(wù)器端實(shí)現(xiàn)就是使用TCP協(xié)議,使用傳統(tǒng)的socket流程進(jìn)行綁定監(jiān)聽,使用epoll控制多路并發(fā),收到Websocket握手包時(shí)候進(jìn)行握手處理,握手成功便可進(jìn)行數(shù)據(jù)收發(fā)。
二、實(shí)現(xiàn)
1、服務(wù)器監(jiān)聽
該部分使用的是TCP socket流程,首先是通過socket函數(shù)建立socket,通過bind函數(shù)綁定到某個(gè)端口,本例使用的是9000,然后通過listen函數(shù)開啟監(jiān)聽,代碼如下:
listenfd_ = socket(AF_INET, SOCK_STREAM, 0); if(listenfd_ == -1){
DEBUG_LOG("創(chuàng)建套接字失敗!"); return -1; } struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(sockaddr_in)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(PORT); if(-1 == bind(listenfd_, (struct sockaddr *) (&server_addr), sizeof(server_addr))){ DEBUG_LOG("綁定套接字失敗!"); return -1; } if(-1 == listen(listenfd_, 5)){ DEBUG_LOG("監(jiān)聽失敗!"); return -1; }
2、epoll控制多路并發(fā)
該部分使用的是epoll流程,首先在初始化時(shí)候使用epoll_create創(chuàng)建epoll句柄
epollfd_ = epoll_create(1024);
然后通過epoll_wait等待fd事件來臨,當(dāng)監(jiān)聽到是listenfd事件時(shí)候,說明是客戶端連接服務(wù)器,就使用accept接受連接,然后注冊(cè)該連接EPOLLIN事件,當(dāng)epoll監(jiān)聽到EPOLLIN事件時(shí)候,即可進(jìn)行握手和數(shù)據(jù)讀取。代碼如下:
void ctl_event(int fd, bool flag){ struct epoll_event ev; ev.data.fd = fd; ev.events = flag ? EPOLLIN : 0; epoll_ctl(epollfd_, flag ? EPOLL_CTL_ADD : EPOLL_CTL_DEL, fd, &ev); if(flag){ set_noblock(fd); websocket_handler_map_[fd] = new Websocket_Handler(fd); if(fd != listenfd_) DEBUG_LOG("fd: %d 加入epoll循環(huán)", fd); } else{ close(fd); delete websocket_handler_map_[fd]; websocket_handler_map_.erase(fd); DEBUG_LOG("fd: %d 退出epoll循環(huán)", fd); } }int epoll_loop(){ struct sockaddr_in client_addr; socklen_t clilen; int nfds = 0; int fd = 0; int bufflen = 0; struct epoll_event events[MAXEVENTSSIZE]; while(true){ nfds = epoll_wait(epollfd_, events, MAXEVENTSSIZE, TIMEWAIT); for(int i = 0; i < nfds; i++){ ? ? ? ? ? if(events[i].data.fd == listenfd_){ ?? ? ? ? ? ? ? fd = accept(listenfd_, (struct sockaddr *)&client_addr, &clilen); ? ? ? ? ? ? ? ?ctl_event(fd, true); ? ? ? ? ? ?} ? ? ? ? ? ?else if(events[i].events & EPOLLIN){ ? ? ?? ? ? ? ? ? ? ?if((fd = events[i].data.fd) < 0) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? continue; ? ? ? ? ? ? ? ?Websocket_Handler *handler = websocket_handler_map_[fd]; ?? ? ? ? ? ? ? if(handler == NULL) ? ? ? ? ? ? ? ? ? ?continue; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? if((bufflen = read(fd, handler->getbuff(), BUFFLEN)) <= 0) { ? ? ? ? ? ? ? ?ctl_event(fd, false); ? ? ? ? ? ? ? ?} ? ? ? ? ? ? ? ?else{ ? ? ? ? ? ? ? ? ? ?handler->process(); } } } } return 0; }
3、Websocket握手連接
握手部分主要是根據(jù)Websocket握手包進(jìn)行解析,然后根據(jù)Sec-WebSocket-Key進(jìn)行SHA1哈希,生成相應(yīng)的key,返回給客戶端,與客戶端進(jìn)行握手。代碼如下:
//該函數(shù)是獲取websocket握手包的信息,按照分割字符進(jìn)行解析int fetch_http_info(){ std::istringstream s(buff_); std::string request; std::getline(s, request); if (request[request.size()-1] == '\r') { request.erase(request.end()-1); } else { return -1; } std::string header; std::string::size_type end; while (std::getline(s, header) && header != "\r") { if (header[header.size()-1] != '\r') { continue; //end } else { header.erase(header.end()-1); //remove last char } end = header.find(": ",0); if (end != std::string::npos) { std::string key = header.substr(0,end); std::string value = header.substr(end+2); header_map_[key] = value; } } return 0; }//該函數(shù)是根據(jù)websocket返回包的格式拼接相應(yīng)的返回包void parse_str(char *request){ strcat(request, "HTTP/1.1 101 Switching Protocols\r\n"); strcat(request, "Connection: upgrade\r\n"); strcat(request, "Sec-WebSocket-Accept: "); std::string server_key = header_map_["Sec-WebSocket-Key"]; server_key += MAGIC_KEY; SHA1 sha; unsigned int message_digest[5]; sha.Reset(); sha << server_key.c_str(); ? ?sha.Result(message_digest); ? ?for (int i = 0; i < 5; i++) { ? ? ? ?message_digest[i] = htonl(message_digest[i]); ? ?} ? ?server_key = base64_encode(reinterpret_cast
4、數(shù)據(jù)讀取
當(dāng)服務(wù)器與客戶端握手成功后,就可以進(jìn)行正常的通信,讀取數(shù)據(jù)了。使用的是TCP協(xié)議的方法,解析Websocket包根據(jù)協(xié)議格式,在前面博客里面有詳細(xì)分析,這里只把實(shí)現(xiàn)代碼貼出來。
int fetch_websocket_info(char *msg){ int pos = 0; fetch_fin(msg, pos); fetch_opcode(msg, pos); fetch_mask(msg, pos); fetch_payload_length(msg, pos); fetch_masking_key(msg, pos); return fetch_payload(msg, pos); } int fetch_fin(char *msg, int &pos){ fin_ = (unsigned char)msg[pos] >> 7; return 0; }int fetch_opcode(char *msg, int &pos){ opcode_ = msg[pos] & 0x0f; pos++; return 0; }int fetch_mask(char *msg, int &pos){ mask_ = (unsigned char)msg[pos] >> 7; return 0; }int fetch_masking_key(char *msg, int &pos){ if(mask_ != 1) return 0; for(int i = 0; i < 4; i++) ? ? ? ?masking_key_[i] = msg[pos + i]; ? ?pos += 4; ? ?return 0; }int fetch_payload_length(char *msg, int &pos){ ? ?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, 4); ? ? ? ?pos += 4; ? ? ? ?payload_length_ = ntohl(length); ? ?} ?? ?return 0; }int fetch_payload(char *msg, int &pos){ ? ?memset(payload_, 0, sizeof(payload_)); ? ?if(mask_ != 1){ ? ? ? ?memcpy(payload_, msg + pos, payload_length_); ? ?} ?? ?else { ?? ? ? ?for(uint i = 0; i < payload_length_; i++){ ? ? ? ? ? ?int j = i % 4; ? ? ? ? ? ?payload_[i] = msg[pos + i] ^ masking_key_[j]; ? ? ? ?} ? ?} ? ?pos += payload_length_; ?? ?return 0; }
5、總結(jié)
到此為止,完整實(shí)現(xiàn)了使用C++對(duì)Websocket協(xié)議進(jìn)行解析,握手,數(shù)據(jù)收發(fā),不借助開源庫(kù)就實(shí)現(xiàn)了websocket相關(guān)功能,最大程度的與項(xiàng)目保存兼容。
-
C++
+關(guān)注
關(guān)注
22文章
2114瀏覽量
73885 -
WebSocket
+關(guān)注
關(guān)注
0文章
29瀏覽量
3796
原文標(biāo)題:WebSocket的C++服務(wù)器端實(shí)現(xiàn)
文章出處:【微信號(hào):C_Expert,微信公眾號(hào):C語言專家集中營(yíng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
Django3如何使用WebSocket實(shí)現(xiàn)WebShell
基于TCP的一種新的網(wǎng)絡(luò)協(xié)議WebSocket
C++教程之函數(shù)的遞歸調(diào)用
基于C++的modbus通訊協(xié)議模型實(shí)現(xiàn)
如何在中斷C函數(shù)中調(diào)用C++
![如何在中斷<b class='flag-5'>C</b><b class='flag-5'>函數(shù)</b>中調(diào)用<b class='flag-5'>C++</b>](https://file.elecfans.com/web1/M00/91/BA/pIYBAFzTzJuAT0pBAAEyN2vFGuQ683.png)
WebSocket有什么優(yōu)點(diǎn)
C++之重載函數(shù)學(xué)習(xí)總結(jié)
EE-128:C++中的DSP:從C++調(diào)用匯編類成員函數(shù)
![EE-128:<b class='flag-5'>C++</b>中的DSP:從<b class='flag-5'>C++</b>調(diào)用匯編類成員<b class='flag-5'>函數(shù)</b>](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
在C++中如何用虛函數(shù)實(shí)現(xiàn)多態(tài)
虛函數(shù),C++開發(fā)者如何有效利用
深度解析C++中的虛函數(shù)
![深度解析<b class='flag-5'>C++</b>中的虛<b class='flag-5'>函數(shù)</b>](https://file.elecfans.com/web2/M00/91/66/pYYBAGPsTWqAcRlFAAEnF_VliNI953.jpg)
C++基礎(chǔ)知識(shí)之函數(shù)1
websocket協(xié)議的原理
![<b class='flag-5'>websocket</b><b class='flag-5'>協(xié)議</b>的原理](https://file1.elecfans.com/web2/M00/AD/3C/wKgaomVMhjeAbH1rAAFa8EDQlqU629.jpg)
評(píng)論