WebSocket
是一种在单个TCP链接上进行全双工通讯的协议, WebSocket
通讯协议于2011年被IETF定为标准RFC 6455
并由RFC7936
补充规范.html
WebSocket
使得客户端和服务器之间的数据交换变得更加简单, 使用WebSocket
的API只须要完成一次握手
就直接能够建立持久性的链接并进行双向数据传输.git
WebSocket
支持的客户端不只限于浏览器
(Web应用), 在现今应用市场内的众多App客户端的长链接推送服务都有一大部分是基于WebSocket
协议来实现交互的.github
Websocket
因为使用HTTP协议升级而来, 在协议交互初期须要根据正常HTTP协议交互流程. 所以, Websocket也很容易创建在SSL数据加密技术的基础上进行通讯.web
WebSocket
与HTTP协议实现相似但也略有不一样. 前面提到: WebSocket
协议在进行交互以前须要进行握手
, 握手协议
的交互就是利用HTTP协议
升级而来.chrome
众所周知, HTTP协议是一种无状态的协议. 对于这种创建在请求->回应
模式之上的链接, 即便在HTTP/1.1
的规范上实现了Keep-alive
也避免不了这个问题.编程
因此, Websocket
经过HTTP/1.1
协议的101
状态码进行协议升级协商, 在服务器支持协议升级的条件下将回应升级请求来完成HTTP->TCP
的协议升级
.浏览器
客户端将在通过TCP3次握手以后发送一次HTTP升级链接请求, 请求中不只包含HTTP交互所须要的头部信息, 同时也会包含Websocket
交互所独有的加密信息.bash
当服务端在接受到客户端的协议升级请求的时候, 各种Web服务实现的实际状况, 对其中的请求版本、加密信息、协议升级详情进行判断. 错误(无效)的信息将会被拒绝.服务器
在两端确认完成交互以后, 双方交互的协议将会从抛弃原有的HTTP协议转而使用Websocket
特有协议交互方式. 协议规范能够参考RFC文档.websocket
在须要消息推送、链接保持、交互效率等要求下, 两种协议的转变将会带来交互方式的不一样.
首先, Websocket
协议使用头部压缩技术将头部压缩成2-10字节大小而且包含数据载荷长度, 这显著减小了网络交互的开销而且确保信息数据完整性.
若是假设在一个稳定(可能)的网络环境下将尽量的减小链接创建开销、身份验证等带来的网络开销, 同时还能拥有比HTTP
协议更方便的数据包解析方式.
其次, 因为基于Websocket
的协议的在请求->回应
上是双向的, 因此不会出现多个请求的阻塞链接的状况. 这也极大程度上减小了正常请求延迟的问题.
最后, Websocket
还能给予开发者更多的链接管控能力: 链接超时、心跳判断等. 在合理的链接管理规划下, 这可提供使用者更优质的开发方案.
cf框架中的httpd
库内置了Websocket
路由, 提供了上述Websocket
链接管理能力.
Websocket
路由须要开发者提供一个lua版的class
对象来抽象路由处理的过程, 这样的抽象能简化代码编写难度.
class
意译为'类'. 是对'对象'的一种抽象描述, 多用于各类面相对象编程语言中. lua没有原生的class
类型, 可是提供了基本构建的元方法.
cf为了方便描述内置对象与内置库封装, 使用lua table的相关元方法创建了最基本的class模型. 几乎大部份内置库都依赖cf的class库.
同时为了简化class
的学习成本, 去除了class本来拥有的'多重继承'概念. 将其仅做为类
定义, 用于完成从class
->object
的初始化工做.
更多关于class
的详情, 请参考Wiki中关于class
库的文档.
如今咱们开始学习Websocket
与之相关的API
初始化Websocket对象, Websocket客户端链接创建完成以前被调用.
此方法在on_open方法以前被调用, 通常用于告诉httpd
应该如何怎么进行数据包交互.
function websocket:ctor (opt) self.ws = opt.ws -- websocket对象 self.send_masked = false -- 掩码(默认为false, 不建议修改或者使用) self.max_payload_len = 65535 -- 最大有效载荷长度(默认为65535, 不建议修改或者使用) end
当有链接初始化完成以后此方法会被调用. 此方法虽然与Websocket:ctor
相似, 但通常在仅用于内部服务初始化的时候使用.
function websocket:on_open() local cf = require "cf" self.timer = cf.at(0.01, function ( ... ) -- 启动一个循环定时器 self.count = self.count + 1 self.ws:send(tostring(self.count)) end) end
此方法将在用户主动发送text/binary数据的时候被回调.
参数data是一个字符串类型的playload; type是一个boolean类型变量, true为binary类型, 不然为text类型.
function websocket:on_message(data, typ) print('on_message', self.ws, data, typ) self.ws:send('welcome') -- self.ws:close(data) end
此方法在发生协议错误与未知错误的时候会被回调, 参数error是字符串类型的错误信息.
一般状况下咱们不会用到这个方法.
function websocket:on_error(error) print('on_error:', error) end
此方法在链接关闭时回调. data为关闭链接时发送过来到数据, 因此data可能为nil
.
不管什么状况, 在链接被关闭的时候都将会调用此方法, 而此方法一般的做用是清理数据.
function websocket:on_close(data) if self.timer then -- 清理定时器 print("清理定时器") self.timer:stop() self.timer = nil end end
更多关于Websocket
的API请参考Wiki的文档.
首先! 让咱们在script
目录下新建2个文件: main.lua
与ws.lua
, 而后分别填入下列内容:
-- app/script/ws.lua local class = require "class" local ws = class("websocket") function ws:ctor(opt) self.ws = opt.ws self.send_masked = false self.max_payload_len = 65535 end function ws:on_open() end function ws:on_message(data, typ) end function ws:on_error(error) end function ws:on_close(data) end return ws
-- main.lua local httpd = require "httpd" local app = httpd:new("httpd") app:ws('/ws', require "ws") app:listen("", 8080) app:run()
咱们使用httpd
库启动了一个Web Server, 同时将ws.lua
内的class
对象注册为Websocket
处理对象.
同时, 咱们在Websocket:ctor
方法内部, 为Websocket路由的链接初始化了一些链接信息. 以上为最精简的Websocket路由处理.
首先, 咱们在ws:on_open
方法内部添加一段定时器代码, 这个定时器用于在链接创建完成以后持续向开发者推送递增消息.
function ws:on_open() local cf = require "cf" local count = 1 self.timer = cf.at(3, function(...) self.ws:send(tostring(count)) count = count + 1 end) print(self.ws, "客户端链接成功.") end
而后, 咱们为ws:on_close
方法添加一段定时器销毁代码用于防止内存泄露.
function ws:on_close(data) if self.timer then self.timer:stop() self.timer = nil end print(self.ws, "客户端关闭了链接.") end
最后, 为每次客户端发送过来的消息执行一次echo回应.
function ws:on_message(data, type) self.ws:send(data, type) print(self.ws, "接受到客户端发送的消息.", data) end
运行cfadmin
,
让咱们使用chrome浏览器点击这里, 使用提取码cgwr
下载Websocket
客户端插件而且安装.
而后打开刚刚下载的websocket client插件并在其Websocket Address
处输入咱们的链接地址进行链接而且查看服务端的推送消息.
开发者能够在运行cfadmin
的终端查看链接创建的消息打印.
[candy@MacBookPro:~/Documents/core_framework] $ ./cfadmin [2019/06/18 21:48:36] [INFO] httpd正在监听: 0.0.0.0:8080 [2019/06/18 21:48:36] [INFO] httpd正在运行Web Server服务... [2019/06/18 21:48:39] - ::1 - ::1 - /ws - GET - 101 - req_time: 0.000080/Sec websocket-server: 0x7f9495e01200 客户端链接成功. websocket-server: 0x7f9495e01200 接受到客户端发送的消息. hello world websocket-server: 0x7f9495e01200 客户端关闭了链接.
-- main.lua local httpd = require "httpd" local app = httpd:new("httpd") app:ws('/ws', require "ws") app:listen("", 8080) app:run()
-- app/script/ws.lua local class = require "class" local ws = class("websocket") function ws:ctor(opt) self.ws = opt.ws self.send_masked = false self.max_payload_len = 65535 end function ws:on_open() local cf = require "cf" local count = 1 self.timer = cf.at(3, function(...) self.ws:send(tostring(count)) count = count + 1 end) print(self.ws, "客户端链接成功.") end function ws:on_message(data, type) self.ws:send(data, type) print(self.ws, "接受到客户端发送的消息.", data) end function ws:on_error(error) end function ws:on_close(data) if self.timer then self.timer:stop() self.timer = nil end print(self.ws, "客户端关闭了链接.") end return ws
下一章咱们将学习cf框架内置的异步库