WebSocket详解:前端js客户端与C++使用mongoose.cpp创建服务端通信示例

一、WebSocket概述

1.1 诞生背景

早期的时候,网站与服务器进行通信与信息获取大都采用轮询方式。只有网站发起请求,服务器才能给与响应。服务器不能主动发起消息。

通过HTTP请求来轮询获取信息的这种形式叫做半双工通信,在获取静态资源时还可以,但是当获取动态信息时,客户端需要不断地轮询以获取结果。比如客户端发起一个请求:“请服务器做一件事”,并且客户端需要知道服务器这件事做完了,客户端才能按顺序去做下一件事。于是客户端需要不停的询问服务端:

Client:“你弄完了吗?”,Server:“没呢!”。

(隔几秒)Client:“你弄完了?”,Server:“没呢!”

(隔几秒)……(不知多少个请求后)

Client:“你弄完了吗?”,Server:“弄完了”

这样频繁的请求无疑占用了很多硬件资源和网络资源,同时由于HTTP请求每次都会携带大量的重复的header信息,也造成了资源的浪费。

可以发现,在这种通信方式下,某些形式时使用起来并不是很方便。为了节省服务器的资源和带宽,WebSocket应运而生。

1.2 简介

Websocket是基于TCP的,使用全双工通信模式的应用层协议。在客户端与服务器连接之后,任何一方都可以主动发出信息。

他与HTTP使用了相同的端口,即80端口和使用TLS时的443端口

使用WebSocket通信,前后端的沟通就变成了:

Client:“hello,服务端你去帮我做一件事,做完跟我说一声哈,我还有别的事情先忙着”

Server:“行,你忙吧,一会我弄完通知你”

……(一段时间过后)

Server:“老客,那事我处理完了哈,给你说一声”

Client:“OKOK”

1.3协议

引用一张图片:
在这里插入图片描述
图片上方的数字代表内存占用,从左到右一共32bit
简单整理一个表格:

字段含义
FIN用于指示当前的 frame 是否结束
RSV在WebSocket扩展时才会使用,不启用时置1
Opcode指示 frame 的类型(文本、二进制、pingpong等)
Mask指示 frame 的数据是否使用掩码掩盖
Payload Len标识数据的长度,其长度也会随着数据长度变化
Masking-keyMask为1时该字段才存在,为32位长度用于覆盖frame
Payloadframe 的数据部分

简单了解一下数据格式后,来看下面这一张图片:
在这里插入图片描述
介绍一下图中的字段:
1.首先可以看到常规信息里的内容:

ws://127.0.0.1:29999/

websocket连接时,关键字为ws,如果使用SSL则为wss。后面跟的是服务器的地址。这里是一个本机ip,29999端口的服务。

请求方法:GET

WebSocket连接时会使用 HTTP GET方法
在这里插入图片描述
连接成功时固定返回
2.再来看一下请求头

Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: tEsrNpPFpJMOcMUxs/a9sA==
Sec-WebSocket-Version: 13

这些都是websocket连接需要增加和注意的字段。
Upgrade: websocket和Connection: Upgrade标识为WebSocket连接,并表示连接要升级
Sec-WebSocket-Extensions 字段是可选的,表示为拓展
Sec-WebSocket-Version: 13标识Websocket的版本
Sec-WebSocket-Key:客户端随机生成的值,转为base64后发送
3.响应头

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Version: 13

Sec-WebSocket-Accept 字段是使用固定的算法将Sec-WebSocket-Key计算后得出的。他不是用来保护数据的,是用来提供基础的防护,减少恶意连接、意外连接的。

二、javascript创建WebSocket客户端

WebSocket.onopen:用于指定连接成功后的回调函数,当WebSocket的连接状态readyState变为“OPEN”时调用
WebSocket.onclose:用于指定连接关闭后的回调函数,当WebSocket的连接状态readyState变为“CLOSED”时被调用
WebSocket.onmessage:用于指定当从服务器接受到信息时的回调函数,当从服务器收到一条消息时,该回调函数将被调用
WebSocket.onerror:用于指定连接失败后的回调函数,定义一个发生错误时执行的回调函数

websocket对象的创建:

var ws;
    function buttonConnnect() {
     if ("WebSocket" in window)
     {
        alert("您的浏览器支持 WebSocket!");
        ws = new WebSocket("ws://127.0.0.1:10010");
         }
     else
     {
        alert("您的浏览器不支持 WebSocket!");
     }
     }

websocket对象的回调函数赋值:

ws.onerror=function (event) {
            alert("error"+event);
        }

也可以以这种格式赋给web的回调函数:

ws.onmessage = OnMessage;
    function OnMessage(msg) {
        try{
            var obj = JSON.parse(msg.data);
            var message = obj.Message;
            alert(message);         
        }
        catch(e){
            alert(msg.data);
        }

    }

示例代码:

    var obj = JSON.parse(msg.data);
    var msgID = obj.msgID;
    var HWPenSign = obj.HWPenSign;
    if (msgID=="0") {
        if (HWPenSign == "HWGetStatus") 
        {
            var DeviceStatus = obj.DeviceStatus;
            if(DeviceStatus==1)
            {
            document.getElementById("Init").removeAttribute("disabled");
            alert("设备已连接");
            }
            else
            alert("设备未连接");
        }
        else if(HWPenSign == "HWInit") 
        {
            var message = obj.message;
            alert(message);
            alert("初始化成功,可以开始签字");
            document.getElementById("StartSign").removeAttribute("disabled");
            document.getElementById("GetSign").removeAttribute("disabled");
            document.getElementById("ClearSign").removeAttribute("disabled");
            document.getElementById("EndSign").removeAttribute("disabled");
        }
        else if (HWPenSign=="HWGetSign") {
            document.getElementById("signimg").src=obj.message;
        }
        else if (HWPenSign=="HWClearSign") {
            clearCanvas();
        }
        else if (HWPenSign=="HWGetPoint") {
            var x = obj.pX;
            var y = obj.pY;
            var s = obj.pStatus;
            addClick(x,y,s);
            draw();
        }
        else
        {
            alert(obj.message);
        }
    }
    else
    {
        alert(obj.message);
    }
    }catch(e){
        alert(msg.data);
    }

}

三、使用mongoose创建服务端

1.概述

Mongoose是C语言网络库,为TCP、UDP、HTTP、WebSocket、CoAP、MQTT实现了事件驱动型的非阻塞api。

这是一个非常轻量的网络库,直接在网上下载mongoose.cpp和mongoose.h,包含到项目里就能用了
2.代码示例


#include "stdafx.h"

#include "mongoose.h" 
static sig_atomic_t s_signal_received = 0; 
static const char *s_http_port = "10086"; 
static struct mg_serve_http_opts s_http_server_opts; 

static void ev_handler(struct mg_connection *nc,
					   int ev,
					   void *ev_data) 
{   
	struct mg_connection *c;   
	char buf[500];   
	char addr[32];   
	mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr),MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);
switch (ev) {     
  case MG_EV_WEBSOCKET_HANDSHAKE_DONE: { //websocket握手事件完成      
	  char addr[32];

	  std::string res_msg="Connect Success!";
	  mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT,res_msg.c_str() , strlen(res_msg.c_str()));          
	  break;     
	 }     
  case MG_EV_WEBSOCKET_FRAME: { //收到websocket数据       
	  struct websocket_message *wm = (struct websocket_message *) ev_data;            
	  struct mg_str d = {(char *) wm->data, wm->size};       
	  mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT,"hello client",strlen("hello client")); 
	  break;     
							  }     
  case MG_EV_HTTP_REQUEST: { //http请求       
	  //http处理   
	  break;     
						   }     
  case MG_EV_CLOSE: {       /* Disconnect. Tell everybody. */       
	  //关闭处理     
	  break;     
	}   
	 }
} 

int main(void) {   
	struct mg_mgr mgr;
	struct mg_connection *nc;

	mg_mgr_init(&mgr, NULL); //创建并初始化事件管理器 

	nc = mg_bind(&mgr, s_http_port, ev_handler);//绑定端口号,设置回调函数               

	mg_set_protocol_http_websocket(nc);     
	s_http_server_opts.document_root = ".";  /
	s_http_server_opts.enable_directory_listing = "yes"; 
 
	while (s_signal_received == 0) {     
		mg_mgr_poll(&mgr, 500); 
	}   
	mg_mgr_free(&mgr); 
	return 0; 
}

代码中有几个结构体和函数需要介绍一下:

struct mg_mgr;///事件管理器,保存所有的活动链接
struct mg_connection;///描述一个链接
struct mbuf;///接收和发送的数据

每次回调调用的函数:

static void ev_handler(struct mg_connection *nc,
					   int ev,
					   void *ev_data) 

对于HTTP请求和WebSocket收到的信息都在这个函数中处理。
其中struct mg_connection *nc,是发送消息用的指针,int ev标识请求类型,void *ev_data里存放的是客户端发来的数据。
mongoose定义了一些宏用于标识区分通信协议和状态。

mg_set_protocol_http_websocket(nc); 这个函数用于支持HTTP与WebSocket

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值