跳至主要內容
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • ==========TCP 编程的相关问题==========
未分類
28 2 月 2022

==========TCP 编程的相关问题==========

==========TCP 编程的相关问题==========

資深大佬 : FreeWong 26

对方通过单片机使用 TCP 客户端的形式(短连接)与我写的服务端进行通讯
他们设计的协议是基于文本的,类似以下格式
@Start:A01:25.41,A02:36.1#
其中#号表示一条完整的可解析的数据的结尾,同时 TCP 客户端要求当服务器收到一条完整的数据后,要回复 @Received 用于表示接收到这条数据。
并且客户端会等待我的这个回复,才会断开 TCP 客户端一侧的连接,十几秒都没有收到则主动断开。(我们不考虑它主动断开的情况)
如果服务端没有回复,则到下一个通讯时间节点时,再次将前面没有收到回复的数据补发,然后再接着发送当前时间节点应该发送的。
像这样
@Start:A01:25.41,A02:36.1#@Start:A01:26.18,A02:38.2#
很多编程语言提供类似 ReadTo() 方法,意思是这个方法不返回,一直读到指定的字符时返回,所以 ReadTo(“#”) 就会返回一条完整的数据。 但是由于存在补发数据的情况,所以得循环调用这个方法像这样
for{
string 完整的数据 =ReadTo(“#”)
开始处理完整的数据的函数(完整的数据)
}
你们发现没有,由于我根本不知道客户端会有多少个完整的数据的指示符,所以我的服务端必须要循环调用 ReadTo(“#”) 并且我根本不知道什么时候该退出循环。
也就无法向 TCP 客户端发送 @Received 让来客户端断开连接。
现在就变成了,我根本不知道何时结束循环也就无法发送 @Received,而客户端也在等待我给它指示断开的指令,这样面临一个类似“死锁”状态。
这里只讨论 ReadTo(“#”) 这个方法
所以我的看法就,TCP 客户端应该将多条补发数据的格式修改为
@Start:A01:25.41,A02:36.1;@Start:A01:26.18,A02:38.2#
将中间的 #号修改为 ; 号,这样的话, 我都不需要使用循环来读。
请问,我的想法有没有考虑不周全的地方?感谢大家指正。
大佬有話說 (21)

  • 資深大佬 : ksc010

    太长了,没细看
    一般直接用 tcp 协议通讯的话,都设置自定义消息包格式
    消息头+消息体
    消息头长度固定,头里面包含消息体的长度,这样就知道读取多少位数据停止了

  • 資深大佬 : rrfeng

    不要 ReadTo ,每次用 ReadAll 全读出来,然后另外拆分处理?

  • 資深大佬 : bfdh

    如果对端支持 udp 的话,换 udp 吧,感觉 udp 这种协议设计。使用 tcp 确实存在你说的问题。
    如果非得用 tcp ,又不愿意改协议,那就你的接收端加超时,一定时间内没有新的数据达到,就认为是一条消息结束。

  • 資深大佬 : bfdh

    @bfdh #3 更正一下 感觉 udp 这种协议设计 ==> 感觉 udp 更适合这种协议设计

  • 資深大佬 : ysc3839

    这个设计是有问题,看起来 Received 的作用只是控制是否补发,那客户端发完后就应该直接 shutdown ( https://man7.org/linux/man-pages/man2/shutdown.2.html ),这样服务端就能知道数据已经发完了,这个方案甚至不需要修改数据包格式。

  • 資深大佬 : ysc3839

    @rrfeng 这里的问题是客户端发完数据后不会 shutdown ,ReadAll 要等客户端 shutdown 后才会返回。

  • 資深大佬 : momocraft

    想办法避免无限阻塞的 API

  • 資深大佬 : ipwx

    解决方案:
    1 、每个客户端给一个 shutdownRequested 变量。
    2 、自己开缓冲区处理 ReadTo(‘#’) 的逻辑,用异步 read 。
    3 、read 读不到就返回,那么进入 epoll 等待队列。
    4 、如果 shutdownRequested ,就唤醒这个 fd 去处理。。
    总结:
    需要一个完整的 event loop 。但是有了就很容易做

  • 主 資深大佬 : FreeWong

    @rrfeng 实际编程语言没有 ReadAll , 如果你的意思是 总是从缓冲区去读,每次读一批,然后接收缓冲区就有了位置,对端才可以继续发送数据过来。但是你如何判断你接收完了? 像这样
    声明 512 长度的字节数组
    for{
    得到的数据= 读到 512 长度的字节数组()
    如果( 得到的数据以用 # 号结尾则是一个完成的数据 )
    }
    但这是有问题的,如果你正好读的最后一个字符是两条数据中间的 # ,你会认为所有的数据都接收完了 ,实际上还有余下的一个完整的没有被接收下来, 如果你认为读到一个 # 不是所有数据都接收完了,那何时能判断哪个 # 号才表示接收完成?

  • 主 資深大佬 : FreeWong

    @ksc010 TCP 客户端的实现是对方协议制定好的,我无法修改它,只是用技术角度来分析这个协议的缺陷

  • 主 資深大佬 : FreeWong

    @bfdh 超时是个办法,但不是一个好办法,我可以定义 5 秒钟都收完,如果收不完,也就结束 了,但这种方式真的不好

  • 主 資深大佬 : FreeWong

    @ysc3839 TCP 客户端不会主动断开的,它一定要待我回 @Received ,所以这就是我说的 “死锁” 双方都在等,我在等待判断如何才能全部收完,即循环结束 ,对方在等待我回复 @Received

  • 主 資深大佬 : FreeWong

    @ipwx 这是一个很常见的网络编程问题,应该不会需要这么复杂去解决。。我只是认定对方协议有瑕疵

  • 資深大佬 : ysc3839

    客户端改不了的情况下有一个可能可行的方案,就是服务端一收到数据就立即发送 Received ,然后 read all 直到断开连接,这个方案可行的前提是客户端会把所有数据发完再断开连接。假设客户端是发送数据、接收数据、断开连接顺序执行的话,这个方案是可行的。

  • 主 資深大佬 : FreeWong

    @ysc3839 看看我的提问的倒数第四行,看看这个办法是否可行

  • 資深大佬 : ipwx

    @FreeWong 不这就是很常见的解决方案。
    只不过在比如 python 语言里面你可以用协程 asyncio ,在 node.js 里面可以用回调,把上面的逻辑实现是个很简单的事情。在 C++ 里面你就不得不用多线程或者 event loop 了。

  • 資深大佬 : ysc3839

    @FreeWong 所以你到底能不能改客户端?能改的话那个方法是可行的。本质是用 # 代替 shutdown 来表示“数据已发完”。

  • 主 資深大佬 : FreeWong

    @ipwx 有兴趣的也愿意的话,用 nodejs 写一个看看。。。

  • 主 資深大佬 : FreeWong

    @ysc3839 单片机是由供应商提供的,如果方法可行可以让供应商下个版本修改

  • 資深大佬 : ysc3839

    @FreeWong 那建议使用 shutdown 的方式,服务端读到连接关闭即可。

  • 資深大佬 : Sricecake

    问题点在于 TCP 是流式的,你却把它当 HTTP 用。
    正常应该是每条数据应该有个 ID 你收到一个# 就回一条 Received(ID)
    你只负责一直循环 ReadTo 处理完一条就回一条 Received
    客户端判断所有消息都 Received 了再断开连接就行。

文章導覽

上一篇文章
下一篇文章

AD

其他操作

  • 登入
  • 訂閱網站內容的資訊提供
  • 訂閱留言的資訊提供
  • WordPress.org 台灣繁體中文

51la

4563博客

全新的繁體中文 WordPress 網站
返回頂端
本站採用 WordPress 建置 | 佈景主題採用 GretaThemes 所設計的 Memory
4563博客
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?
在這裡新增小工具