Lua Web快速开发指南(8) - 利用httpd提供Websocket服务

栏目: Lua · 发布时间: 4年前

Websocket的技术背景

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文档 .

优势

在需要消息推送、连接保持、交互效率等要求下, 两种协议的转变将会带来交互方式的不同.

首先, Websocket 协议使用头部压缩技术将头部压缩成2-10字节大小并且包含数据载荷长度, 这显著减少了网络交互的开销并且确保信息数据完整性.

如果假设在一个稳定(可能)的网络环境下将尽可能的减少连接建立开销、身份验证等带来的网络开销, 同时还能拥有比 HTTP 协议更方便的数据包解析方式.

其次, 由于基于 Websocket 的协议的在 请求->回应 上是双向的, 所以不会出现多个请求的阻塞连接的情况. 这也极大程度上减少了正常请求延迟的问题.

最后, Websocket 还能给予开发者更多的连接管控能力: 连接超时、心跳判断等. 在合理的连接管理规划下, 这可提供使用者更优质的开发方案.

API

cf框架中的 httpd 库内置了 Websocket 路由, 提供了上述 Websocket 连接管理能力.

Websocket 路由需要开发者提供一个 lua 版的 class 对象来抽象路由处理的过程, 这样的抽象能简化代码编写难度.

lua class

class 意译为'类'. 是对'对象'的一种抽象描述, 多用于各种面相对象编程语言中. lua没有原生的 class 类型, 但是提供了基本构建的元方法.

cf为了方便描述内置对象与内置库封装, 使用lua table的相关元方法建立了最基本的class模型. 几乎大部分内置库都依赖cf的class库.

同时为了简化 class 的学习成本, 去除了class原本拥有的'多重继承'概念. 将其仅作为 定义, 用于完成从 class -> object 的初始化工作.

更多关于 class 的详情, 请参考Wiki中关于 class 库的 文档 .

Websocket 相关的API

现在我们开始学习 Websocket 与之相关的API

WebSocket:ctor(opt)

初始化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:on_open()

当有连接初始化完成之后此方法会被调用. 此方法虽然与 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

WebSocket:on_message(data, type)

此方法将在用户主动发送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

WebSocket:on_error(error)

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

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

function websocket:on_error(error)
  print('on_error:', error)
end

WebSocket:on_close(data)

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

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

function websocket:on_close(data)
  if self.timer then -- 清理定时器
    print("清理定时器")
    self.timer:stop()
    self.timer = nil
  end
end

更多API

更多关于 Websocket 的API请参考Wiki的(文档)[ https://github.com/CandyMi/co... ].

开始实践

建立路由

首先! 让我们在 script 目录下新建2个文件: main.luaws.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路由处理.

开始编写一个简单的Demo

首先, 我们在 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 处输入我们的连接地址进行连接并且查看服务端的推送消息.

ws1.png

开发者可以在运行 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框架内置的异步库


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

精通CSS与HTML设计模式

精通CSS与HTML设计模式

Michael Bowers / 刘申 朱瑜敏 鲁奇 / 人民邮电出版社 / 2008-9 / 69.00元

本书是一部非常实用的CSS 与HTML(XHTML)解决方案手册。书中包含了350 多种可以立即使用的设计模式(涉及文本、背景、边框、图片、表格、布局等多方面),并介绍了每种模式的原理和使用。每种设计模式、示例和源代码都经过了精心设计,易于实现和使用。通过阅读此书,可大大提高读者在 Web 设计和开发中的效率和创造力。 本书结构清晰,示例丰富,实践性强,适用于所有Web 开发和设计人员......一起来看看 《精通CSS与HTML设计模式》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

MD5 加密
MD5 加密

MD5 加密工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具