由于需要在項目中增加Websocket協(xié)議,與客戶端進行通信,不想使用開源的庫,比如WebSocketPP,就自己根據(jù)WebSocket協(xié)議實現(xiàn)一套函數(shù),完全使用C++實現(xiàn)。
代碼已經(jīng)實現(xiàn),放在個人github上面,下面進行解釋說明:
一、原理
Websocket協(xié)議解析,可以參考博客http://www.cnblogs.com/jice1990/p/5435419.html,這里就不詳細細說。
服務器端實現(xiàn)就是使用TCP協(xié)議,使用傳統(tǒng)的socket流程進行綁定監(jiān)聽,使用epoll控制多路并發(fā),收到Websocket握手包時候進行握手處理,握手成功便可進行數(shù)據(jù)收發(fā)。
二、實現(xiàn)
1、服務器監(jiān)聽
該部分使用的是TCP socket流程,首先是通過socket函數(shù)建立socket,通過bind函數(shù)綁定到某個端口,本例使用的是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流程,首先在初始化時候使用epoll_create創(chuàng)建epoll句柄
epollfd_ = epoll_create(1024);
然后通過epoll_wait等待fd事件來臨,當監(jiān)聽到是listenfd事件時候,說明是客戶端連接服務器,就使用accept接受連接,然后注冊該連接EPOLLIN事件,當epoll監(jiān)聽到EPOLLIN事件時候,即可進行握手和數(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ù)Sec-WebSocket-Key進行SHA1哈希,生成相應的key,返回給客戶端,與客戶端進行握手。代碼如下:
//該函數(shù)是獲取websocket握手包的信息,按照分割字符進行解析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返回包的格式拼接相應的返回包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ù)讀取
當服務器與客戶端握手成功后,就可以進行正常的通信,讀取數(shù)據(jù)了。使用的是TCP協(xié)議的方法,解析Websocket包根據(jù)協(xié)議格式,在前面博客里面有詳細分析,這里只把實現(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é)
到此為止,完整實現(xiàn)了使用C++對Websocket協(xié)議進行解析,握手,數(shù)據(jù)收發(fā),不借助開源庫就實現(xiàn)了websocket相關功能,最大程度的與項目保存兼容。
-
C++
+關注
關注
22文章
2108瀏覽量
73651 -
WebSocket
+關注
關注
0文章
29瀏覽量
3748
原文標題:WebSocket的C++服務器端實現(xiàn)
文章出處:【微信號:C_Expert,微信公眾號:C語言專家集中營】歡迎添加關注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論