zoukankan      html  css  js  c++  java
  • 【物联网智能网关14】Html5:Canvas+WebSocket实现远程实时通信(下)

    在上篇博文《Html5:Canvas+WebSocket实现远程实时通信(上)》中已经介绍了当前实现动态网页的一些基本技术,也说明了在.NET micro framework平台下实现Web Server需要注意的一些设计原则,本篇文章将继续介绍Canvas和WebSocket实现远程实时通信的技术细节。

    (2)网页动态画面实现(Canvas

     我们采用Dreamweaver软件进行网页和脚本编写(如下图所示) 

    Index.html文件相对比较简单,在<body>中定义一个<canvas>区,声明一定的大小和外观即可,具体的的动态画面我们主要在canvas.js中实现。

    <html>
    
    <head>
    
    <title>MF-Html5</title>
    
    <style>
    
     html,body{font:normal 0.9em arial,helvetica;}
    
    </style>
    
    <script src="canvas.js"  language="javascript" ></script>
    
    </head>
    
    <body onLoad="init()">
    
     <img src="logo.gif" alt="YFSoft"/>
    
     <h3>.NET Micro Framework html5 Demo</h3>
    
     <canvas id="canvas1" style="border:1px solid;background-color:#EEEEEE;" width="440" height="450">
    
     </canvas>
    
     <div><button onClick="quit()">close websocket</button>
    
      <a href="www.sky-walker.com.cn">叶帆科技 www.sky-walker.com.cn</a></div>
    
    </body>
    
    </html>

    canvas.js脚本文件中的init()函数,由于我们在index.html中定义了<body onLoad="init()">,所以网页加载后自动自行这个初始化函数的内容。

    我们再这个初始化函数中获取canvas对象,代码如下:

    canvas = document.getElementById('canvas1');

    获取该对象后,我们就可以定义我们可以绘图的上下文对象了(目前可获取2d对象,3d对象在未来的标准中也会支持)。

    context = canvas.getContext('2d');

    'canvas1' 对应我们网页中的<canvas id="canvas1">。

    有了context这个对象之后,我们就可以在画布上进行诸如,画点、画线、画矩形、画圆和贴图等操作了。(请参见百度百科 Canvas元素)。

    另外我们还支持画面鼠标捕捉,可以绘制一些按钮(不过在一些嵌入式设备上,该功能支持不太好,建议还是直接使用网页按钮)。

    canvas.addEventListener('mousedown',mousedown,false);

    canvas.addEventListener('mousemove',mousemove,false);

    canvas.addEventListener('mouseup',mouseup,false);

    添加这三个事件后,就可以获取鼠标相关信息了。

    详细的代码如下:

    View Code
    var socket;
    
    var canvas;
    
    var context;
    
    var ads=new Array(33);
    
    var ad1;
    
    var showAd1;
    
     
    
    function drawNetworkState(IsLink)
    
    {
    
        if(IsLink==false)
    
           context.fillStyle="#005500";
    
        else
    
           context.fillStyle="#00FF00"; 
    
        context.fillRect(10,10,20,10);
    
     
    
        context.strokeStyle="#000000";    
    
        context.strokeRect(10,10,20,10);
    
    }
    
     
    
    function drawLED(i,IsON)
    
    {
    
        if(IsON==true)
    
           context.fillStyle="#FF0000";     
    
        else
    
           context.fillStyle="#AA0000"; 
    
        context.beginPath();
    
        context.arc(80+130*i,70,30,0,Math.PI*2,true);
    
        context.closePath();
    
        context.fill();
    
     
    
        if(IsON==true)
    
           context.fillStyle="#FFFF00";     
    
        else
    
           context.fillStyle="#888800"; 
    
        context.beginPath();
    
        context.arc(80+130*i+8,53,5,0,Math.PI*2,true);
    
        context.closePath();
    
        context.fill();
    
    }
    
     
    
    function drawButton(i,IsClick)
    
    {
    
         context.fillStyle="#AAAAAA";     
    
         context.fillRect(50+130*i,160,60,50);
    
         if(IsClick==false)
    
           context.strokeStyle="#000000";
    
         else
    
           context.strokeStyle="#EEEEEE";
    
         context.strokeRect(50+130*i,160,60,50);
    
     
    
         if(IsClick==false)
    
           context.strokeStyle="#EEEEEE";
    
         else
    
           context.strokeStyle="#000000";
    
         context.beginPath();
    
         context.moveTo(50+130*i,210);
    
         context.lineTo(50+130*i,160);
    
         context.lineTo(50+130*i+60,160);
    
         context.stroke();
    
    }
    
     
    
    function drawCurve(ad1)
    
    {
    
       for(var i=0;i<32;i++)
    
       {     
    
          ads[i]=ads[i+1];
    
       }
    
       ads[32]=ad1;
    
     
    
       context.fillStyle="#000000";     
    
       context.fillRect(50,250,320,140);
    
       var x=50;
    
       var y=370;
    
         
    
       context.strokeStyle="#00FF00";
    
       context.beginPath();  
    
       context.moveTo(x,y-ads[0]);
    
       for(var i=1;i<33;i++)
    
       {     
    
          context.lineTo(x+i*10,y-ads[i]);
    
       }
    
       context.stroke();
    
     
    
       context.strokeStyle="#FFFF00";
    
       context.beginPath();  
    
       context.moveTo(x,320);
    
       context.lineTo(x+320,320);
    
       context.stroke();
    
    }
    
     
    
    function update()
    
    {  
    
       context.clearRect(50,395,400,30);
    
       context.fillStyle="#000055";
    
       context.font = "9px Arial";
    
       var now= new Date();
    
       var hour=now.getHours();
    
       var minute=now.getMinutes();
    
       var second=now.getSeconds();
    
       context.fillText(hour+":"+minute+":"+second,320,405); 
    
     
    
       //var temp=Math.random()*4096;
    
       //showAd1 = (temp * 3.3 / 4096).toFixed(2);
    
       //ad1 = temp / 40;
    
     
    
       context.fillStyle="#0000FF";
    
       context.font = "9px Arial";
    
       context.fillText("AD1 = " + showAd1 + "V",50,405);
    
     
    
       drawCurve(ad1)
    
       setTimeout(update,500);
    
    }
    
     
    
    function getMousePos(canvas, evt)
    
    {
    
       var obj = canvas;
    
       var top = 0;
    
       var left = 0;
    
       while (obj && obj.tagName != 'BODY')
    
       {
    
            top += obj.offsetTop;
    
            left += obj.offsetLeft;
    
            obj = obj.offsetParent;
    
        }
    
     
    
        var mouseX = evt.clientX - left + window.pageXOffset;
    
        var mouseY = evt.clientY - top + window.pageYOffset;
    
        return {
    
            x: mouseX,
    
            y: mouseY
    
        };
    
    }
    
     
    
    function IsRect(pt,x,y,w,h)
    
    {
    
        if(pt.x>x && pt.y>y  && pt.x<x+w && pt.y<y+h)
    
        {
    
           return true;
    
        }
    
        return false;
    
    }
    
     
    
    function mousedown(evt)
    
    {
    
       var mousePos = getMousePos(canvas, evt);
    
     
    
       for(var i=0;i<3;i++)
    
       {
    
           if(IsRect(mousePos,50+130*i,160,60,50)==true)
    
           {
    
             drawButton(i,true);
    
             send("led"+(i+1).toString()+"=on");
    
             break;
    
           }
    
       }
    
    }
    
     
    
    function mouseup(evt)
    
    {
    
       var mousePos = getMousePos(canvas, evt);
    
       for(var i=0;i<3;i++)
    
       {
    
           if(IsRect(mousePos,50+130*i,160,60,50)==true)
    
           {
    
             drawButton(i,false);
    
             send("led"+(i+1).toString()+"=off");
    
             break;
    
           }
    
       }
    
    }
    
     
    
    function mousemove(evt)
    
    {
    
       var mousePos = getMousePos(canvas, evt);
    
       var message = "Mouse position: " + mousePos.x + "," + mousePos.y;
    
     
    
       context.clearRect(50,0,200,30);
    
       context.fillStyle="#0000FF";
    
       context.font = "9px Arial";
    
       context.fillText(message ,50,15);
    
      
    
    }
    
     
    
    function init()
    
    {
    
       try
    
       {
    
           canvas = document.getElementById('canvas1');
    
           context = canvas.getContext('2d');
    
     
    
           canvas.addEventListener('mousedown',mousedown,false);
    
           //canvas.addEventListener('mousemove',mousemove,false);
    
           canvas.addEventListener('mouseup',mouseup,false);
    
     
    
           drawNetworkState(false);
    
          
    
           for(var i=0;i<3;i++)
    
           {
    
              drawLED(i,false);
    
              drawButton(i,false);
    
            
    
              context.fillStyle="#0000FF";
    
              context.font = "9px Arial";
    
              if(i<2)
    
                context.fillText("LED"+(i+1).toString(10),80+130*i-20,230);
    
              else
    
                context.fillText("Beep",80+130*i-20,230);
    
     
    
              context.fillText("Button"+(i+1).toString(10),80+130*i-20,120);
    
           }  
    
          
    
           for(var i=0;i<33;i++)
    
           {
    
              ads[i]=Math.random()*80+20;
    
           }
    
           drawCurve(0);
    
       }
    
       catch(ex){}
    
      update();
    
    }

    (3)、客户端和服务端实时通信实现(WebSocket

    WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。

    在WebSocket API中,浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

    (详情请参见 维基百科 WebSocket条目)。

    在脚本文件中,我们创建WebSocket的代码如下:

    function init()
    
    {
    
      var host = "ws://192.168.1.100:10189";
    
    try
    
      {
    
        socket = new WebSocket(host);
    
        socket.onopen    = function(msg)
    
        {
    
           drawNetworkState(true);
    
        };
    
        socket.onmessage = function(msg)
    
        {
    
           if(msg.data=="button1=down")
    
           {
    
               drawLED(0,true);
    
           }
    
           else if(msg.data=="button1=up")
    
           {
    
               drawLED(0,false);
    
           }
    
           else if(msg.data=="button2=down")
    
           {
    
               drawLED(1,true);
    
           }
    
           else if(msg.data=="button2=up")
    
           {
    
               drawLED(1,false);
    
           }
    
           else if(msg.data=="button3=down")
    
           {
    
               drawLED(2,true);
    
           }
    
           else if(msg.data=="button3=up")
    
           {
    
               drawLED(2,false);
    
           }
    
           else
    
           {
    
               try
    
               {
    
                  var temp = parseInt(msg.data,10);
    
                  showAd1 = (temp * 3.3 / 4096).toFixed(2);
    
                  ad1 = temp / 40;
    
               }
    
               catch(ex){}
    
           }
    
        };
    
        socket.onclose = function(msg)
    
        {
    
           drawNetworkState(false);
    
        };
    
      }
    
      catch(ex){}
    
    }
    
    function send(msg)
    
    {
    
        try{ socket.send(msg);} catch(ex){  }
    
    }
    
    function quit()
    
    {
    
      socket.close();
    
      socket=null;
    
    }

    在代码中需要指定服务器IP和端口,换句话说,这个实现和我们用其它开发语言开发socket客户端没有任何区别。

    和xmlhttp的区别就是,它不是基于http协议的,传输可以直接是二进制数据。

    在系统中我们只需要构建一个socket server,建立连接后,互发握手指令,成功后,就可以互发数据了。

    3.1 握手

    握手协议旧版和新版有很大的不同,我们这里仅介绍最新版的(也应该是最终版了)。

    打开所提供的Htm5_Websocket.sln工程,确保我们打开websocket库的debugmode(webSocket.DebugMode = true)。

     

    一旦浏览器访问我们的开发板,则串口中会输出上图的信息。

    客户端所发:

    GET / HTTP/1.1

    Upgrade: websocket

    Connection: Upgrade

    Host: 192.168.1.100:10189

    Origin: http://192.168.1.100

    Sec-WebSocket-Key: G9CM/xrVbYUo00RaAuThLQ==

    Sec-WebSocket-Version: 13

    Sec-WebSocket-Extensions: x-webkit-deflate-frame

    服务端返回:

    HTTP/1.1 101 Switching Protocols

    Upgrade: websocket

    Connection: Upgrade

    Sec-WebSocket-Accept: Id0PdUz40uyrpDdq0OwQdkg/ODA=

    WebSocket-Origin: http://192.168.1.100

    WebSocket-Location: ws:192.168.1.100:10189/

    可以看出目前最新版的协议是V13

    握手中最重要的就是key的验证了,服务端发送的key为Sec-WebSocket-Key: G9CM/xrVbYUo00RaAuThLQ==,这是一个base64编码。

    这个key+” 258EAFA5-E914-47DA-95CA-C5AB0DC85B11”的字符串进行SHA1换算后,转换为base64编码,也就是Sec-WebSocket-Accept: Id0PdUz40uyrpDdq0OwQdkg/ODA=中的key。

    然后服务端发送上段内容后,就完成了握手,后续就可以直接收发数据了。

    小插曲:我在.NET Micro Framework平台实现这段代码的过程中遇到两个问题,一是SHA1加密,二是官方提供的base64库有bug。后来,只好自己想办法实现了这两个功能,才完成了websocket的握手验证。

    3.2 数据通信

    新版和老版不同,数据格式定义的比较复杂,如下图所示: 

    具体格式的介绍,可以参见3.4参考中所提到的文章。

    3.3 YFSoft.Html5.websocket封装库

    由以上介绍可知,实现websocket的功能还是相对复杂的,所以我封装了一个websocket库,用户可以非常方便的实现websocket。

    定义:

    webSocket = new WebSocket(10189, 3, 60);

    webSocket.DataReceived += new WebSocketDataReceivedHandler(webSocket_DataReceived);

    webSocket.Run();

    事件处理:

    static void webSocket_DataReceived(WebSocketClient client, WebSocket.Frame frame)
    
    {
    
        string info = frame.Dump();
    
     
    
        if (info == "led1=on")
    
        {
    
            LED1.Write(true);
    
        }
    
        else if (info == "led1=off")
    
        {
    
            LED1.Write(false);
    
        }
    
        else if (info == "led2=on")
    
        {
    
            LED2.Write(true);
    
        }
    
        else if (info == "led2=off")
    
        {
    
            LED2.Write(false);
    
        }
    
        else if (info == "led3=on")
    
        {
    
            LED3.Write(true);
    
        }
    
        else if (info == "led3=off")
    
        {
    
            LED3.Write(false);
    
        }
    
    }

    事件参数中直接可以获取websocket的数据。

    AD数据发送:

    static void ADSend()  //这是一个线程,不断发送AD的值

    {

        while (true)

        {

            webSocket.SendText(ad1.ReadRaw().ToString());

            Thread.Sleep(1000);

        }

    }

    不局限于仅发字符信息,也可以发二进制信息。

    库下载(示例源码+文档):

     http://www.sky-walker.com.cn/MFRelease/library/v42/YFSoft.Html5.WebSocket.rar

    3.4 参考

    websocket 通信协议(已更新到version 13)

    http://www.zendstudio.net/archives/websocket-protocol/

    基于Websocket草案10协议的升级及基于Netty的握手实现

    http://blog.csdn.net/fenglibing/article/details/6852497

    WebSocket新版Hybi-10协议介绍

    http://www.hackecho.com/archives/1104.html

     

    (4)、网页部署和发布

    我们编写的网页文件涉及到三个文件:index.html、canvas.js和LOGO.gif,我们通过YFFileViewer工具把它们部署到开发板中的文件系统中去。

     

    关于YFFileViewer请参见博文博文《【玩转.Net MF –03】远程文件查看器》和《【玩转.Net MF –05】加载文件系统中的Pe文件》。

    仅需要写如下代码,就可以实现web页面发布。

    using (WebServer server = new WebServer(80))

    {

        server.SetWebRoot("\\ROOT\\web");

        Thread.Sleep(Timeout.Infinite);

    }

    程序运行后,我们在浏览器中输入IP地址,就可以访问网页了。 

    相关操作视频: 

    http://v.youku.com/v_show/id_XNDY3NzM1Mzky.html

    声明:由于作者以前主要在桌面程序、通信和嵌入式领域进行开发,对网页开发技术也是最近才开始深入研究,本文描述有失误和偏颇之处还望方家多多指教。

    MF简介:http://blog.csdn.net/yefanqiu/article/details/5711770

    MF资料:http://www.sky-walker.com.cn/News.asp?Id=25

    本文源码:http://www.sky-walker.com.cn/mfrelease/sample/html5_websocket_file.rar

    相关硬件: http://www.sky-walker.com.cn/Products.asp?Id=24

  • 相关阅读:
    python—打开图像文件报错
    CTFshow萌新赛-萌新福利
    微信小程序bug
    微信小程序
    架构
    命令行
    MyBatis
    avalon
    并发测试工具
    less
  • 原文地址:https://www.cnblogs.com/yefanqiu/p/2782390.html
Copyright © 2011-2022 走看看