websocket实时聊天系统韦德娱乐1946网页版

2019-05-08 11:35 来源:未知

介绍下websocket:

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

webSocket协议本质上是一个基于tcp的协议;

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

建立一个websocket连接,大体的过程:

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

1.客户端浏览器首先向服务器发起一个http请求,这个请求和平常的请求有什么不同呢?

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

多了一点附加头信息:"upgrade:web Socket” 表明我这申请的是一个websocket的http请求;

可以看到,websocket并不是简单地将socket这一概念在浏览器环境中的移植,本文最后也会通过一个小的demo来进一步讲述socket和websocket在使用上的区别。

2.服务器收到请求后,解析这些附加的头信息,然后产生应答信息返回给客户端,这样,连接就建立了;

websocket的通信原理和机制

3.双方就可以通过这个连接通道自由的信息传递,这个连接会一直存在,直到一方自动关闭连接;

既然是基于浏览器端的web技术,那么它的通信肯定少不了http,websocket本身虽然也是一种新的应用层协议,但是它也不能够脱离http而单独存在。具体来讲,我们在客户端构建一个websocket实例,并且为它绑定一个需要连接到的服务器地址,当客户端连接服务端的时候,会向服务端发送一个类似下面的http报文

 

韦德娱乐1946网页版 1

客户端到服务端:

GET /demo HTTP/1.1

Host: example.com

Connection: Upgrade

Sec-WebSocket-Key2: 12998 5 Y3 1 .P00

Upgrade: WebSocket

Sec-WebSocket-Key1: 4@1 46546xW%0l 1 5

Origin: http://example.com

[8-byte security key]



服务端到客户端:

HTTP/1.1 101 WebSocket Protocol Handshake

Upgrade: WebSocket

Connection: Upgrade

WebSocket-Origin: http://example.com

WebSocket-Location: ws://example.com/demo

[16-byte hash response]

可以看到,这是一个http get请求报文,注意该报文中有一个upgrade首部,它的作用是告诉服务端需要将通信协议切换到websocket,如果服务端支持websocket协议,那么它就会将自己的通信协议切换到websocket,同时发给客户端类似于以下的一个响应报文头

从客户端到服务端请求的信息里面包含:‘Sec-webSocket-key1","Sec-WebSocket-key2"和“[8-byte security key]”这样的信息;这是客户端浏览器需要向服务端提供的握手信息,服务端解析这些头信息,并且在握手的过程中依据这些信息生成一个16位的安全密钥并返回给客户端,以表明服务器端获取了客户端的请求;

韦德娱乐1946网页版 2

大致步骤:

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

1. 逐个字符读取 Sec-WebSocket-Key1 头信息中的值,将数值型字符连接到一起放到一个临时字符串里,同时统计所有空格的数量;
2. 将在第 1 步里生成的数字字符串转换成一个整型数字,然后除以第 1 步里统计出来的空格数量,将得到的浮点数转换成整数型;
3. 将第 2 步里生成的整型值转换为符合网络传输的网络字节数组;
4. 对 Sec-WebSocket-Key2 头信息同样进行第 1 到第 3 步的操作,得到另外一个网络字节数组;
5. 将 [8-byte security key] 和在第 3,第 4 步里生成的网络字节数组合并成一个 16 字节的数组;
6. 对第 5 步生成的字节数组使用 MD5 算法生成一个哈希值,这个哈希值就作为安全密钥返回给客户端,以表明服务器端获取了客户端的请求,同意创建 WebSocket 连接

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

 

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

 var  wsServer = 'ws://localhost:8888/Demo';  //连接地址
 var  websocket = new WebSocket(wsServer);   //建立连接
 websocket.onopen = function (evt) { onOpen(evt) };  //4个事件
 websocket.onclose = function (evt) { onClose(evt) }; 
 websocket.onmessage = function (evt) { onMessage(evt) }; 
 websocket.onerror = function (evt) { onError(evt) }; 
 function onOpen(evt) { 
 console.log("Connected to WebSocket server."); 
 } 
 function onClose(evt) { 
 console.log("Disconnected"); 
 } 
 function onMessage(evt) { 
 console.log('Retrieved data from server: '   evt.data); 
 } 
 function onError(evt) { 
 console.log('Error occured: '   evt.data); 
 }

1)定期轮询的方式:

   浏览器的支持情况:

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

浏览器 支持情况
Chrome Supported in version 4 
Firefox Supported in version 4 
Internet Explorer Supported in version 10 
Opera Supported in version 10 
Safari Supported in version 5 

2)comet技术

 正文来了:基于websocket制作的简单聊天系统;

comet并不是一种新的通信技术,它是在客户端请求服务端这个模式上的一种hack技术,通常来讲,它主要分为以下两种做法

client.html:

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

 <style>
        .kuang {
            width: 600px;
            min-height: 50px;
            max-height: 296px;
            border: 1px solid;
            float: left;
            display: block;
            position: relative;
            overflow-y: scroll;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="row">
              <div class="jumbotron  bg-dark">
                <h1 class="jumbotron-heading">WebSocket chat,欢迎使用:</h1>
              </div>
            <div class="input-group text-left">
                <label>输入用户名:</label>
                <input type="text" id="name" />
                <button id="conn">连接</button>
                <button id="close">断开</button>
            </div>
            <div class="input-group text-muted">
                <div class="kuang" id="mess"></div>
            </div>
         <hr class="featurette-divider">
            <div class="input-group text-left">
                <input type="text" class="value" id="value1" />
                <button id="send">发送</button>
            </div>
        </div>
    </div>

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

 简单的界面,大致效果就是这样的:

韦德娱乐1946网页版 3

韦德娱乐1946网页版 4

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

然后实现逻辑代码:

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

 var ws = new WebSocket('ws://127.0.0.1:8082');
                ws.onopen = function (e) {
                    console.log("连接服务器成功");
                }
                ws.onmessage = function (e) {
                    value1.removeAttribute("readOnly");
                    var time = new Date();
                    mess.innerHTML  = time.toUTCString()   ":"   e.data   "<br>";
                    document.getElementById("send").onclick = function (e) {
                        ws.send(input.value   "说:"   value1.value);
                        value1.value = " ";
                    }
                    document.onkeydown = function (e) {
                        e = e || window.event;
                        if (e.keyCode == 13) {
                            document.getElementById("send").onclick();
                            return false;
                        }
                    }
                }
                ws.onclose = function (e) {
                    console.log("服务器关闭");
                }
                ws.onerror = function () {
                    console.log("连接出错");
                }

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

 连接地址:ws://127.0.0.1:8082  那是哪里来的呢?  (注意http请求则是写成

韦德娱乐1946网页版 5

wbsocket只是客服端,地址当然是从我们的服务端给的呀;

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

服务端的搭建采用了一个这样的库:

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

nodejs-websocket

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

 

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

1.npm isntall -g nodejs-websocket

首先是客户端如下:

2.在js页面引入它 

client.html

var ws = require("nodejs-websocket");

<!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>

3.创建一个服务

客户端js代码

var server = ws.createServer(function (conn) {
    conn.on('text', function (str) {
    })

    conn.on("close", function (code, reason) {
        console.log("关闭连接");
    })
    conn.on("error", function (code, reason) {
        console.log("异常关闭");
    });
}).listen(8082);
console.log("websocket连接完毕")
  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);          }      }

 好了,websocket连接算是建立啦!

这里使用的是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模块写成的。

client.html

server.js

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <link rel="stylesheet" type="text/css" href="bootstrap-3.3.7-dist/css/bootstrap.min.css" />
    <script src="jquery.min.js"></script>
    <script src="bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
    <style>
        .kuang {
            width: 600px;
            min-height: 50px;
            max-height: 296px;
            border: 1px solid;
            float: left;
            display: block;
            position: relative;
            overflow-y: scroll;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="row">
              <div class="jumbotron  bg-dark">
                <h1 class="jumbotron-heading">WebSocket chat,欢迎使用:</h1>
              </div>
            <div class="input-group text-left">
                <label>输入用户名:</label>
                <input type="text" id="name" />
                <button id="conn">连接</button>
                <button id="close">断开</button>
            </div>
            <div class="input-group text-muted">
                <div class="kuang" id="mess"></div>
            </div>
         <hr class="featurette-divider">
            <div class="input-group text-left">
                <input type="text" class="value" id="value1" />
                <button id="send">发送</button>
            </div>
        </div>
    </div>
    <script>
        var input = document.getElementById("name");
        var conn = document.getElementById("conn");
        var close = document.getElementById("close");
        var mess = document.getElementById("mess");
        var value1 = document.getElementById("value1");
        var pattern = /^[u4e00-u9fa5]{2,10}$/;
        close.disabled = true;
        if (window.WebSocket) {
            conn.onclick = function () {
                if (!pattern.test(input.value)) {
                    alert("名称不能为空且必须为中文");
                    return;
                }
                var ws = new WebSocket('ws://127.0.0.1:8082');
                conn.disabled = true;
                close.disabled = false;
                ws.onopen = function (e) {
                    console.log("连接服务器成功");
                    ws.send(input.value);
                    input.setAttribute("readOnly", 'true');
                    value1.setAttribute("readOnly", 'true');
                }
                ws.onmessage = function (e) {
                    value1.removeAttribute("readOnly");
                    var time = new Date();
                    mess.innerHTML  = time.toUTCString()   ":"   e.data   "<br>";
                    document.getElementById("send").onclick = function (e) {
                        ws.send(input.value   "说:"   value1.value);
                        value1.value = " ";
                    }
                    document.onkeydown = function (e) {
                        e = e || window.event;
                        if (e.keyCode == 13) {
                            document.getElementById("send").onclick();
                            return false;
                        }
                    }
                }
                ws.onclose = function (e) {
                    console.log("服务器关闭");
                }
                ws.onerror = function () {
                    console.log("连接出错");
                }

                close.onclick = function () {
                    ws.onclose();
                    ws.send(input.value   'close'   "了连接");
                    input.removeAttribute("readOnly");
                    conn.disabled = false;
                    close.disabled = true;
                }
            }
        }
    </script>
</body>

</html>
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);      })  }

 server.js

首先利用http模块监听用户的http请求并显示client.html界面,然后创建一个websocket服务端等待用户连接,在接收到用户发送来的数据之后将它广播到所有连接到的客户端。

var ws = require("nodejs-websocket");
console.log("开始建立连接...");
var str1 = null, str2 = null, clientReady = false, serverReady = false;
var a = [];
var server = ws.createServer(function (conn) {
    conn.on('text', function (str) {
          a.push(str);
        if (!clientReady) {
            if (a[0] === str) {
                str1 = conn;
                clientReady = true;
                str1.sendText("欢迎你"   str);

            }
        } else if (!serverReady) {
            if (str.indexOf('close') >= 0) {    
                     a.splice(2,1);
                     clientReady = false;
                     str1=null;   
                     return;
                }
            if (a[1] === str) {
                str2 = conn;
                serverReady = true;
                str2.sendText("欢迎你"   str);
                str1.sendText(str   "在线啦,你们可以聊天啦");
                return;
            } 
        } else if (clientReady && serverReady) {
                str2.sendText(str);
                str1.sendText(str);
                if (str.indexOf('close') >= 0) {
                    a.splice(2, a.length);
                    var len = a.length;
                    for (var i = 0; i < len; i  ) {
                        // 定位该元素位置
                        if (str.indexOf(a[i])>=0) {     
                           a.splice(i,1);
                           if(i==0){
                               str1=str2;
                           }
                           serverReady = false; 
                           str2=null;
                           return;
                        }

                    } 
                }
            }  


    })

    conn.on("close", function (code, reason) {
        console.log("关闭连接");
        clientReady = false;
        serverReady = false;
    })
    conn.on("error", function (code, reason) {
        console.log("异常关闭");
    });
}).listen(8082);
console.log("websocket连接完毕")

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

 实现双人聊天,client.html开启两个窗口就行!

客户端一连接:

 详细代码在github上:

韦德娱乐1946网页版 6

 https://github.com/sulishibaobei/websocket-

请求响应报文如下:

 

韦德娱乐1946网页版 7

可以看到这次握手和我们之前讲的如出一辙,

客户端二的连接过程和1是一样的,这里为了查看我们使用ff浏览器,两个客户端的连接情况如下:

韦德娱乐1946网页版 8

发送消息情况如下:

韦德娱乐1946网页版 9

可以看到,双方发送的消息被服务端广播给了每个和自己连接的客户端。

从以上我们可以看到,要想做一个点对点的im应用,websocket采取的方式是让所有客户端连接服务端,服务器将不同客户端发送给自己的消息进行转发或者广播,而对于原始的socket,只要两端建立连接之后,就可以发送端对端的数据,不需要经过第三方的转发,这也是websocket不同于socket的一个重要特点。

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


TAG标签: 韦德娱乐1946
版权声明:本文由韦德娱乐1946_韦德娱乐1946网页版|韦德国际1946官网发布于韦德娱乐1946网页版,转载请注明出处:websocket实时聊天系统韦德娱乐1946网页版