介绍

WebSocket是一种在单个TCP连接上进行全双工通信的协议, WebSocket通信协议于2011年被IETF定为标准RFC 6455并由RFC7936补充规范.

WebSocket使得客户端和服务器之间的数据交换变得更加简单, 使用WebSocket的API只需要完成一次握手就直接可以创建持久性的连接并进行双向数据传输.

WebSocket支持的客户端不仅限于浏览器(Web应用), 在现今应用市场内的众多App客户端的长连接推送服务都有一大部分是基于WebSocket协议来实现交互的.

Websocket由于使用HTTP协议升级而来, 在协议交互初期需要根据正常HTTP协议交互流程. 因此, Websocket也很容易建立在SSL数据加密技术的基础上进行通信.

协议

WebSocket与HTTP协议实现类似但也略有不同. 前面提到: WebSocket协议在进行交互之前需要进行握手, 握手协议的交互就是利用HTTP协议升级而来.

众所周知, HTTP协议是一种无状态的协议. 对于这种建立在请求->回应模式之上的连接, 即使在HTTP/1.1的规范上实现了Keep-alive也避免不了这个问题.

所以, Websocket通过HTTP/1.1协议的101状态码进行协议升级协商, 在服务器支持协议升级的条件下将回应升级请求来完成HTTP->TCP协议升级.

原理

客户端将在经过TCP3次握手之后发送一次HTTP升级连接请求, 请求中不仅包含HTTP交互所需要的头部信息, 同时也会包含Websocket交互所独有的加密信息.

当服务端在接受到客户端的协议升级请求的时候, 各类Web服务实现的实际情况, 对其中的请求版本、加密信息、协议升级详情进行判断. 错误(无效)的信息将会被拒绝.

在两端确认完成交互之后, 双方交互的协议将会从抛弃原有的HTTP协议转而使用Websocket特有协议交互方式. 协议规范可以参考RFC文档.

cf框架提供了websocket client与server库.

API

websocket server

首先, 创建一个Websocket的class. 如下所示:

  1. local class = require "class"
  2. local websocket = class('ws')

WebSocket:ctor(opt)

初始化Websocket对象, Websocket客户端连接建立完成之前被调用.

此方法在on_open方法之前被调用, 一般用于告诉httpd应该如何怎么进行数据包交互.

  1. function websocket:ctor (opt)
  2. self.ws = opt.ws -- websocket对象
  3. self.send_masked = false -- 掩码(默认为false, 不建议修改或者使用)
  4. self.max_payload_len = 65535 -- 最大有效载荷长度(默认为65535, 不建议修改或者使用)
  5. end

WebSocket:on_open()

当有连接初始化完成之后此方法会被调用. 此方法虽然与Websocket:ctor类似, 但一般在仅用于内部服务初始化的时候使用.

  1. function websocket:on_open()
  2. local cf = require "cf"
  3. self.timer = cf.at(0.01, function ( ... ) -- 启动一个循环定时器
  4. self.count = self.count + 1
  5. self.ws:send(tostring(self.count))
  6. end)
  7. end

WebSocket:on_message(data, type)

此方法将在用户主动发送text/binary数据的时候被回调.

参数data是一个字符串类型的playload; type是一个boolean类型变量, true为binary类型, 否则为text类型.

  1. function websocket:on_message(data, typ)
  2. print('on_message', self.ws, data, typ)
  3. self.ws:send('welcome')
  4. -- self.ws:close(data)
  5. end

WebSocket:on_error(error)

此方法在发生协议错误与未知错误的时候会被回调, 参数error是字符串类型的错误信息.

通常情况下我们不会用到这个方法.

  1. function websocket:on_error(error)
  2. print('on_error:', error)
  3. end

WebSocket:on_close(data)

此方法在连接关闭时回调. data为关闭连接时发送过来到数据, 所以data可能为nil.

无论什么情况, 在连接被关闭的时候都将会调用此方法, 而此方法通常的作用是清理数据.

  1. function websocket:on_close(data)
  2. if self.timer then -- 清理定时器
  3. print("清理定时器")
  4. self.timer:stop()
  5. self.timer = nil
  6. end
  7. end

websocket client

创建一个websocket-client对象.

  1. local wc = require "protocol.websocket.client"
  2. -- local w = wc:new {url = "wss://[::1]/ws"}
  3. -- local w = wc:new {url = "wss://[::1]:8080/ws"}
  4. local w = wc:new {url = "ws://localhost:8080/ws"}

1. w:connect()

此方法用于创建websocket client对象后连接至url指定的server端.

此方法返回为true时连接成功, 否则err为失败原因.

  1. local ok, err = w:connect()
  2. if ok then
  3. return print(err)
  4. end
  5. print("连接成功")

2. w:set_timeout(timeout)

此方法用于给client库设置最大recv等待时间. 默认情况下会一直等待.

  1. w:set_timeout(30)

3. w:ping(data)

此方法用于给server端发送ping.

data为字符串类型payload.

  1. w:ping('ping')

4. w:pong(data)

此方法用于给server端发送pong.

data为字符串类型payload.

  1. w:pong('pong')

5. w:recv()

此方法用来获取服务端的发送的数据.

此方法返回data, typ; data为服务端发送的数据, typ为命令类型(text/binary/ping/pong等等).

  1. while 1 do
  2. local data, typ = w:recv()
  3. if not data then
  4. return w:close()
  5. end
  6. print(data, typ)
  7. end

6. w:send(data, binary)

此方法用于给server端发送数据.

data为字符串类型payload, binary是一个boolean类型变量, true为binary类型, 否则为text类型.

  1. -- w:send('hello world', true)
  2. w:send('hello world')

注意: 请不要对w对象进行异步数据发送. 如:

  1. local cf = require "cf"
  2. while 1 do
  3. local data = w:recv()
  4. if not data then
  5. return w:close
  6. end
  7. cf.fork(function ()
  8. w:send(data)
  9. end)
  10. end

这可能会导致不可预料的错误出现.

7. w:close()

当您不再使用client对象时请用此方法关闭.