当前位置: 首页 > news >正文

c++ websocket 协议分析与实现

前言
网上有很多第三方库,nopoll,uwebsockets,libwebsockets,都喜欢回调或太复杂,个人只需要在后端用,所以手动写个;

1:环境
ubuntu18
g++(支持c++11即可)
第三方库:jsoncpp,openssl
2:安装
jsoncpp 读取json 配置文件 用 自动安装 网上一堆教程
openssl 如果系统没带,需要安装下 sudo apt-get install openssl 一般是1.1版本 够用了
3:websocket server
1> 主要就用到 epoll 模式(io_uring 更好点,就是内核版本要高点),3个进程 主进程作为监控进程 2个子进程 一个network进程 一个 logic 进程
2> 子进程间 主要通过共享内存 加socketpair 通知 交换数据
在这里插入图片描述
在这里插入图片描述
3>websocket 握手协议 先看例子
在这里插入图片描述
上前端代码 html

<!DOCTYPE HTML>  
<html>  
<head>  <meta http-equiv="content-type" content="text/html" />  <meta name="author" content="https://github.com/" />  <title>websocket test</title>  <script>var socket;  function Connect(){  try{  socket=new WebSocket('ws://192.168.1.131:9000'); //'ws://192.168.1.131:9000');  }catch(e){  alert('error catch'+e);  return;  }  socket.onopen = sOpen;  socket.onerror = sError;socket.onmessage= sMessage;socket.onclose= sClose;}  function sOpen(){alert('connect success!');}function sError(e){alert("[error] " + e);//writeObj(e);}function sMessage(msg){ if(typeof(msg) == 'object'){//let json = JSON.stringify(msg);//console.log('server says:' +json);//writeObj(msg);if(msg.data){  //msg.hasOwnProperty('data')console.log('server says'+msg.data);}else{writeObj(msg);//console.log('[1]server says'+msg.data);}}else{alert('server says:' + msg);  }}function sClose(e){alert("connect closed:" + e.code);}  function Send(){socket.send(document.getElementById("msg").value);} function Close(){socket.close();} function writeObj(obj){ var description = ""; for(var i in obj){ var property=obj[i]; description+=i+" = "+property+"\n"; } alert(description); }</script>
</head>  <body>  
<input id="msg" type="text">  
<button id="connect" onclick="Connect();">Connect</button>  
<button id="send" onclick="Send();">Send</button>  
<button id="close" onclick="Close();">Close</button></body>  </html>  

在Microsoft Edge 运行结果
在这里插入图片描述
golang 前端代码如下

package mainimport ("fmt""golang.org/x/net/websocket""log""strings"
)var origin = "http://192.168.1.131:9000"  
//var url = "ws://192.168.1.131:7077/websocket"
var url = "wss://192.168.1.131:9000/websocket"
func main() {ws, err := websocket.Dial(url, "", origin)if err != nil {log.Fatal(err)}// send text framevar message2 = "hello"websocket.Message.Send(ws, message2)fmt.Printf("Send: %s\n", message2)// receive text framevar message stringwebsocket.Message.Receive(ws, &message)fmt.Printf("Receive: %s\n", message)for true {fmt.Printf("please input string:")var inputstr stringfmt.Scan(&inputstr)if(strings.Compare(inputstr,"quit") == 0){break}else{websocket.Message.Send(ws, inputstr)fmt.Printf("Send: %s\n", inputstr)var output stringwebsocket.Message.Receive(ws, &output)fmt.Printf("Receive: %s\n", output)}}ws.Close()//关闭连接fmt.Printf("client exit\n")
}

测试结果
在这里插入图片描述

server 握手代码
在这里插入图片描述

int  c_WebSocket::recv_handshake() {int n, len, ret;uint32_t pos = 0;uint16_t u16msglen = 0;const bool bssl = isSsl();if (bssl) {n = SSL_read(m_ssl, m_recv_buf + m_recv_pos, m_recv_buf_size - m_recv_pos);}else {n = recv(m_fd, m_recv_buf + m_recv_pos, m_recv_buf_size - m_recv_pos, 0);}if (n > 0) {if (m_is_closed) {m_recv_pos = 0;return true;}m_recv_pos += n;m_recv_buf[m_recv_pos] = 0;printf("recv %d handshake %s len=%d recvlen=%d", m_id, m_recv_buf,n,m_recv_pos);// goto READ;//  int32_t pos = 0;for (;;) {if (m_recv_pos >= c_u16MinHandShakeSize)  //消息头{//  \r 0x0D \n  0xAconst  int nRet = fetch_http_info((char*)m_recv_buf, m_recv_pos);if (1 == nRet) {  //ok//  f(strcasecmp(header, "Sec-Websocket-Protocol") == 0)//      conn->accepted_protocol = value;// std::map<std::string, std::string>::iterator it1 = m_map_header.find("Sec-WebSocket-Key"); //一般固定24个字节std::map<std::string, std::string>::iterator it2 = m_map_header.find("Sec-WebSocket-Protocol");//int map_size = m_map_header.size();if (it1 != m_map_header.end()) {printf("key=%s value=%s %d \n", it1->first.c_str(), it1->second.c_str(), map_size);}else {return -1;}char acceptvalue[1024] = { 0, };uint32_t  value_len = it1->second.length();memcpy(acceptvalue, it1->second.c_str(), value_len);//  memcpy(accept_key, websocket_key, key_length);
#define MAGIC_KEY "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"memcpy(acceptvalue + value_len, MAGIC_KEY, 36);acceptvalue[value_len + 36] = 0;unsigned char md[SHA_DIGEST_LENGTH];SHA1((unsigned char*)acceptvalue, strlen(acceptvalue), md);std::string server_key = base64_encode(reinterpret_cast<const unsigned char*>(md), SHA_DIGEST_LENGTH);char rep_handshake[1024] = { 0, };memset(rep_handshake, 0, sizeof(rep_handshake));if (it2 != m_map_header.end()) {//子协议char szsub_protocol[512] = { 0, };std::size_t pos_t = it2->second.find(",");if (pos_t != std::string::npos && pos_t < 512) {memcpy(szsub_protocol, it2->second.c_str(), pos_t);sprintf(rep_handshake, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %s\r\nSec-WebSocket-Protocol: %s\r\n\r\n",server_key.c_str(), szsub_protocol);}else {return -1;}}else {sprintf(rep_handshake, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %s\r\n\r\n",server_key.c_str());}m_recv_pos = 0;set_handshake_ok(); //握手完毕send_pkg((uint8_t*)rep_handshake, strlen(rep_handshake));break;}else {printf("fetch_http_info nRet=%d \n ", nRet);return -2;}}else {break;}}//end forif (pos != 0 && m_recv_pos > 0) {memcpy(m_recv_buf, m_recv_buf + pos, m_recv_pos);}}else {if (bssl) {//EAGAIN或EWOULDBLOCK二者应该是一样的,对应的错误码是11//ret = SSL_get_error(m_ssl, n);//int ssl_error = SSL_get_verify_result(ssl);//if (SSL_ERROR_WANT_READ == ret || SSL_ERROR_WANT_WRITE == ret) return true;SSL_ERROR_NONE == ret  n>0 ok  other error//printf("SSL_get_error(%d %d %d)\n", n, ret, errno);//SSL_get_error(-1 1,11)//return false;int ret = ssl_check_error(m_ssl, n);printf("SSL_get_error(%d %d %d %d)\n", n, ret, errno, m_recv_pos);//SSL_get_error(-1 -1 11)if (ret == -2) {return true;}if (errno == EAGAIN || errno == EINTR) {return true;}return false;}else {if (n == 0)return false;if (errno == EAGAIN || errno == EINTR) {return true;}else {return false;}}}return  true;
}int c_WebSocket::fetch_http_info(char* recv_buf, const uint32_t buf_len) {//  \r 0x0D \n  0xAconst uint32_t max_len = 1024;char bufline[max_len] = { 0, };uint32_t  bufpos = 0;uint8_t   ustate = 0;//std::map<std::string, std::string> map_header;char szsubhead[max_len] = { 0, };for (uint32_t i = 0; i < buf_len; i++) {bufline[bufpos++] = recv_buf[i];if (bufpos >= max_len) return  -1;if (recv_buf[i] == 0x0A) {bufline[bufpos] = 0;if (0 == ustate) { //GET /websocket HTTP/1.1if (ws_strncmp(bufline, "GET ", 4)) {if (bufpos < 15) {return -1;}//the get url must have a minimum size: GET / HTTP/1.1\r\n 16 (15 if only \n)//return nopoll_cmp (buffer + iterator, "HTTP/1.1\r\n") || nopoll_cmp (buffer + iterator, "HTTP/1.1\n");//  char* pos1 = strstr(bufline, "HTTP/1.1\r\n");///  char* pos2 = strstr(bufline, "HTTP/1.1\n");int32_t  nhttp1_1_pos = (int32_t)(bufpos - 2 - 8); //HTTP/1.1  8BYTE   HTTP/1.1\r\n  //H的位置if (bufline[bufpos - 2] != '\r') {nhttp1_1_pos += 1;//HTTP/1.1\n}const  int32_t ucopylen = nhttp1_1_pos - 1 - 4; // -1 http前的空格  -4 是GET空格 的长度if (ucopylen > 0 && ucopylen < 128) { //   /websocket 长度memcpy(szsubhead, bufline + 4, ucopylen);szsubhead[ucopylen] = 0;}else {return -3;}}else {return -1;}ustate = 1;bufpos = 0;}else {//if (buffer_size == 2 && nopoll_ncmp (buffer, "\r\n", 2))if (2 == bufpos && ws_strncmp(bufline, "\r\n", 2)) {//握手协议结尾ustate == 2;//检查最基本的握手协议// Connection: Upgrade//     Host: 192.168.1.2 : 8080//      Sec - WebSocket - Key : 821VqJT7EjnceB8m7mbwWA ==//      Sec - WebSocket - Version : 13//      Upgrade : websocket// ensure we have all minumum data std::map<std::string, std::string>::iterator it1 = m_map_header.find("Upgrade");//固定  websocketstd::map<std::string, std::string>::iterator it2 = m_map_header.find("Connection");  //固定 Upgradestd::map<std::string, std::string>::iterator it3 = m_map_header.find("Sec-WebSocket-Version");std::map<std::string, std::string>::iterator it4 = m_map_header.find("Sec-WebSocket-Key"); //一般固定24个字节const bool  bcheckOrigin = false; //浏览器必须有,其他可能没有std::map<std::string, std::string>::iterator it5 = m_map_header.find("Origin"); //if (it1 != m_map_header.end() && ws_strncmp(it1->second.c_str(), "websocket", 9) &&it2 != m_map_header.end() && ws_strncmp(it2->second.c_str(), "Upgrade", 7) &&it3 != m_map_header.end() && ws_strncmp(it3->second.c_str(), "13", 2) &&it4 != m_map_header.end() && it4->second.length() > 12 &&(bcheckOrigin == (bcheckOrigin && it5 != m_map_header.end()))) { //其他字段忽略了return 1;}return -6;}else {char* pos1 = strstr(bufline, ":");if (pos1 != nullptr) {//  std::string key = header.substr(0, end);//  std::string value = header.substr(end + 2);int32_t  key_len = pos1 - bufline;int32_t  value_len = bufpos - key_len - 1 - 1;if (key_len > 1 && value_len > 1) {bufline[key_len] = 0;std::string key = bufline;if (bufline[bufpos - 1] == '\n') {bufline[bufpos - 1] = 0;//   --value_len;}if (bufline[bufpos - 2] == '\r') {bufline[bufpos - 2] = 0;//   --value_len;}std::string value = &bufline[key_len + 2];m_map_header[key] = value;}else {return -4;}}else {return -4;}bufpos = 0;}}}}return 0;
}

握手请求与回复
Origin: http://192.168.1.131:9000 : 原始的协议和URL
Connection: Upgrade:表示要升级协议了
Upgrade: websocket:表示要升级到 WebSocket 协议;
Sec-WebSocket-Version: 13:表示 WebSocket 的版本。如果服务端不支持该版本,需要返回一个 Sec-WebSocket-Versionheader ,里面包含服务端支持的版本号
Sec-WebSocket-Key:与后面服务端响应首部的 Sec-WebSocket-Accept 是配套的,提供基本的防护,比如恶意的连接,或者无意的连接
服务端响应协议升级
HTTP/1.1 101 Switching Protocols
Connection:Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PTU=
HTTP/1.1 101 Switching Protocols: 状态码 101 表示协议切换
Sec-WebSocket-Accept:根据客户端请求首部的 Sec-WebSocket-Key 计算出来
将 Sec-WebSocket-Key 跟 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 拼接。
通过 SHA1 计算出摘要,并转成 base64 字符串。计算公式如下:
Base64(sha1(Sec-WebSocket-Key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11))
Connection:Upgrade:表示协议升级
Upgrade: websocket:升级到 websocket 协议

4:接受数据帧
在这里插入图片描述
代码如下

int  c_WebSocket::recv_dataframe() {int n, len,ret;//   uint32_t pos = 0;uint16_t u16msglen = 0;const bool bssl = isSsl();if (isSsl()) {n = SSL_read(m_ssl, m_recv_buf + m_recv_pos, m_recv_buf_size - m_recv_pos);}else {n = recv(m_fd, m_recv_buf + m_recv_pos, m_recv_buf_size - m_recv_pos, 0);}//  n = recv(m_fd, m_recv_buf + m_recv_pos, m_recv_buf_size - m_recv_pos, 0);if (n > 0) {if (m_is_closed) {m_recv_pos = 0;return true;}m_recv_pos += n;// goto READ;int32_t pos = 0;for (;;) {if (m_recv_pos >= c_u16MsgHeadSize)  //消息头 2个字节{int t = parse_dataframe(m_recv_buf + pos, m_recv_pos);if (t < 0) return false;else if (0 == t) break;pos += t;m_recv_pos -= t; u16msglen + c_u16MsgHeadSize; //sub one packet len//  pos += u16msglen + c_u16MsgHeadSize;}else {break;}}//end forif (pos != 0 && m_recv_pos > 0) {memcpy(m_recv_buf, m_recv_buf + pos, m_recv_pos);}if (pos > 0) { //收到消息的时间m_lastrecvmsg = get_reactor().getCurSecond();}}else {if (bssl) {//ret = SSL_get_error(m_ssl, n);//if (SSL_ERROR_WANT_READ == ret || SSL_ERROR_WANT_WRITE == ret) return true;SSL_ERROR_NONE == ret  n>0 ok  other error//return false;int ret = ssl_check_error(m_ssl, n);if (ret == -2) {return true;}if (errno == EAGAIN || errno == EINTR) {return true;}return false;}else {if (n == 0)return false;if (errno == EAGAIN || errno == EINTR) {return true;}else {return false;}}}return  true;
}

处理数据帧,把payload 转发到 logic进程,由logic去处理
一帧数据长度超过 65k 直接抛弃,这里可以根据实际需求设定长度

int  c_WebSocket::parse_dataframe(uint8_t* recv_buf, const uint32_t buf_len) {/* get fin bytes */
#define  FAIL_AND_CLOSE  -1         //接受失败OR 关闭
#define  NEED_CLOSE  -1             //需要关闭#define  CONTINUE_RECV_MSG  0       //消息不完整,需要继续接受
#define  ONE_MSG_LENGHT(X) X        //接受完一条消息,消息总长度为X#define  MASK_LEN   4      //掩码长度 
#define  PAYLOAD_LENGTH_126  2   //126 额外2个字节uint8_t  t_fin = msg_get_bit(recv_buf[0], 7);if (t_fin == 0) return FAIL_AND_CLOSE;uint8_t t_code = recv_buf[0] & 0x0F;uint8_t t_masked = msg_get_bit(recv_buf[1], 7);uint16_t t_payload_size = recv_buf[1] & 0x7F;if (t_masked == 0) return  FAIL_AND_CLOSE;if (t_code == CLOSE_FRAME) {  //关闭帧return NEED_CLOSE;}uint16_t t_playload_pos = c_u16MsgHeadSize;if (t_payload_size == 126) {if (buf_len < c_u16MsgHeadSize + PAYLOAD_LENGTH_126)  return CONTINUE_RECV_MSG;uint16_t length = 0;memcpy(&length, recv_buf + c_u16MsgHeadSize, PAYLOAD_LENGTH_126);if (length > MAX_PAYLOAD_REQ) return FAIL_AND_CLOSE;  //消息过长if (buf_len < c_u16MsgHeadSize + PAYLOAD_LENGTH_126 + MASK_LEN + length) return CONTINUE_RECV_MSG; //等下此接受  //4 为mask长度,前端发过来必须有t_payload_size = length;t_playload_pos += PAYLOAD_LENGTH_126; //}else if (t_payload_size == 127) {return FAIL_AND_CLOSE;}else {if (buf_len < c_u16MsgHeadSize + MASK_LEN + t_payload_size) return CONTINUE_RECV_MSG; //等下此接受}memcpy(masking_key_, &recv_buf[t_playload_pos], MASK_LEN);t_playload_pos += MASK_LEN; //if (t_code == PONG_FRAME) {if (m_lastsendping > 0) {printf("time=[%u]recv PONG_FRAME \n",g_reactor.getCurSecond());m_lastsendping = 0; //ping消息回复m_sendpingcount = 0;}return ONE_MSG_LENGHT(t_playload_pos + t_payload_size;)}if (t_payload_size == 0) {if (t_code == PING_FRAME) {//  nopoll_conn_send_pong(conn, nopoll_msg_get_payload_size(msg), (noPollPtr)nopoll_msg_get_payload(msg));//  nopoll_msg_unref(msg);send_data((char*)&recv_buf[t_playload_pos], t_payload_size, PONG_FRAME);return  t_playload_pos + t_payload_size;}return FAIL_AND_CLOSE;}// char* play_load = (char*)&recv_buf[t_playload_pos];m_payload_length_ = t_payload_size;int j = 0;for (uint i = 0; i < m_payload_length_; i++) {j = i % 4;m_payload_[i] = recv_buf[t_playload_pos + i] ^ masking_key_[j];}//put to public procshm_block_t sb;sb.fd = m_fd;sb.id = m_id;sb.len = t_payload_size;sb.type = PROTO_BLOCK;sb.frametype = t_code;//把数据发送出去// recv_push(m_u32channel, m_u32pipeindex, &sb, m_recv_buf + pos + c_u16MsgHeadSize, false);recv_push(m_u32channel, m_u32pipeindex, &sb, (uint8_t*)m_payload_, false);//int32_t nRet =	printf("client recv one complete pack len=%d m_u32pipeindex=%d nRet=%d\n", u16msglen, m_u32pipeindex, nRet);//   m_recv_pos -= u16msglen + c_u16MsgHeadSize; //sub one packet len//   pos += u16msglen + c_u16MsgHeadSize;return  t_playload_pos + t_payload_size;}

再来个logic 进程处理

void c_Logic::dologic(struct shm_block_t* pblock, uint8_t *buf, bool brecv)
{//处理收到的逻辑switch (pblock->type){case CLOSE_BLOCK:{}break;case PROTO_BLOCK:{if (strncmp((char*)buf, "hello", 5) == 0) {buf[0] = 'H';buf[1] = 'E';buf[2] = 'L';buf[3] = 'L';buf[4] = 'O';send_data(pblock,buf,pblock->len, (WebSocketFrameType)pblock->frametype);}else {buf[0] = '_';send_data(pblock, buf, pblock->len, (WebSocketFrameType)pblock->frametype);}}break;case CDUMP_BLOCK:{}break;default:break;}
}

发送数据

int send_data(struct shm_block_t* pblock, uint8_t* msg, const uint32_t msglen, WebSocketFrameType ftype) {const uint32_t   MAX_PAYLOAD_SEND = 4 * 1024; //最大发送长度if (msglen > MAX_PAYLOAD_SEND) return -1;uint32_t length = msglen;char               header[14];int                header_size;memset(header, 0, sizeof(header));const bool bhas_fin = true;if (bhas_fin) {msg_set_bit(header, 7);}if (ftype >= 0) {header[0] |= ftype & 0x0f;}const bool bhas_mask = false; //服务器发送不需要mask,前端给过来才需要if (bhas_mask) {msg_set_bit(header + 1, 7);}header_size = 2;if (length < 126) {header[1] |= length;}else if (length <= 0xFFFF) {/* set the next header length is at least 65535 */header[1] |= 126;header_size += 2;/* set length into the next bytes */msg_set_16bit(length, header + 2);}else {//再大的不让发送 //先写上,用不上也没关系header[1] = 127;
#if defined(WS_64BIT_PLATFORM)if (length < 0x8000000000000000) {header[2] = (length & 0xFF00000000000000) >> 56;header[3] = (length & 0x00FF000000000000) >> 48;header[4] = (length & 0x0000FF0000000000) >> 40;header[5] = (length & 0x000000FF00000000) >> 32;}
#else// (length < 0x80000000)header[2] = header[3] = header[4] = header[5] = 0;
#endifheader[6] = (length & 0x00000000FF000000) >> 24;header[7] = (length & 0x0000000000FF0000) >> 16;header[8] = (length & 0x000000000000FF00) >> 8;header[9] = (length & 0x00000000000000FF);header_size += 8;}if (bhas_mask) {//不写了 ////  msg_set_32bit(mask_value, header + header_size);//  header_size += 4;}uint8_t buf[MAX_PAYLOAD_SEND + 14];memcpy(buf, header, header_size);memcpy(buf + header_size, msg, msglen);//send_pkg(buf, msglen + header_size);//return msglen + header_size;shm_block_t sb;sb.fd = pblock->fd;sb.id = pblock->id;sb.type = PROTO_BLOCK;sb.len = msglen + header_size;sb.frametype = (uint8_t)ftype;send_push(0, 1, &sb, buf, true);return 0;
}

5:支持 SSL
先加载证书

bool c_Accept::loadssl(const char* private_key_file, const char* server_crt_file, const char* ca_crt_file) {m_ctx = SSL_CTX_new(SSLv23_server_method());if (!m_ctx) { return false; }//assert(ctx);// 不校验客户端证书SSL_CTX_set_verify(m_ctx, SSL_VERIFY_NONE, nullptr);// 加载CA的证书  if (!SSL_CTX_load_verify_locations(m_ctx, ca_crt_file, nullptr)) {printf("SSL_CTX_load_verify_locations error!\n");return false;}// 加载自己的证书  if (SSL_CTX_use_certificate_file(m_ctx, server_crt_file, SSL_FILETYPE_PEM) <= 0) {printf("SSL_CTX_use_certificate_file error!\n");return false;}// 加载私钥if (SSL_CTX_use_PrivateKey_file(m_ctx, private_key_file, SSL_FILETYPE_PEM) <= 0) {printf("SSL_CTX_use_PrivateKey_file error!\n");return false;}// 判定私钥是否正确  if (!SSL_CTX_check_private_key(m_ctx)) {printf("SSL_CTX_check_private_key error!\n");return false;}return true;}

accept 后, ssl = SSL_new(get_ssl_ctx()); 再调用 SSL_accept

bool c_Accept::handle_input()
{sockaddr_in ip;socklen_t len;int cli_fd;while (1) {len = sizeof(ip);cli_fd = accept(m_fd, (sockaddr *)&ip, &len);if (cli_fd >= 0) {if ((uint32_t)cli_fd >= get_reactor().max_handler()) {close(cli_fd);continue;}if (!get_reactor().add_cur_connect(get_max_connect())) {printf("client max connect is over \n");close(cli_fd);return true;}SSL* ssl = nullptr;if (isSsl()) {ssl = SSL_new(get_ssl_ctx());if (ssl == nullptr) {get_reactor().sub_cur_connect();close(cli_fd);continue;}printf("accept SSL_new \n");}c_WebSocket*ts = new (std::nothrow) c_WebSocket();if (!ts) {get_reactor().sub_cur_connect();close(cli_fd);continue;}printf("accept client connect \n");ts->start(cli_fd, ip,m_u32channel,m_u32pipeindex,ssl);}else {if (errno == EAGAIN || errno == EINTR || errno == EMFILE || errno == ENFILE) {return true;}else {return false;}}}
}
void c_WebSocket::start(int fd, sockaddr_in& ip, uint32_t channel, uint32_t u32pipeindex,SSL* ssl)
{m_u32channel = channel;m_u32pipeindex = u32pipeindex;m_fd = fd;m_ip = ip;//---------------------------------------------m_lastrecvmsg = g_reactor.getCurSecond();c_heartbeat::GetInstance().handle_input_modify(fd, m_id, m_lastrecvmsg, m_lastrecvmsg);set_noblock(m_fd);m_ssl = ssl;if (isSsl()) {printf("ssl client handshake ready\n");SSL_set_fd(ssl, m_fd);int code, ret;int retryTimes = 0;//   uint64_t begin = 0;//Time::SystemTime();// 防止客户端连接了但不进行ssl握手, 单纯的增大循环次数无法解决问题,while ((code = SSL_accept(ssl)) <= 0 && retryTimes++ < 100) {ret = SSL_get_error(ssl, code);if (ret != SSL_ERROR_WANT_READ) {printf("ssl accept error. sslerror=%d  errno=%d \n", ret,errno); // SSL_get_error(ssl, code));break;}usleep(20 * 1000);//20ms //msleep(1); //这里最多会有2s的等待时间,以后一定要异步}if (code != 1) {handle_fini();return;}printf("ssl client handshake ok (%d)\n", retryTimes);}m_recv_buf_size = default_recv_buff_len;m_recv_buf = (uint8_t*)malloc(m_recv_buf_size);if (!m_recv_buf) {handle_fini();return;}//----------------------------------return;
}

6:心跳检查 10秒(可以自行设定)未收到消息,发送ping,发送2次,没回应 断线

bool c_WebSocket::checcklastmsg(uint32_t t) {if (m_lastrecvmsg + 10 <= t) {if (!is_handshake_ok()) return true;if (m_lastsendping > 0 && m_lastsendping + 10 <= t && m_sendpingcount > 1) {//disconnectprintf("[%u]ready disconnect \n",t);return true;}else if(m_lastsendping == 0  || (m_sendpingcount > 0 && m_lastsendping+10 <=t)){//发送pingm_lastsendping = t;++m_sendpingcount;send_ping_frame();printf("time=%u,%d send ping frame\n", t, m_sendpingcount);}}return false;
}int c_WebSocket::send_ping_frame() {uint32_t length = 0;char               header[14];int                header_size;memset(header, 0, sizeof(header));const bool bhas_fin = true;if (bhas_fin) {msg_set_bit(header, 7);}header[0] |= PING_FRAME & 0x0f;const bool bhas_mask = false; //服务器发送不需要mask,前端给过来才需要if (bhas_mask) {msg_set_bit(header + 1, 7);}header_size = 2;if (length < 126) {header[1] |= length;}uint8_t buf[MAX_PAYLOAD_SEND + 14];memcpy(buf, header, header_size);// memcpy(buf + header_size, msg, msglen);send_pkg(buf, header_size);return header_size;
}
void c_WebSocket::send_pkg(uint8_t* buf, uint32_t len){
//--------------------------------------------------------------
//有上次预留的uint32_t p = 0;int n;if (isSsl()) {n = SSL_write(m_ssl, buf, len);   // 发送响应主体}else {n = send(m_fd, buf, len, 0);}if (n > 0) {if ((uint32_t)n == len) {//printf("send data len = %d need send len=%d \n",n,len);return;}else {p = n;}}else {if (errno != EAGAIN && errno != EINTR) {handle_error();return;}}//没发送完,存起来下次再发送,这里自行处理//----------------------------------------------------------------
}

7:json配置文件读取 jsoncpp API

bool  c_JsonReader::read_json_file(const char* jsonfile)
{
#define  LISTENIP				"listenip"
#define  LISTENPORT				"listenport"
#define  USESSL					"usessl"
#define  PRIVATEKEYFILE			"privatekeyfile"
#define  SERVERCRTFILE			"servercrtfile"
#define  CACRTFILE				"cacrtfile"
#define  AES128KEYHANDSHAKE		"aes128keyhandshake"
#define  AES128IV				"aes128iv"
#define  MAXCONN				"maxconn"
#define  CHECKHEARTBEAT			"checkheartbeat"
#define  OPENBLACKWHITEIP		"openblackwhiteip"
#define  SINGLEIPMAXCONN		"singleipmaxconn"#define  MIN(A,B)  A<B?A:BFILE* f = fopen(jsonfile, "rb");if (f) {const int buf_size = 4 * 1024; char buf[buf_size] = { 0, };memset(buf, 0, sizeof(buf));size_t n = fread(buf, sizeof(char), buf_size, f);fclose(f);if (n < 10) {printf("read_json_file file  length too short \n");return false;}Json::Reader reader;Json::Value root;if (reader.parse(buf,root)) {if (root[LISTENIP].empty() || root[LISTENPORT].empty() || root[MAXCONN].empty()  \|| root[USESSL].empty()  || root[AES128KEYHANDSHAKE].empty() || root[AES128IV].empty()) {printf("read_json_file base fail\n");return  false;}const bool busessl = root[USESSL].asBool();m_chatSerCfg.buseSsl = busessl;if (busessl) { const bool bp = root[PRIVATEKEYFILE].empty();const bool bs = root[SERVERCRTFILE].empty();const bool bc = root[CACRTFILE].empty();if (bp || bs || bc) {printf("read_json_file ssl fail\n");return  false;}strncpy(m_chatSerCfg.szprivatekeyfile, root[PRIVATEKEYFILE].asString().c_str(), MIN(root[PRIVATEKEYFILE].asString().length(), ssl_file_len));strncpy(m_chatSerCfg.szservercrtfile, root[SERVERCRTFILE].asString().c_str(), MIN(root[SERVERCRTFILE].asString().length(), ssl_file_len));strncpy(m_chatSerCfg.szcacrtfile, root[CACRTFILE].asString().c_str(), MIN(root[CACRTFILE].asString().length(), ssl_file_len));}else{}m_chatSerCfg.u16maxconn = (uint16_t)root[MAXCONN].asUInt();strncpy(m_chatSerCfg.szlistenip, root[LISTENIP].asString().c_str(), sizeof(m_chatSerCfg.szlistenip) - 1);m_chatSerCfg.nlistenport = (int32_t)root[LISTENPORT].asInt();memcpy(m_chatSerCfg.u8AES128keyhandshake, root[AES128KEYHANDSHAKE].asString().c_str(), root[AES128KEYHANDSHAKE].asString().length());memcpy(m_chatSerCfg.u8AES128iv, root[AES128IV].asString().c_str(), 16);{//safe configconst bool bcheck = root[CHECKHEARTBEAT].empty();const bool bbwip = root[OPENBLACKWHITEIP].empty();const bool bmaxconn = root[SINGLEIPMAXCONN].empty();if (!bcheck) {m_chatSerCfg.bcheckheartbeat = root[CHECKHEARTBEAT].asBool();}if (bbwip && (bbwip == bmaxconn)) {m_chatSerCfg.u8openblackwhiteip =(uint8_t) root[CHECKHEARTBEAT].asUInt();m_chatSerCfg.u8singleipmaxconn = (uint8_t)root[SINGLEIPMAXCONN].asUInt();}}return true;}}printf("json file no exist or  parse json file fail \n");return false;
}

8:只是帮助分析websocket 协议
红框这边 ssl_accept 是需要优化的,可以考虑用coroutine 或 thread callback
在这里插入图片描述
9: 后续继续优化,差不多,再上demo
如果觉得有用,麻烦点个赞,加个收藏

相关文章:

c++ websocket 协议分析与实现

前言 网上有很多第三方库&#xff0c;nopoll,uwebsockets,libwebsockets,都喜欢回调或太复杂&#xff0c;个人只需要在后端用&#xff0c;所以手动写个&#xff1b; 1:环境 ubuntu18 g(支持c11即可) 第三方库:jsoncpp,openssl 2:安装 jsoncpp 读取json 配置文件 用 自动安装 网…...

kali虚拟机无网络

1.查看虚拟机的网卡模式 在虚拟机设置里&#xff0c;一般选择桥接模式&#xff0c;也可以选择NAT模式。 2、你的IP地址是否写死了&#xff08;设置为静态IP&#xff09; vim编辑模式下的命令&#xff1a; 按a或i进入编辑模式&#xff0c;然后按esc键退出编辑模式&#xff0c;s…...

Unity2023.3(Unity6)版本开始将可以发布WebGPU

翻译一段官网上的话&#xff1a; 利用Unity 2023.3(正式发布时应该称为Unity6)中最新的WebGPU图形API集成&#xff0c;尝试最大限度的提升您的网络游戏的真实感。 通过与谷歌的战略合作&#xff0c;Unity实时3D平台的强大的图形功能现在为图形丰富的网络游戏进行微调&#xff0…...

计算机网络期末考试A卷及答案

一、选择题&#xff08;30分&#xff0c;每题1分&#xff09; 世界上第一个网络系统是( C )。 A、ENIAC B、以太网 C、ARPANET D、DECNET 2&#xff0e;在常用的传输介质中&#xff0c;&#xff08; C &#xff09;的带宽最宽、信号传输衰减最小、抗干扰能力最强。 A.双绞线 …...

<蓝桥杯软件赛>零基础备赛20周--第10周--二分

报名明年4月蓝桥杯软件赛的同学们&#xff0c;如果你是大一零基础&#xff0c;目前懵懂中&#xff0c;不知该怎么办&#xff0c;可以看看本博客系列&#xff1a;备赛20周合集 20周的完整安排请点击&#xff1a;20周计划 每周发1个博客&#xff0c;共20周&#xff08;读者可以按…...

C++友元类,工厂模式和继承的融合案例

//友元没有继承性&#xff0c;没有传递性,所以在animal中定义友元类是无效的class animal{public:animal(){};virtual ~animal(){};};class Cat:public animal{friend class animalFactory;private:Cat(){}private:string m_name;string m_color;public:void about(){cout<&…...

使用 ?? 重新定义逻辑以获得更严格、更安全的 JavaScript 默认值

使用 ?? 重新定义逻辑以获得更严格、更安全的 JavaScript 默认值 JavaScript 中的 ?? 运算符称为 nullish 合并运算符。该运算符接受任一侧的操作数&#xff0c;并且仅当左侧操作数为空值时才返回右侧操作数。这个运算符绝对是一个较新的运算符&#xff0c;它是在 ES2020 …...

Could not initialize class org.codehaus.groovy.vmplugin.v7.Java7

问题描述&#xff1a;Could not initialize class org.codehaus.groovy.vmplugin.v7.Java7 最近在学习如何将YOLO部署在手机端&#xff0c;出现了许多错误&#xff0c;下面这个错误是手机和电脑连结之后&#xff0c;点击run之后出现的错误。 解决办法&#xff1a;将JDK版本将为…...

Python Django Suit:构建现代化的Django后台管理

概要 Django Suit是一款为Django后台管理提供现代、优雅界面的第三方应用&#xff0c;它致力于提升Django开发者的管理体验。本文将深入介绍Django Suit的安装、配置和高级功能&#xff0c;提供详实的示例代码&#xff0c;帮助大家更好地使用和定制Django后台管理界面。 安装与…...

电子学会C/C++编程等级考试2021年09月(六级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:双端队列 定义一个双端队列,进队操作与普通队列一样,从队尾进入。出队操作既可以从队头,也可以从队尾。编程实现这个数据结构。 时间限制:1000 内存限制:65535输入 第一行输入一个整数t,代表测试数据的组数。 每组数据的…...

SpringBoot 源码解析

前言 本文只是纯源码分析文章&#xff0c;阅读者需要有Spring或者SpringBoot使用经验。 SpringBoot 源码解析 SpringBoot 源码解析1&#xff1a;环境搭建 SpringBoot 源码解析2&#xff1a;启动流程1 SpringBoot 源码解析3&#xff1a;启动流程2 SpringBoot 源码解析4&#…...

dockerfile---创建镜像

dockerfile创建镜像&#xff1a;创建自定义镜像。 包扩配置文件的创建&#xff0c;挂载点&#xff0c;对外暴露的端口。设置环境变量。 docker镜像的方式: 1、基于官方源进行创建 根据官方提供的镜像源&#xff0c;创建镜像&#xff0c;然后拉起容器。是一个白板&#xff0c…...

Raspberry PI + Codesys + EtherCAT步进驱动ECR60 Motion功能测试

原文连接&#xff1a;Raspberry PI Codesys EtherCAT步进驱动ECR60 Motion功能测试 – 个人资料收集 (rtplc.com) <div class"post_info_wrapper "> <p class"has-drop-cap">运动控制功能是codesys及EtherCAT通讯的重要功能&am…...

03 Temporal 详细介绍

前言 在后端开发中&#xff0c;大家是否有遇到如下类型的开发场景 需要处理较多的异步事件需要的外部服务可靠性较低需要记录保存某个对象的复杂状态 在以往的开发过程中&#xff0c;可能更多的直接使用数据库、定时任务、消息队列等作为基础&#xff0c;来解决上面的问题。然…...

【算法】【动规】乘积为正数的最长子数组长度

跳转汇总链接 &#x1f449;&#x1f517;算法题汇总链接 1.1 乘积为正数的最长子数组长度 &#x1f517;题目链接 给你一个整数数组 nums &#xff0c;请你求出乘积为正数的最长子数组的长度。 一个数组的子数组是由原数组中零个或者更多个连续数字组成的数组。 请你返回乘积…...

Kubernetes实战(十四)-k8s高可用集群扩容master节点

1 单master集群和多master节点集群方案 1.1 单Master集群 k8s 集群是由一组运行 k8s 的节点组成的&#xff0c;节点可以是物理机、虚拟机或者云服务器。k8s 集群中的节点分为两种角色&#xff1a;master 和 node。 master 节点&#xff1a;master 节点负责控制和管理整个集群…...

Spring之容器:IOC(1)

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…...

【.Net 6.0--通用帮助类--ConvertHelper】

前言 类型转换帮助类&#xff0c;包含下表中的方法&#xff1a; 方法名方法解释ObjToIntobject转intObjToMoneyobject转doubleObjToStringobject转stringObjToDecimalobject转decimalObjToDateobject转datetimeObjToDateSplitYMDobject转datetime&#xff08;yyyy-MM-dd&…...

【加解密】报文签名与加解密,MD5,RSA,AES使用案例(基于 Java)

需要考虑哪些问题&#xff1f; 在进行报文传输时&#xff0c;有两个问题需要考虑&#xff1a; 消息防篡改加密报文 定义消息结构 为了方便后面使用&#xff0c;这里定义消息结构&#xff1a; public static class Message {public String data; //消息public String sign;…...

新建vue3项目

三种方法 一. 第一种方式 1、操作步骤&#xff1a; 创建项目目录 vue create 项目名称选择配置方式 ? Please pick a preset: #选择一个配置 Default &#xff08;[Vue 3] babel, eslint&#xff09;Default &#xff08;[Vue 2] babel, eslint&#xff09;Manually select …...

出现 Error:Unable to access jarfile xxxx\target\nacos-server.jar 解决方法

目录 1. 问题所示2. 原理分析3. 解决方法1. 问题所示 执行Nacos中的startup.cmd的时候出现闪退,于是在该脚本的最后一行添加pause,查看因为什么原因闪退 出现的bug如下所示:Error:Unable to access jarfile xxxx\target\nacos-server.jar 截图如下所示: 查看内部文件夹,…...

记录一次API报文替换点滴

1. 需求 各位盆友在日常开发中&#xff0c;有没有遇到上游接口突然不合作了&#xff0c;临时需要切换其他接口的情况&#xff1f;这不巧了&#xff0c;博主团队近期遇到了&#xff0c;又尴尬又忐忑。 尴尬的是临时通知不合作了&#xff0c;事前没有任何提醒&#xff1b; 忐忑…...

PMP项目管理 - 沟通管理

系列文章目录 PMP项目管理 - 质量管理 PMP项目管理 - 采购管理 PMP项目管理 - 资源管理 PMP项目管理 - 风险管理 现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everything is for the future of dream weaving wings, let the dream fly in…...

fckeditor编辑器改造示例:增加PRE,CODE控件

查看专栏目录 Network 灰鸽宝典专栏主要关注服务器的配置&#xff0c;前后端开发环境的配置&#xff0c;编辑器的配置&#xff0c;网络服务的配置&#xff0c;网络命令的应用与配置&#xff0c;windows常见问题的解决等。 文章目录 修改方法&#xff1a;1&#xff09;修改fckco…...

风速预测(五)基于Pytorch的EMD-CNN-LSTM模型

目录 前言 1 风速数据EMD分解与可视化 1.1 导入数据 1.2 EMD分解 2 数据集制作与预处理 2.1 先划分数据集&#xff0c;按照8&#xff1a;2划分训练集和测试集 2.2 设置滑动窗口大小为96&#xff0c;制作数据集 3 基于Pytorch的EMD-CNN-LSTM模型预测 3.1 数据加载&…...

单元测试二(理论)-云计算2023.12-云南农业大学

文章目录 一、单选题1、三次握手、四次挥手发生在网络模型的哪一层上&#xff1f;2、互联网Internet的拓扑结构是什么&#xff1f;3、以下哪一种网络设备是工作在网络层的&#xff1f;4、以下哪种关于分组交换网络的说法是错误的&#xff1f;5、以下哪种协议是在TCP/IP模型中的…...

QModelIndex 是 Qt 框架中的一个类,用于表示数据模型中的索引位置

QModelIndex 是 Qt 框架中的一个类&#xff0c;用于表示数据模型中的索引位置。 在 Qt 中&#xff0c;数据模型是一种组织和管理数据的方式&#xff0c;常见的数据模型包括 QAbstractItemModel、QStandardItemModel 和 QSqlQueryModel 等。QModelIndex 类提供了一种标识数据模…...

前端实现一个时间区间内,再次单选功能,使用Antd组件库内日历组件Calendar

需求&#xff1a;需要先让用户选择一个时间区间&#xff0c;然后再这个时间区间中&#xff0c;让用户再次去单选其种特殊日期。 思路&#xff1a; 1.先用Antd组件库中日期选择DatePicker.RangePicker实现让用户选择时间区间 2.在选择完时间区间后&#xff0c;用这个时间区间…...

【运维笔记】Hyperf正常情况下Xdebug报错死循环解决办法

问题描述 在使用hyperf进行数据库迁移时&#xff0c;迁移报错&#xff1a; 查看报错信息&#xff0c;错误描述是Xdebug检测到死循环&#xff0c;可是打印的堆栈确实正常堆栈&#xff0c;没看到死循环。 寻求解决 gpt 说的跟没说一样。。 google一下 直接把报错信息粘贴上去…...

嵌入式开发中的总线与时钟

总线 AHB总线 AHB的全称是"Advanced High-performance Bus",中文翻译就是"高级高性能总线"。这是一种在计算机系统中用于连接不同硬件组件的总线架构,它可以帮助这些组件之间高效地传输数据和信息。这个总线架构通常用于处理速度较快且对性能要求较高的…...

建设电子商务网站需要什么设备/seo的工作内容主要包括

2017年5月12日 09:57:48 星期五 最近接触了几天的composer, 不吹不黑, 简单说下用法吧 官方说要先用PHP命令行下载installer, 其实作用就是检测当前的PHP环境是否支持, 再一个就是自动下载composer.phar包 其实可以直接下载composer.phar放到某个地方 怎么跟你的PHP项目结合呢 …...

linux下载wordpress/哔哩哔哩推广网站

印度市场是当下全球前20大智能手机市场当中增长最快的&#xff0c;这让全球手机企业都高度关注该市场。苹果当然也垂涎该市场&#xff0c;不过它似乎并不愿意放弃利润以获取更多市场份额&#xff0c;而转为在印度市场推售发布已有三年时间的iPhone6s&#xff0c;并将在该市场生…...

中学生网站源码/媒体宣传推广方案

硬件工程师跟结构工程师交互的文件&#xff0c;就只有结构图了&#xff0c;也就是PCB板框&#xff0c;这类文件一般是由AutoCAD导出的DWG、DXF文件&#xff0c;当然&#xff0c;也有只给你3D图的&#xff08;如SolidWorks、Pro-E等&#xff09;&#xff0c;让你自己导。 这里以…...

枣庄市庄里水库建设管理处网站/网络视频营销

CPrimer第五版 习题答案 【总目录】&#xff1a;https://blog.csdn.net/Dust_Evc/article/details/114334124 练习4.1 表达式 5 10 * 20 / 2 的求值结果是多少&#xff1f; 105。 练习4.2 根据4.12节中的表&#xff0c;在下述表达式的合理位置添加括号&#xff0c;使得添加括…...

维度网络做网站/苹果cms永久免费建站程序

Easy Scheduler Release 1.0.2 Easy Scheduler 1.0.2是1.x系列中的第三个版本。此版本增加了调度开放接口、worker分组(指定任务运行的机器组)、任务流程及服务监控以及对oracle、clickhouse等支持&#xff0c;具体如下&#xff1a; 新特性&#xff1a; [EasyScheduler-79] 调度…...

wordpress去掉链接中的m/山东seo推广公司

原文 Oracle查看和修改其最大的游标数 以下的文章主要是介绍Oracle查看和修改其最大的游标数&#xff0c;本文主要是通过相关代码的方式来引出Oracle查看和修改其最大的游标数的实际操作步骤&#xff0c;以下就是文章的具体内容的描述&#xff0c;望你在浏览完之后&#xff0c…...