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

2024-05-19 11:08:08 +08:00
 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 对象。

3376 次点击
所在节点    Web Dev
16 条回复
ryc111
2024-05-19 12:55:00 +08:00
用 grpc 定义两个 service?
InDom
2024-05-19 13:28:25 +08:00
| ver | route id | body len | body |

然后 body 才是你的 Protobuf 结构。

或者

| ver | route len | route str | body len | body |
uiosun
2024-05-19 15:30:58 +08:00
@ryc111 那就太多了哈哈,我打算写个游戏,玩家的动作会比较多

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

再给 request.Body 赋予 Protobuf 的对象,最后将整个对象打包成二进制,发给 Client 。
cloud107202
2024-05-19 18:16:31 +08:00
抛弃路由的概念,用 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
2024-05-19 18:22:06 +08:00
第二种是直接用 pb 的高阶用法,oneof 字段。参考这里 https://zhuanlan.zhihu.com/p/453913153 例子,可以避开对 bytes 的 raw byte manipulation. 有兴趣研读 pb 文档的话,我推荐第二种
cloud107202
2024-05-19 18:25:56 +08:00
针对你的困扰出发,核心就是这也许是你头一次针对 websocket 场景编程。这里跟 HTTP 的语义封装没关系,尤其是没有请求-响应的通讯模式,没有路由的概念。先定义消息类型(完全由你自行定义),把消息发跟收分开处理就好,各自独立
kiracyan
2024-05-19 21:43:33 +08:00
建议用 2 个 key 区分功能 内容用 bytebuff
Nazz
2024-05-19 21:48:03 +08:00
在消息头用两个字节(uint16)标识路由
Nazz
2024-05-19 21:53:56 +08:00
用字符串标识路由更好些,开头的两个字节表示路由长度. ws 库可以使用 gws, 它的 payload 是 bytes.buffer 类型.
sunny352787
2024-05-19 22:20:11 +08:00
对于你的技术储备,我建议直接 grpc 最省事,有切割二进制流的功夫 grpc 写好多功能了
kuanat
2024-05-19 22:23:30 +08:00
这种通信场景一般没有路由的说法吧,都是用协议这个词。

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

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

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

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


自己去实现这些很麻烦.
uiosun
2024-05-20 11:35:59 +08:00
@cloud107202 @kiracyan @Nazz @kuanat 谢谢大佬们,你们好强!

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

我先读读 gRPC 流的知识,看能不能快速实现我的需求。再次感谢各位!
Nazz
2024-05-20 11:50:36 +08:00
做 IM 应该用 WebSocket 而不是 gRPC Stream, io.Reader 切割二进制流很简单的, 发送成功确认做起来麻烦些
Nazz
2024-05-20 11:51:40 +08:00
对性能没有高要求的话应该使用 JSON
Nazz
2024-05-20 11:52:19 +08:00
或者 MsgPack

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

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

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

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

© 2021 V2EX