html5-websocket初探

HTML5规范在传统的web交互基础上为咱们带来了众多的新特性,随着web技术被普遍用于web APP的开发,这些新特性得以推广和使用,而websocket做为一种新的web通讯技术具备巨大意义。javascript

什么是socket?什么是websocket?二者有什么区别?websocket是仅仅将socket的概念移植到浏览器中的实现吗?html

咱们知道,在网络中的两个应用程序(进程)须要全双工相互通讯(全双工即双方可同时向对方发送消息),须要用到的就是socket,它可以提供端对端通讯,对于程序员来说,他只须要在某个应用程序的一端(暂且称之为客户端)建立一个socket实例而且提供它所要链接一端(暂且称之为服务端)的IP地址和端口,而另一端(服务端)建立另外一个socket并绑定本地端口进行监听,而后客户端进行链接服务端,服务端接受链接以后双方创建了一个端对端的TCP链接,在该链接上就能够双向通信了,并且一旦创建这个链接以后,通讯双方就没有客户端服务端之分了,提供的就是端对端通讯了。咱们能够采起这种方式构建一个桌面版的im程序,让不一样主机上的用户发送消息。从本质上来讲,socket并非一个新的协议,它只是为了便于程序员进行网络编程而对tcp/ip协议族通讯机制的一种封装。html5

websocket是html5规范中的一个部分,它借鉴了socket这种思想,为web应用程序客户端和服务端之间(注意是客户端服务端)提供了一种全双工通讯机制。同时,它又是一种新的应用层协议,websocket协议是为了提供web应用程序和服务端全双工通讯而专门制定的一种应用层协议,一般它表示为:ws://echo.websocket.org/?encoding=text HTTP/1.1,能够看到除了前面的协议名和http不一样以外,它的表示地址就是传统的url地址。java

能够看到,websocket并非简单地将socket这一律念在浏览器环境中的移植,本文最后也会经过一个小的demo来进一步讲述socket和websocket在使用上的区别。node

websocket的通讯原理和机制git

既然是基于浏览器端的web技术,那么它的通讯确定少不了http,websocket自己虽然也是一种新的应用层协议,可是它也不可以脱离http而单独存在。具体来说,咱们在客户端构建一个websocket实例,而且为它绑定一个须要链接到的服务器地址,当客户端链接服务端的时候,会向服务端发送一个相似下面的http报文程序员

能够看到,这是一个http get请求报文,注意该报文中有一个upgrade首部,它的做用是告诉服务端须要将通讯协议切换到websocket,若是服务端支持websocket协议,那么它就会将本身的通讯协议切换到websocket,同时发给客户端相似于如下的一个响应报文头github

返回的状态码为101,表示赞成客户端协议转换请求,并将它转换为websocket协议。以上过程都是利用http通讯完成的,称之为websocket协议握手(websocket Protocol handshake),进过这握手以后,客户端和服务端就创建了websocket链接,之后的通讯走的都是websocket协议了。因此总结为websocket握手须要借助于http协议,创建链接后通讯过程使用websocket协议。同时须要了解的是,该websocket链接仍是基于咱们刚才发起http链接的那个TCP链接。一旦创建链接以后,咱们就能够进行数据传输了,websocket提供两种数据传输:文本数据和二进制数据。web

基于以上分析,咱们能够看到,websocket可以提供低延迟,高性能的客户端与服务端的双向数据通讯。它颠覆了以前web开发的请求处理响应模式,而且提供了一种真正意义上的客户端请求,服务器推送数据的模式,特别适合实时数据交互应用开发。编程

在websocket以前,咱们在web上要获得实时数据交互都采用了哪些方式?

1)按期轮询的方式:

客户端按照某个时间间隔不断地向服务端发送请求,请求服务端的最新数据而后更新客户端显示。这种方式实际上浪费了大量流量而且对服务端形成了很大压力。

2)comet技术

comet并非一种新的通讯技术,它是在客户端请求服务端这个模式上的一种hack技术,一般来说,它主要分为如下两种作法

(1)基于长轮询的服务端推送技术

具体来说,就是客户端首先给服务端发送一个请求,服务端收到该请求以后若是数据没有更新则并不当即返回,服务端阻塞请求的返回,直到数据发生了更新或者发生了链接超时,服务端返回数据以后客户端再次发送一样的请求,以下所示:

2)基于流式数据传输的长链接

一般的作法是在页面中嵌入一个隐藏的iframe,而后让这个iframe的src属性指向咱们请求的一个服务端地址,而且为了数据更新,咱们将页面上数据更新操做封装为一个js函数,将函数名当作参数传递到这个地址当中,

服务端收到请求后解析地址取出参数(客户端js函数调用名),每当有数据更新的时候,返回对客户端函数的调用,而且将要跟新的数据以js函数的参数填入到返回内容当中,例如返回“<script type="text/javascript">update("data")</script>”这样一个字符串,意味着以data为参数调用客户端update函数进行客户端view更新。基本模型以下所示:

能够看到comet技术是针对客户端请求服务器响应模型而模拟出的一个服务端推送数据实时更新技术。并且因为浏览器兼容性不可以普遍应用。

固然并非说这些技术没有用,就算websocket已经做为规范被提出并实现,可是对于老式浏览器,咱们依然须要将它降级为以上方式来实现实时交互和服务端数据推送。

到此为止,咱们明白了websocket的原理,下面经过一个简单的聊天应用来再次加深下对websocket的理解。

该应用需求很简单,就是在web选项卡中打开两个网页,模拟两个web客户端实现聊天功能。

首先是客户端以下:

client.html

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        .message{
            width: 60%;
            margin: 0 10px;
            display: inline-block;
            text-align: center;
            height: 40px;
            line-height: 40px;
            font-size: 20px;
            border-radius: 5px;
            border: 1px solid #B3D33F;
        }
        .form{
            width:100%;
            position: fixed;
            bottom: 300px;
            left: 0;
        }
        .connect{
            height: 40px;
            vertical-align: top;
            /* padding: 0; */
            width: 80px;
            font-size: 20px;
            border-radius: 5px;
            border: none;
            background: #B3D33F;
            color: #fff;
        }
    </style>
</head>
<body>
<ul id="content"></ul>
<form class="form">
<input type="text" placeholder="请输入发送的消息" class="message" id="message"/>
<input type="button" value="发送" id="send" class="connect"/>
<input type="button" value="链接" id="connect" class="connect"/>
</form>
<script></script>
</body>
</html>

客户端js代码

  var oUl=document.getElementById('content');
    var oConnect=document.getElementById('connect');
    var oSend=document.getElementById('send');
    var oInput=document.getElementById('message');
    var ws=null;
    oConnect.onclick=function(){
        ws=new WebSocket('ws://localhost:5000');
         ws.onopen=function(){
             oUl.innerHTML+="<li>客户端已链接</li>";
         }
        ws.onmessage=function(evt){
            oUl.innerHTML+="<li>"+evt.data+"</li>";
        }
        ws.onclose=function(){
            oUl.innerHTML+="<li>客户端已断开链接</li>";
        };
        ws.onerror=function(evt){
            oUl.innerHTML+="<li>"+evt.data+"</li>";

        };
    };
    oSend.onclick=function(){
        if(ws){
            ws.send(oInput.value);
        }
    }

这里使用的是w3c规范中关于HTML5 websocket API的原生API,这些api很简单,就是利用new WebSocket建立一个指定链接服务端地址的ws实例,而后为该实例注册onopen(链接服务端),onmessage(接受服务端数据),onclose(关闭链接)以及ws.send(创建链接后)发送请求。上面说了那么多,事实上能够看到html5 websocket API自己是很简单的一个对象和它的几个方法而已。

服务端采用nodejs,这里须要基于一个nodejs-websocket的nodejs服务端的库,它是一个轻量级的nodejs websocket server端的实现,实际上也是使用nodejs提供的net模块写成的。

server.js

var app=require('http').createServer(handler);
var ws=require('nodejs-websocket');
var fs=require('fs');
app.listen(80);
function handler(req,res){
    fs.readFile(__dirname+'/client.html',function(err,data){
        if(err){
            res.writeHead(500);
            return res.end('error ');
        }
        res.writeHead(200);
        res.end(data);
    });
}
var server=ws.createServer(function(conn){
    console.log('new conneciton');
    conn.on("text",function(str){
        broadcast(server,str);
    });
    conn.on("close",function(code,reason){
        console.log('connection closed');
    })
}).listen(5000);

function broadcast(server, msg) {
    server.connections.forEach(function (conn) {
        conn.sendText(msg);
    })
}

首先利用http模块监听用户的http请求并显示client.html界面,而后建立一个websocket服务端等待用户链接,在接收到用户发送来的数据以后将它广播到全部链接到的客户端。

下面咱们打开两个浏览器选项卡模拟两个客户端进行链接,

客户端一链接:

请求响应报文以下:

能够看到此次握手和咱们以前讲的一模一样,

客户端二的链接过程和1是同样的,这里为了查看咱们使用ff浏览器,两个客户端的链接状况以下:

发送消息状况以下:

能够看到,双方发送的消息被服务端广播给了每一个和本身链接的客户端。

从以上咱们能够看到,要想作一个点对点的im应用,websocket采起的方式是让全部客户端链接服务端,服务器将不一样客户端发送给本身的消息进行转发或者广播,而对于原始的socket,只要两端创建链接以后,就能够发送端对端的数据,不须要通过第三方的转发,这也是websocket不一样于socket的一个重要特色。

最后,本文为了说明html5规范中的websocket在客户端采用了原生的API,实际开发中,有比较著名的两个库socket.iosockjs,它们都对原始的API作了进一步封装,提供了更多功能,都分为客户端和服务端的实现,实际应用中,能够选择使用。