Html5 websocket浅析

初次接触websocket,究竟它与http协议有何不同,
HTTP 协议有一个缺陷:通信只能由客户端发起,HTTP 协议做不到服务器主动向客户端推送信息。
这样说,如果我想服务器定时推送消息,比如说天气预报,每隔一段时间就会变化,如何在客户端获取最新信息。
之前是使用"轮询":每隔一段时候,就发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室。
缺点:轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。

websocket最大的特点:服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息。

  • 数据格式比较轻量,性能开销小,通信高效。
  • 可以发送文本,也可以发送二进制数据。
  • 没有同源限制,客户端可以与任意服务器通信。

废话不多讲,直接上代码:

var ws = new WebSocket("[wss://echo.websocket.org](wss://echo.websocket.org/)");
ws.onopen = function(evt) {
  console.log("Connection open ...");
  ws.send("Hello WebSockets!");
};
ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  ws.close();
};
ws.onclose = function(evt) { 
 console.log("Connection closed.");
}; 

下面我们来分析会怎么输出:
1.onopen建立连接后输出connection open。
2.send发送一条消息,在onmessage事件里监听,evt.data输出 hello websockets。
3.close关闭连接,最后输出connection closed。

websocket状态

webSocket.readyState

CONNECTING:值为0,表示正在连接。
OPEN:值为1,表示连接成功,可以通信了。
CLOSING:值为2,表示连接正在关闭。
CLOSED:值为3,表示连接已经关闭,或者打开连接失败。

我们可以在这些特殊时刻做许多事情

switch (ws.readyState) {
  case WebSocket.CONNECTING:
    // do something
    break;
  case WebSocket.OPEN:
    // do something
    break;
  case WebSocket.CLOSING:
    // do something
    break;
  case WebSocket.CLOSED:
    // do something
    break;
  default:
    // this never happens
    break;
}

我们可以给每个事件添加多个函数:

ws.addEventListener('open', function (event) {
   console.log('socket open')
});

ws.addEventListener('open', function (event) {
   ws.send('Hello Server!');
});

代码示例

这样吧,我们下面给出一段示例代码,更能说明问题。
1.下面是一个点击切换的事件,控制socket的链接与释放。

function ToggleConnectionClicked() {
         if (SocketCreated && (ws.readyState == 0 || ws.readyState == 1)) {  
               ws.close();
           } else {
               Log("准备连接到聊天服务器 ...");
               try {
                ws = 
                new WebSocket("ws://" + document.getElementById("Connection").value);
                 SocketCreated = true;
               } catch (ex) {
                 Log(ex, "ERROR");
                 return;
               }
               document.getElementById("ToggleConnection").innerHTML = "断开";
               ws.onopen = WSonOpen;
               ws.onmessage = WSonMessage;
               ws.onclose = WSonClose;
               ws.onerror = WSonError;
           }
       };

2.各种事件函数。

function WSonOpen() {
           Log("连接已经建立。", "OK");
           $("#SendDataContainer").show("slow");  //消息发送窗口显示
       };
 
       function WSonMessage(event) {
           Log(event.data);            
       };
 
       function WSonClose() {
           Log("连接关闭。", "ERROR");
           document.getElementById("ToggleConnection").innerHTML = "连接";
           $("#SendDataContainer").hide("slow");
       };
 
 
       function WSonError() {
           Log("WebSocket错误。", "ERROR");
       };

3.当用户按下发送按钮,客户端会调用WebSocket对象向服务器发送信息,并且这个消息会广播给所有的用户

function SendDataClicked()
 {
            if (document.getElementById("DataToSend").value != "") {
                ws.send(document.getElementById("txtName").value + "说 :\"" + 
document.getElementById("DataToSend").value + "\"");
                document.getElementById("DataToSend").value = "";
            }
        };

数据类型

注意,服务器数据可能是文本,也可能是二进制数据(blob对象或Arraybuffer对象)。

ws.onmessage = function(event){
  if(typeof event.data === String) {
    console.log("Received data string");
  }

  if(event.data instanceof ArrayBuffer){
    var buffer = event.data;
    console.log("Received arraybuffer");
  }
}

除了动态判断收到的数据类型,也可以使用binaryType属性,显式指定收到的二进制数据类型。

// 收到的是 blob 数据
ws.binaryType = "blob";
ws.onmessage = function(e) {
  console.log(e.data.size);
};

// 收到的是 ArrayBuffer 数据
ws.binaryType = "arraybuffer";
ws.onmessage = function(e) {
  console.log(e.data.byteLength);
};

可能有同学对blob和arraybuffer(二进制数组)不是很了解,这里我简单讲一下arraybuffer的用法。
ArrayBuffer对象代表储存二进制数据的一段内存,它不能直接读写,只能通过视图(TypedArray视图和DataView视图)来读写,视图的作用是以指定格式解读二进制数据。

var buf = new ArrayBuffer(32);

上面代码生成了一段32字节的内存区域,每个字节的值默认都是0。可以看到,ArrayBuffer构造函数的参数是所需要的内存大小(单位字节)。

为了读写这段内容,需要为它指定视图。DataView视图的创建,需要提供ArrayBuffer对象实例作为参数。

var buf = new ArrayBuffer(32);
var dataView = new DataView(buf);
dataView.getUint8(0) // 0

上面代码对一段32字节的内存,建立DataView视图,然后以不带符号的8位整数格式,读取第一个元素,结果得到0,因为原始内存的ArrayBuffer对象,默认所有位都是0。

ArrayBuffer有一个静态方法isView,返回一个布尔值,表示参数是否为ArrayBuffer的视图实例。这个方法大致相当于判断参数,是否为TypedArray实例或DataView实例。

var buffer = new ArrayBuffer(8);
ArrayBuffer.isView(buffer) // false

var v = new Int32Array(buffer);
ArrayBuffer.isView(v) // true

大家对这段感兴趣的话,可以点 这里.

下面展示了几种数据的发送:

// Sending String
connection.send('your message');

// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(100, 100, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
  binary[i] = img.data[i];
}
connection.send(binary.buffer);

// Sending file as Blob
var file = document.querySelector('input[type="file"]').files[0];
connection.send(file);

我们可以截取canvas绘画的图像某一部分,通过二进制数组发送给服务器。

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.rect(10, 10, 100, 100);
ctx.fill();

console.log(ctx.getImageData(50, 50, 100, 100));
// ImageData { width: 100, height: 100, data: Uint8ClampedArray[40000] }

下面分享几个小tip:
1.How to get the IP address of the client?

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', function connection(ws, req) {
  const ip = req.connection.remoteAddress;
});

获取远程客户端ip。

2.How to detect and close broken connections?

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

function heartbeat() {
  this.isAlive = true;
}

wss.on('connection', function connection(ws) {
  ws.isAlive = true;
  ws.on('pong', heartbeat);
});

const interval = setInterval(function ping() {
  wss.clients.forEach(function each(ws) {
    if (ws.isAlive === false) return ws.terminate();

    ws.isAlive = false;  // 断开
    ws.ping('', false, true);   //重新ping  如果可以ping通,会接受pong
  });
}, 30000);

上面这个例子检测了与服务器通信的客户端,若是有链接异常的会被terminate。

3.How to send broadcast to all connected client

const server = http.createServer(app);
    const wss = new WebSocket.Server({ server });

   wss.on('connection', function connection(ws) {
     ws.on('message', function(message) {
       wss.broadcast(message);
     }
   }

   wss.broadcast = function broadcast(msg) {
     console.log(msg);
     wss.clients.forEach(function each(client) {
       client.send(msg);  //这个有所疑问 为什么是send 这个时候不该是receive吗 
     });
    };

    server.listen(8080, function listening() {
      console.log('Listening on %d', server.address().port);
    });

上面是服务器接收了一个信息后,将该信息发送给所有与之建立连接的client。或许下面这个更适合。

var ws = require("ws");

  global_counter = 0;
  all_active_connections = {};

  ws.createServer(function (websocket) 
  {
      websocket.on('connect', function() 
      {
          var id = global_counter++;
          all_active_connections[id] = websocket;
          websocket.id = id; 
      }).on('message', function (data) {
          if (data == 'broadcast me!')
          {
              for (conn in all_active_connections)
                 all_active_connections[conn].write(data);  // 这里用的write
          }       
      }
    }).on('close', function() {
        delete all_active_connections[websocket.id];
    });
  }).listen(8080);

今天关于websocket的知识就讲解到这里,其实有很多资源可以去学习,socket.io也是一个很好的学习途径,如果有空我会做一期node.js配合socket的文章。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容

  • 二进制数组(ArrayBuffer对象、TypedArray视图和DataView视图)是JavaScript操作...
    呼呼哥阅读 21,118评论 2 12
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,103评论 18 139
  • 壹:读书无用论 我们很容易相信自己听到看到的就是真实的,比如现在中国有大批的大学毕业生出来找工作好难,在大学也盛传...
    夜语山林阅读 583评论 1 1
  • 依稀记得印度电影最著名的洗脑音乐——大篷车里面的“阿~巴~拉~~古”。这一次又有一段旋律在脑子里挥之不去了,那充满...
    清灵_简书阅读 606评论 0 51
  • 简书,挺简洁的。可惜现在的我已经不喜欢写文章了。
    amoore阅读 116评论 0 1