0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

根據(jù)WebSocket協(xié)議完全使用C++實(shí)現(xiàn)函數(shù)

C語言專家集中營 ? 來源:未知 ? 2018-11-28 14:29 ? 次閱讀

由于需要在項(xiàng)目中增加Websocket協(xié)議,與客戶端進(jìn)行通信,不想使用開源的庫,比如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接受連接,然后注冊該連接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(message_digest),20); server_key += "\r\n"; strcat(request, server_key.c_str()); strcat(request, "Upgrade: websocket\r\n\r\n"); }

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++對Websocket協(xié)議進(jìn)行解析,握手,數(shù)據(jù)收發(fā),不借助開源庫就實(shí)現(xiàn)了websocket相關(guān)功能,最大程度的與項(xiàng)目保存兼容。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • C++
    C++
    +關(guān)注

    關(guān)注

    21

    文章

    2085

    瀏覽量

    73300
  • WebSocket
    +關(guān)注

    關(guān)注

    0

    文章

    25

    瀏覽量

    3696

原文標(biāo)題:WebSocket的C++服務(wù)器端實(shí)現(xiàn)

文章出處:【微信號:C_Expert,微信公眾號:C語言專家集中營】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    Django3如何使用WebSocket實(shí)現(xiàn)WebShell

    websocket 服務(wù)。 大致看了下覺得這不夠有趣,翻了翻 django 的官方文檔發(fā)現(xiàn) django 原生是不支持 websocket 的,但 django3 之后支持了 asgi 協(xié)議可以自己
    的頭像 發(fā)表于 11-17 09:58 ?4181次閱讀

    基于TCP的一種新的網(wǎng)絡(luò)協(xié)議WebSocket

    開啟 WebSocket 服務(wù)WebSocket 服務(wù)是網(wǎng)頁程序、安卓 App、微信小程序等獲得數(shù)據(jù)和服務(wù)的接口,是基于TCP 的一種新的網(wǎng)絡(luò)協(xié)議,它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工通信。通
    發(fā)表于 12-16 07:38

    C++教程之函數(shù)的遞歸調(diào)用

    C++教程之函數(shù)的遞歸調(diào)用 在執(zhí)行函數(shù) f 的過程中,又要調(diào)用 f 函數(shù)本身,稱為函數(shù)的遞歸調(diào)用;形式上:一個(gè)正在執(zhí)行的
    發(fā)表于 05-15 18:00 ?35次下載

    基于C++的modbus通訊協(xié)議模型實(shí)現(xiàn)

    基于C++的modbus通訊協(xié)議模型實(shí)現(xiàn),很好的資料,快來下載學(xué)習(xí)吧。
    發(fā)表于 03-21 17:27 ?54次下載

    如何在中斷C函數(shù)中調(diào)用C++

    之前,我們在單片機(jī)程序開發(fā)時(shí)都會(huì)面對中斷函數(shù)。眾所周知的,這個(gè)中斷函數(shù)肯定是要用C函數(shù)來定義的。我在用C++進(jìn)行程序開發(fā)的時(shí)候就發(fā)現(xiàn)了一個(gè)需
    發(fā)表于 05-09 18:17 ?0次下載
    如何在中斷<b class='flag-5'>C</b><b class='flag-5'>函數(shù)</b>中調(diào)用<b class='flag-5'>C++</b>

    WebSocket有什么優(yōu)點(diǎn)

    WebSocket是一種在單個(gè)TCP連接上進(jìn)行全雙工通信的協(xié)議。WebSocket通信協(xié)議于2011年被IETF定為標(biāo)準(zhǔn)RFC 6455,并由RFC7936補(bǔ)充規(guī)范。
    的頭像 發(fā)表于 02-15 15:53 ?8160次閱讀
    <b class='flag-5'>WebSocket</b>有什么優(yōu)點(diǎn)

    C++之重載函數(shù)學(xué)習(xí)總結(jié)

    函數(shù)重載是c++c的一個(gè)重要升級;函數(shù)重載通過參數(shù)列表區(qū)分不同的同名函數(shù);extern關(guān)鍵字能夠實(shí)現(xiàn)
    的頭像 發(fā)表于 12-24 17:10 ?727次閱讀

    EE-128:C++中的DSP:從C++調(diào)用匯編類成員函數(shù)

    EE-128:C++中的DSP:從C++調(diào)用匯編類成員函數(shù)
    發(fā)表于 04-16 17:04 ?2次下載
    EE-128:<b class='flag-5'>C++</b>中的DSP:從<b class='flag-5'>C++</b>調(diào)用匯編類成員<b class='flag-5'>函數(shù)</b>

    C++中如何用虛函數(shù)實(shí)現(xiàn)多態(tài)

    01 — C++函數(shù)探索 C++是一門面向?qū)ο笳Z言,在C++里運(yùn)行時(shí)多態(tài)是由虛函數(shù)和純虛函數(shù)
    的頭像 發(fā)表于 09-29 14:18 ?1618次閱讀

    C++ C語言函數(shù)查詢電子版下載

    C++ C語言函數(shù)查詢電子版下載
    發(fā)表于 01-18 10:15 ?0次下載

    函數(shù)C++開發(fā)者如何有效利用

    函數(shù)是基類中聲明的成員函數(shù),且使用者期望在派生類中將其重新定義。那么,在 C++ 中,什么是虛函數(shù)呢?在 C++ 中,通常將虛
    的頭像 發(fā)表于 02-11 09:39 ?832次閱讀

    深度解析C++中的虛函數(shù)

    函數(shù)作為C++的重要特性,讓人又愛又怕,愛它功能強(qiáng)大,但又怕駕馭不好,讓它反咬一口,今天我們用CPU的角度,撕掉語法的偽裝,重新認(rèn)識一下虛函數(shù)。 虛函數(shù)
    的頭像 發(fā)表于 02-15 11:14 ?737次閱讀
    深度解析<b class='flag-5'>C++</b>中的虛<b class='flag-5'>函數(shù)</b>

    C++基礎(chǔ)知識之函數(shù)1

    函數(shù)C++ 中的一個(gè)重要概念,它可以讓我們將一段代碼封裝起來,然后在需要的時(shí)候調(diào)用它。C++ 中的函數(shù)有以下幾個(gè)特點(diǎn): * 函數(shù)
    的頭像 發(fā)表于 04-03 10:34 ?457次閱讀

    c++常見函數(shù)

    c++常見函數(shù)集包括:線性代數(shù)方程組的解法、插值、數(shù)值積分、特殊函數(shù)、函數(shù)逼近、隨機(jī)數(shù)排序、特征值問題、數(shù)據(jù)擬合、方程求根和非線性方程組的解法、函數(shù)
    發(fā)表于 05-09 14:52 ?1次下載

    websocket協(xié)議的原理

    WebSocket協(xié)議是基于TCP的一種新的網(wǎng)絡(luò)協(xié)議。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工(full-duplex)通信——允許服務(wù)器主動(dòng)發(fā)送信息給客戶端。
    的頭像 發(fā)表于 11-09 15:13 ?810次閱讀
    <b class='flag-5'>websocket</b><b class='flag-5'>協(xié)議</b>的原理