Websocket使用指南

cf提供了websocket client与server库, 具体API使用请参考Websocket API

server handle使用示例

cf的websocket协议层实现将其作为一种升级协议内嵌到httpd库内. 所以, 当您使用httpd库的时候即可很方便的使用websocket.

我们根据cf web的初始化与使用最后的示例, 在script/main.lua文件内添加一段代码:

  1. app:ws('/ws', require 'ws')

然后我们在script目录建立一个ws.lua文件, 在其内部添加如下代码:

  1. local class = require "class"
  2. local cf = require "cf"
  3. local json = require "json"
  4. local websocket = class("websocket")
  5.  
  6. function websocket:ctor(opt)
  7. self.ws = opt.ws -- websocket对象
  8. self.send_masked = false -- 掩码(默认为false, 不建议修改或者使用)
  9. self.max_payload_len = 65535 -- 最大有效载荷长度(默认为65535, 不建议修改或者使用)
  10. self.timeout = 15 -- 默认为一直等待, number类型会导致异常.
  11. self.count = 0
  12. end
  13.  
  14. function websocket:on_open()
  15. print('on_open')
  16. self.timer = cf.at(0.01, function ( ... ) -- 定时器
  17. self.count = self.count + 1
  18. self.ws:send(tostring(self.count))
  19. end)
  20. end
  21.  
  22. function websocket:on_message(data, typ)
  23. print('on_message', self.ws, data)
  24. self.ws:send('welcome')
  25. -- self.ws:close(data)
  26. end
  27.  
  28. function websocket:on_error(error)
  29. print('on_error', self.ws, error)
  30. end
  31.  
  32. function websocket:on_close(data)
  33. print('on_close', self.ws, data)
  34. if self.timer then -- 清理定时器
  35. print("清理定时器")
  36. self.timer:stop()
  37. self.timer = nil
  38. end
  39. end
  40.  
  41. return websocket

上述流程做了些什么?

我们利用app:ws为httpd对象导入了一个叫websocket的lua class, 这个class定义了websocket handle所需的所有方法.

httpd库实现了websocket需要的: on_openon_messageon_erroron_close等方法, 这些方法对应用户定义的websocket class方法.

在websocket ctor方法中, httpd会为开发者传入一个websocket对象. 这个对象目前有2个公有方法可供开发者使用: send方法与close方法.

首先会才连接建立的时候会调用ctor进行初始化, 这个初始化的可选操作是让用户定义send_maskedmax_payload_lentimeout.

其中timeout默认为永不超时(nil), 除非开发者指定超时时间; max_payload_len指定最大数据包长度; send_masked表示是否需要进行掩码处理;

上述三个内置参数推荐都使用默认值或者不指定, 而超时时间一般推荐websocket server开发者主动构建应用层心跳包并根据是否回应来决定是否断开连接.

在初始化完成之后, httpd建立连接完成将会再次调用on_open方法, 这个方法一般用于用户注册、初始化一些与websocket class本身无关的一些资源(例如MQ).

值得注意的是: 在这个on_open执行完成之前, httpd库将不会接受任何websocket消息. 在生产环境运行中的业务需要特别注意.

每当httpd server接受到一个客户端发送一个消息过来, 将会调用on_message方法将数据传入进来. 第一个参数是数据载荷, 第二个参数是数据类型;

用户可以使用ctor调用时传入到send与close方法来决定是否应该回应消息与关闭消息(ping类型的消息由框架自动回应).

on_error是传输协议错误或其它原因导致的异常将会将error信息导入进来, 一般正常websocket消息交互与关闭将不会触发此方法.

on_close一般会在客户端关闭、网络连接中断、服务端主动关闭的时候主动触发, 留给开发者释放资源的机会.

client 使用示例

首先导入wc库, 然后初始化一url后进行连接. url支持ipv6与ipv4或者hostname方式.

示例代码在script/test_wsclient.lua.

  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"}
  5. local ok, ret = w:connect()
  6. if not ok then
  7. return print(ok, ret)
  8. end
  9.  
  10. w:ping('ping')
  11.  
  12. print(w:recv())
  13.  
  14. while 1 do
  15. local data, typ = w:recv()
  16. print(data, typ)
  17. if not data then
  18. break
  19. end
  20. end
  21.  
  22. w:close()

websocket client 使用new方法初始化传参, url参数将会被自动解析为有效域名、端口、路径;

websocket client支持ws与wss两种连接方式. 在初始化对象完成之后, 可以尝试调用connect进行数据连接. 当连接成功时返回true; 否则返回nil与出错信息.

websocket client支持ping、pong、send、close等方法用于数据交互. 用户在所有websocket client完成之后需要调用close方法释放连接.

注意事项

上述两个最简单的例子提示了基于cf框架的websocket业务应该如何编写, 但是在正式环境下应该注意一下几点:

  • cf是事件驱动框架, 高效稳定的websocket server应该有效合理的利用MQ来处理推送业务与消息订阅服务.

  • cf框架都消息推送是由队列来保证顺序的, 通常情况下这不会有性能问题. 但是过多的消息将会导致队列长度的自动伸缩, 这无疑对稳定性与延迟无益.

  • cf框架没限制用户注册的定时器数量, 但是经过无数测试代码证明: 海量高精度定时器(<0.01ms)无法保证开发者有效的利用事件驱动模型和更好的利用CPU、内存.

  • cf的websocket server/client的send都是异步发送! 所以不会保证一定会发送成功, 即是发送成功也不保证对方一定能收到(这需要依赖于系统配置与应用层来保证).