V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
uiosun
V2EX  ›  Web Dev

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

  •  
  •   uiosun · 27 天前 · 1613 次点击

    问题:

    目前在项目中采用 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 对象。

    16 条回复
    ryc111
        1
    ryc111  
       27 天前
    用 grpc 定义两个 service?
    InDom
        2
    InDom  
       27 天前   ❤️ 1
    | ver | route id | body len | body |

    然后 body 才是你的 Protobuf 结构。

    或者

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

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

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

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

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

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

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


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

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

    我先读读 gRPC 流的知识,看能不能快速实现我的需求。再次感谢各位!
    Nazz
        14
    Nazz  
       26 天前   ❤️ 2
    做 IM 应该用 WebSocket 而不是 gRPC Stream, io.Reader 切割二进制流很简单的, 发送成功确认做起来麻烦些
    Nazz
        15
    Nazz  
       26 天前
    对性能没有高要求的话应该使用 JSON
    Nazz
        16
    Nazz  
       26 天前
    或者 MsgPack
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2647 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 14:38 · PVG 22:38 · LAX 07:38 · JFK 10:38
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.