使用WebSocket构建实时Web应用

使用WebSocket构建实时Web应用

随着时代的进步,传统的网页技术已经无法满足人民群众日益增长的物质文化需求(误)。对于一些特定的需求,如消息推送、在线聊天等,往往受限于BS架构的特性而没有完美的解决方案。好在现今HTML5标准已日渐成熟,现代浏览器大多也实现了对WebSocket 的支持。有了WebSocket,我们就可以构建真正意义上的Web App,实现客户端到服务端的实时通信。

什么是WebSocket?

WebSocket是一套基于TCP的协议,可用于在单个TCP连接上进行全双工通信。虽然设计的时候是为了在浏览器和服务端之间通信,但也可以单独拿出来在任意的客户端和服务端之间通信。WebSocket通过在Http头上加入Upgrade:websocket来进行握手,但之后就和Http没有任何关系了。

WebSocket握手——客户端请求:

GET /chat HTTP/1.1

Host: server.example.com

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Key:x3JJHMbDL1EzLkh9GBhXDw==

Sec-WebSocket-Protocol:chat, superchat

Sec-WebSocket-Version: 13

Origin: http://example.com

 

WebSocket握手——服务端响应:

HTTP/1.1 101 SwitchingProtocols

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Accept:HSmrc0sMlYUkAGmm5OPpG2HaGWk=

Sec-WebSocket-Protocol: chat

注:该例子摘自Wiki—— http://en.wikipedia.org/wiki/WebSocket

什么时候用WebSocket

并非所有的场景都适合用WebSocket来解决问题。对大多数客户端向服务端请求内容的需求来说,使用Http的Request-Response方式仍是最好的解决方案。

真正需要WebSocket的主要是一些是实时性要求较高的场景:如消息通知推送,即时通信等。在没有WebSocket的时代,这类需求往往是通过轮询或长连接来实现的。轮询的问题是频繁的请求会无意义地消耗大量网络带宽,而轮询周期过长又会影响实时性。长连接则充斥了黑科技的味道,违背了HTTP协议的设计初衷,而且会占用大量的服务器资源。有了WebSocket之后,这类问题终于迎刃而解。

NodeJS方案

虽然很多平台都对WebSocket提供了支持,但是对于需要同时维持大量客户端连接的场景来说,基于单进程异步调用的NodeJS是一个非常合适的解决方案。在服务器安装了NodeJS环境以后,只需要在项目中执行npm install nodejs-websocket,就可以实现对该协议的支持。

首先,让我们来看一个简单的例子:

服务端代码示例(摘自https://www.npmjs.com/package/nodejs-websocket):

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

var server =ws.createServer(function (conn) {

    console.log("New connection");

    conn.on("text", function (str) {

        console.log("Received " + str);

        conn.sendText(str.toUpperCase() + "!!!");

    })

    conn.on("close", function (code,reason) {

        console.log("Connectionclosed");

    })

}).listen(8001);

客户端(网页)代码示例

<html>

<header>

</header>

<body>

<scripttype="text/javascript">

var wsServer ='ws://localhost:8001';

var webSocket = newWebSocket(wsServer);

webSocket.onopen = function(evt) { onOpen(evt) };

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 WebSocketserver.");

   webSocket.send("hello");

}

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);

}

</script>

</body>

</html>

该例子的执行逻辑如下:

客户端页面加载之后,便会同服务端建立WebSocket连接=》服务端收到连接后,便会触发createServer的回调=》客户端连接建立成功后,触发onOpen事件,通过webSocket.send()向服务端发送文本=》服务端收到文本触发conn.on(“text”)事件,并使用conn.sendText向客户端推送文本=》客户端收到该文本,触发onMessage事件。

从本例可以看出,通过WebSocket,客户端和服务端可以轻易地实现双向实时通信。

注:服务端可以通过conn.path对url进行检查,从而复用同一个端口实现多个接口。

一个简单的通信框架

由于WebSocket提供的仅是最底层网络通信的支持,直接在其之上编写实际应用绝对不是一个好主意。以下的设计对其进行了简单的封装,从而将WebSocket的底层实现同业务逻辑进行分离,并实现了简单的用户及连接管理。

协议设计

使用JSON作为消息格式。

服务端发往客户端的消息:

{

    action: “alert”,

    message: “hello”

}

其中action是必选项,表明操作的类型,由客户端注册的相应的消息处理器来处理。

客户端发往服务端的消息:

{

    action: “register”,

    user_id: 123456

}

其中action和user_id是必选项。action表明操作的类型,由服务端注册的相应的消息处理器来处理。user_id为识别用户的唯一id,用于判断消息来源。

客户端在建立WebSocket连接后,需要向服务端发送register消息,并附上自己的userId。

服务端设计

模块

ServiceModule

描述

封装WebSocket底层逻辑

方法

startService

封装NodeJS-WebSocket中的ws.createServer函数,用于启动WebSocket监听

sendMessage

通过指定WebSocket连接发送消息到客户端

registerHandler

注册消息处理器,格式为handler(conn, request)

 

模块

UserManager

描述

维护用户/WebSocket连接列表

方法

getUsers

获取当前活跃用户

sendMessageToUser

向指定用户发送消息

 

 

 

初始化时,UserManager会在ServiceModule中注册action为register的消息处理器。一旦有新用户注册,便会触发该处理器,从而将该用户及其对应的WebSocket连接加入列表维护。需要向某个用户发送消息时,则通过该列表查找到对应的WebSocket连接,通过ServiceModule向客户端发送消息。

注1:如果只是用于实现消息推送,服务端仅处理register消息便足够了。若需要实现更复杂的逻辑,可以考虑添加一个用户纬度的ServiceModule,封装原ServiceModule和UserManager,支持形如handler(userId, request)的消息处理函数。

注2:如果不允许一个用户建立多个连接,可以在register消息处理器中断开之前的连接。

客户端设计

模块

WSAgent

描述

封装WebSocket底层逻辑

方法

sendMessage

向服务端发送消息

registerHandler

注册消息处理器,格式为handler(request)

 

 

 

客户端无需维护用户列表,因此只需单个模块即可提供所需的封装。WSAgent初始化时需要输入服务端url和userId作为参数,从而同服务端建立WebSocket连接,并发送register消息。收到服务端消息后,则调用相应的消息处理器对其进行处理。

可以改进的点

上述框架仅提供了一个初级的双向通信解决方案,无法得知消息发送是成功还是失败。可以根据具体需求进行进一步优化。

对于推送服务来说,只需在此基础上添加推送相关的消息即可。如果需要推送到达通知,可以为推送消息指定唯一id。客户端收到推送消息后,向服务端发送带有该id的回执。服务端即可更新消息推送状态。

对于即时通信来说则更为复杂一些,需要分别在客户端和服务端跟踪消息的发送、接收情况。

另外就是需要获取服务端信息的场景。一种做法是直接在WebSocket上实现类似传统Http协议的Request-Response机制;还有一种做法对于这种情况通通走Http。前者可自定义协议,且无需重新建立连接,性能更好;后者则更为通用,实现起来较简单。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值