ProtoBuf ParseDelimitedFrom是否与NetworkStream不一致?

时间:2017-09-12 02:48:31

标签: c# sockets parsing endpoint networkstream

我正在尝试做什么

我是this项目的作者,该项目通过本地API从Spotify的本地实例中获取当前正在播放的歌曲,并设置Discord'正在播放'反映它的信息。

非常简单的东西,但出于各种原因我想切换到C#,并且它对Spotify没有相同的支持级别。

缩短篇幅,跨平台+工作实用API + Spotify播放列表= Clementine。所以我决定为Clementine创建一个类似的Discord Integration。

But there's a problem

我可以创建一个连接到127.0.0.1:5500的套接字 我可以通过此套接字成功发送ConnectRequest消息 我可以毫无问题地收到这些消息类型:

  • KEEP_ALIVE,
  • 播放,
  • PAUSE,
  • 停止,
  • REPEAT,
  • SHUFFLE,
  • VOLUME_UPDATE,
  • TRACK_POSITION_UPDATE

但是,如果我尝试播放停止状态的歌曲,则会引发20 exceptions,然后播放"播放"消息被解析。
我相信这应该是CURRENT_METAINFO消息 如果我尝试将新歌添加到播放列表,则会抛出类似的异常。

我用来检索邮件的机制是

Message.Parser.ParseDelimitedFrom(Client.GetStream());

其中:
消息= .proto file from Repo中定义的类 Parser = Protobuf内置Object Parser
ParseDelimitedFrom = Protobuf内置方法,它从Stream中获取[Length:PayloadOfLength'Length']消息,并将Payload解析为提供类型(Message)的对象。
Client = System.Net.Sockets.TcpClient
GetStream = System.Net.Sockets.NetworkStream

除了检测到大约3'未知'每个keepalive的消息(NetworkStream似乎每隔四分之一秒发送0而没有数据?)当Clementine处于空闲状态时,此方法适用于不太复杂的消息(IE:没有SongMetadata类的消息)在响应对象中,以及根本没有响应对象的那些)。

我的怀疑

由于Proto3不与Proto2向后兼容,我不得不稍微修改provided .proto文件以删除所有'可选'关键字和事物的默认值,并将所有枚举重新设置为从0开始 这可能在Parser中引入了一个微妙的错误,它试图读取一个不存在的值,然后转到下一个,而不是意识到那里有默认值,或类似的东西。

某些异常似乎表明解析器没有正确处理数据的长度 这可能意味着解析器在消息完全写入之前将Stream读取到最后,并且在停止并尝试解析之前不等待其余部分。然后无法读取未完成消息的第一部分,但无论如何都要将其从流中删除,渲染以下所有块也难以辨认。
这也可能意味着由于Clementine嵌套消息的方式,解析器正在检测外部消息的大小,而不是正确地适应可选的嵌套对象(或者实际的Clementine消息没有正确设置长度)。 专辑封面也是一个不确定长度字节序列的问题。

我遇到的墙壁

我完全重写了Socket逻辑至少3次。前两个使用裸Socket并使用byte []缓冲区来读取消息。
这似乎很有希望,但实际上永远不会从旨在检索特定消息的剩余字节的递归函数返回,因为总是有更多的字节要读取。在我写这篇文章之前尝试从前面构建缓冲区并在每次迭代时尝试使用它直到它理解了消息,然后从缓冲区中删除消息之后,它才发生在我身上。

我尝试使用TcpClient以与上面类似的方式使用我当前的实现来实现byte []缓冲区。但同样,Socket编写0而不是保持空闲的问题导致导致0被视为无效标记并抛出异常的问题。

我曾尝试使用BufferedStream来封装NetworkStream,但不确定究竟如何实现重新读取先前读取的异常数据,再次数据到达。

莽撞

整个实际代码。在正确安装和配置C#插件和dotnet核心的情况下,任何VSCode安装都应该可以随时使用。

https://bitbucket.org/roberestarkk/discordclementineremotedotnet

致力于帮助

我非常感谢任何人都可以提供的帮助!这个项目让我很有智慧。
我已经尝试了所有关于开发人员的消息(我打算在短时间内完成这些工作,我只需要安装一个IRC客户端),并且不再接近底层。
如果我只知道为什么抛出这些例外......

1 个答案:

答案 0 :(得分:0)

解决这个问题的方法是滚动我自己的消息缓冲,将前4个字节(int)切换成一个字节数组,反转它并将其解析成一个int。如果int非零(Clementine在没有发送消息的情况下每隔[interval]发送一个代表0的int),我就可以获取实际消息(buffer [3..length]),并将其提供给它进入protobuf消息解析器的非分隔版本。

将工作代码上传到回购。