我是C的新手并编写TCP服务器,并且想知道如何从发送服务器将响应的命令的客户端处理recv() . 为了这个问题,我们只说头是第1个字节,命令标识符是第2个字节,有效载荷长度是第3个字节,然后是有效载荷(如果有的话) .
recv()这些数据的最佳方法是什么?我想调用recv()来读入缓冲区中的前3个字节,检查以确保头和命令标识符有效,然后检查有效负载长度并再次调用recv(),并将有效负载长度作为长度并将其添加到回到前面提到的缓冲区 . 阅读Beej的网络文章(特别是这里的部分:http://beej.us/guide/bgnet/output/html/singlepage/bgnet.html#sonofdataencap),然而,他建议使用"an array big enough for two [max length] packets"来处理诸如获取下一个数据包之类的情况 .
处理这些类型的recv()的最佳方法是什么?基本问题,但我想有效地实施它,处理可能出现的所有情况 . 提前致谢 .
4 回答
Beej提到的方法和AlastairG提到的方法是这样的:
对于每个并发连接,您维护一个已读但尚未处理的数据的缓冲区 . (这是Beej建议调整到最大数据包长度两倍的缓冲区) . 显然,缓冲区从空开始:
只要您的套接字可读,请读入缓冲区中的剩余空间,然后立即尝试处理您拥有的内容:
process_buffer()
将尝试将数据包中的数据作为数据包进行处理 . 如果缓冲区尚未包含完整数据包,则只返回 - 否则,它会处理数据并将其从缓冲区中删除 . 所以对于你的示例协议,它看起来像:(
do_command()
函数将检查有效的头和命令字节) .这种技术最终是必要的,因为任何
recv()
都可以返回一个很短的长度 - 使用您提出的方法,如果您的有效负载长度为500,会发生什么,但下一个recv()
只返回400字节?你必须保存那400个字节,直到下一次套接字变得可读为止 .处理多个并发客户端时,每个客户端只需要一个
recv_buffer
和recv_len
,并将它们填充到每个客户端结构中(这可能包含其他内容 - 如客户端的套接字,可能是它们的源地址,当前状态等) .好问题 . 你想要多么完美?对于所有歌唱所有舞蹈解决方案,使用异步套接字,尽可能读取所有数据,并且每当您获得新数据时,在缓冲区上调用一些数据处理功能 .
这允许你做大读 . 如果您获得大量流水线命令,则可以在不必再次等待套接字的情况下处理它们,从而提高性能和响应时间 .
在写作上做类似的事情 . 那就是命令处理函数写入缓冲区 . 如果缓冲区中有数据,则在检查套接字(select或poll)时检查可写性并尽可能多地写入,记住只删除实际从缓冲区写入的字节 .
循环缓冲区在这种情况下运行良好 .
有更简单的解决方案 . 不过这个很好 . 请记住,服务器可能会获得多个连接,并且可以拆分数据包 . 如果从套接字读入缓冲区只是为了找不到完整命令的数据,那么你对已经读过的数据做了什么?你在哪里存放?如果将它存储在与该连接相关联的缓冲区中,那么您也可以完全按照上面的描述读取缓冲区 .
此解决方案还避免了为每个连接生成单独的线程 - 您可以处理任意数量的连接而不会出现任何实际问题 . 每个连接产生一个线程是一种不必要的系统资源浪费 - 除非在某些情况下建议使用多个线程,为此你可以让工作线程执行这样的阻塞任务,同时保持套接字处理单线程 .
基本上我同意你所说的Beej所说的,但是不要一次读掉一点点的东西 . 一次读大块 . 编写像这样的套接字服务器,基于一点点套接字经验和手册页进行学习和设计,这是我曾经做过的最有趣的项目之一,非常教育 .
Alastair描述的解决方案在性能方面是最好的 . 仅供参考 - 异步编程也称为事件驱动编程 . 换句话说,您等待数据进入套接字,将其读入缓冲区,处理什么/何时可以,然后重复 . 您的应用程序可以在读取数据和处理数据之间执行其他操作
我发现一些非常相似的链接:
http://www.kegel.com/c10k.html
http://software.schmorp.de/pkg/libev.html
第二个是一个很棒的库来帮助实现所有这些 .
至于使用缓冲区和尽可能多地阅读,这是另一个表现的事情 . 批量读取更好,系统调用(读取)更少 . 当您决定有足够的处理时,您可以处理缓冲区中的数据,但确保一次只处理一个“数据包”(您使用3字节 Headers 描述的数据包),而不是破坏缓冲区中的其他数据 .
如果你使用多个连接,基本上有两个假设,那么处理多个连接(无论是监听套接字,readfd还是writefd)的最佳方式是select / poll / epoll . 您可以根据您的要求使用其中任何一种 .
在你的第二个查询中如何处理多个recv()这种做法可以使用:每当数据到达时,只需查看 Headers (它应该是你所描述的固定长度和格式) .
通过这个你得到你的头,你可以验证参数,并提取完整的消息长度 . 获得完整的msg长度后,只需收到完整的消息
所以这样你就不需要采用任何具有固定长度的数组,你可以轻松实现你的逻辑 .