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

  • 相关阅读:
    记第一场省选
    POJ 2083 Fractal 分形
    CodeForces 605A Sorting Railway Cars 思维
    FZU 1896 神奇的魔法数 dp
    FZU 1893 内存管理 模拟
    FZU 1894 志愿者选拔 单调队列
    FZU 1920 Left Mouse Button 简单搜索
    FZU 2086 餐厅点餐
    poj 2299 Ultra-QuickSort 逆序对模版题
    COMP9313 week4a MapReduce
  • 原文地址:https://www.cnblogs.com/yefanqiu/p/2782390.html
Copyright © 2011-2022 走看看