请教: WebSocket + Protobuf 做服务,如何定义路由

216 天前
 uiosun

问题:

目前在项目中采用 WebSocket 长连接做服务,Protobuf 作为请求/响应的数据结构。

当存在多个路由时,如何定义它们呢?

举个例子:聊天室

接口 1:(按条件)筛选聊天内容 接口 2:发送消息

请教各位,长连接服务在这种场景,如何与客户端通讯(怎么定义路由,或者如果不定义,有什么其他办法吗?)

谢谢各位!


背景:我对于 Protobuf 、长连接的使用经验比较匮乏,之前只做过 HTTP/RPC + Protobuf 的项目。

我的思路历程:

最简单的就是 Protobuf 封装一个标准路由结构:

message IRoute {
  string key = 1;
  string jsonRes = 2;
}

key 做路由键,jsonRes 就是 JSON 字符串,只有响应时才返回纯 Protobuf 对象,但这就买椟还珠了吧?(不如直接用 JSON )

后面又想定义 Interface 来设计,先将数据转化为标准对象,然后判断 key 来将数据转化为实际路由,但对二进制数据,我做不到只转化部分内容……

最后,目前打算使用 text 作为路由,自己手动分割,然后响应时返回 Protobuf 对象。

2662 次点击
所在节点    Web Dev
16 条回复
ryc111
216 天前
用 grpc 定义两个 service?
InDom
216 天前
| ver | route id | body len | body |

然后 body 才是你的 Protobuf 结构。

或者

| ver | route len | route str | body len | body |
uiosun
216 天前
@ryc111 那就太多了哈哈,我打算写个游戏,玩家的动作会比较多

@InDom 谢谢大佬!再请教一下,`|...|...|` 的结构,是个 Request 对象?就是:

再给 request.Body 赋予 Protobuf 的对象,最后将整个对象打包成二进制,发给 Client 。
cloud107202
216 天前
抛弃路由的概念,用 pb 定义消息结构就好。举个例子

message FetchChatHistoryRequest(id, count, start, end, e.g.)
message FetchChatHistoryResponse(repeat string xxx)
message SendChatMsg(string target, string content)

在实现里收到消息,解析类型,派发给对应 Type 的逻辑做业务逻辑处理,他们的逻辑当是独立离散的。
理解这一层之后,收发两端都有个需要,就是识别一个 raw bytes 比如 Java 语言会接收到 byte[] 作为消息包,要知道它具体是什么。 这里有两种思路: 第一是像二楼这样,外层用 TCP 的 TV 或 TLV 来包装一下,就是 type-length-value 这种。前两个字段一定要定长,比如 type 是 4byte 的数字类型,自己给上述消息定义好类型 id 。lenght 是 8byte 的长整形,数值是后面 value 部分的长度。value 里就是 pb 消息,encoded pb bytes 。 自己写个简单的 encoding / decoding 逻辑
cloud107202
216 天前
第二种是直接用 pb 的高阶用法,oneof 字段。参考这里 https://zhuanlan.zhihu.com/p/453913153 例子,可以避开对 bytes 的 raw byte manipulation. 有兴趣研读 pb 文档的话,我推荐第二种
cloud107202
216 天前
针对你的困扰出发,核心就是这也许是你头一次针对 websocket 场景编程。这里跟 HTTP 的语义封装没关系,尤其是没有请求-响应的通讯模式,没有路由的概念。先定义消息类型(完全由你自行定义),把消息发跟收分开处理就好,各自独立
kiracyan
216 天前
建议用 2 个 key 区分功能 内容用 bytebuff
Nazz
216 天前
在消息头用两个字节(uint16)标识路由
Nazz
216 天前
用字符串标识路由更好些,开头的两个字节表示路由长度. ws 库可以使用 gws, 它的 payload 是 bytes.buffer 类型.
sunny352787
216 天前
对于你的技术储备,我建议直接 grpc 最省事,有切割二进制流的功夫 grpc 写好多功能了
kuanat
216 天前
这种通信场景一般没有路由的说法吧,都是用协议这个词。

如楼上说得都挺好了。我有个建议,你可以看看用 unix domain socket 做 IPC 通信一般是怎么做的。ws 就是把本地变远程,protobuf 就是 socket 的具体实现(协议/路由)。

在 web 编程里是匹配路由然后把请求交给对应的 handler ,在 socket 编程里硬要说路由或者协议的话就是某个字节代表特定的类型,然后每个类型有一个专门的 handler 来响应。
gamexg
216 天前
路由简单,
如前面回复,在包内容前面加点路由字段就行.

但是其他麻烦还有很多,
计划是否允许并行请求(前一个接口 1 请求未响应就发送新的接口 2 请求)?
如果允许并行请求,那么能处理响应顺序和请求顺序不一致吗?

另一个情况,比如发送消息,第一个消息还没返回响应,第二个消息又发送了. 那么之后收到的响应可能是第一个也可能是第二个的响应.虽然加个 id 也能处理,但是加上超时/连接断开重发请求等情况会很麻烦.


自己去实现这些很麻烦.
uiosun
215 天前
@cloud107202 @kiracyan @Nazz @kuanat 谢谢大佬们,你们好强!

@gamexg @sunny352787 也谢谢两位大佬,你们也好强!这个项目是学点新东西,所以不介意费事。

我先读读 gRPC 流的知识,看能不能快速实现我的需求。再次感谢各位!
Nazz
215 天前
做 IM 应该用 WebSocket 而不是 gRPC Stream, io.Reader 切割二进制流很简单的, 发送成功确认做起来麻烦些
Nazz
215 天前
对性能没有高要求的话应该使用 JSON
Nazz
215 天前
或者 MsgPack

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1041972

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX