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

问个 socket 相关问题——recv 接收数据

  •  
  •   bbsfoo · 347 天前 · 1614 次点击
    这是一个创建于 347 天前的主题,其中的信息可能已经有所发展或是发生改变。

    recv 不保证能收到参数里缓冲区大小的数据,譬如说

    int n = recv(client_socket, buffer, sizeof(buffer), 0);

    假设 buffer 的大小是 16 个字节,如果客户端发送 8 个字节,recv 就返回 8

    现在我的包结构是 包长度( 2 个字节) + 包体(可变长度) 程序先接读 2 字节包长度 ushort package_size; int n = recv(client_socket, ( byte *) package_size, sizeof(package_size), 0);

    这个 n 会不会少于 2 如果有这个可能,那么岂不是要重复 recv ?好像很麻烦

    12 条回复    2023-04-17 11:30:05 +08:00
    codehz
        1
    codehz  
       347 天前 via iPhone
    所以不是有 MSG_WAITALL 选项给你用吗)
    不过一般来说不会只给 2 字节就 wait ,因为这意味着至少要读取两次才能把一个包读完(
    通常是给个足够大的 buffer ,然后不停的调用 recv ,直到够用为止,很多时候反而会减少总 syscall 数量
    eastphoton
        2
    eastphoton  
       347 天前
    可能
    hankai17
        3
    hankai17  
       347 天前
    if n < 2 就继续监听读 直到读到长度
    cnbatch
        4
    cnbatch  
       347 天前
    TCP 是流,消息边界需要自己拆。n 确实可以少于 2 。

    数据量少的时候,也许收 /发的数据看起来都是老老实实的,不多不少刚刚好。但数据量大或者受到网络延迟、丢包重传等因素的影响,这就难以保证每次 recv 收到的数据大小都一样。

    例如发送端分两次分别发出“8ABCD” “EFGH”,接收端既有可能会分两次收到“8ABCD” “EFGH”这两段,也有可能会一次性收到“8ABCDEFGH”拼起来的一长串,甚至还可以是“8ABCDEFG” “H”。

    所以最稳妥的做法就是循环调用 recv ,自己设好边界规则逻辑,自己从 TCP 流拆出并重新组合成实际想要的数据。
    liuguangxuan
        5
    liuguangxuan  
       347 天前
    是这样的。
    不止包长度( 2 个字节)有这个问题,包体也有这个问题,都需要不断的重复调用 recv 来等到所需要的字节长度数。

    或者建议直接使用成熟的通信库或者一些消息队列,如 libevent 、grpc 、brpc 等。
    alikesi
        6
    alikesi  
       347 天前 via Android
    没必要分开读,这样的话就是理想情况下你都要读两次才能拼成一个完整的包。
    artnowben
        7
    artnowben  
       347 天前
    TCP 数据是按照 IP 报文来发送 /接收的,IP 报文的 MTU 是 1.5K ,所以大概一次通讯后,数据头部和部分 body 已经在内核里了,至于上层一次调用 recv 取出多少是你自己的事情。
    如果想要更多的了解 TCP/IP 的细节,可以读一读实现的源码,dperf 是专门正对性能测试的一个实现版本,比较精简,容易读,方便调试,国内大厂都在用。
    https://github.com/baidu/dperf/
    bg7lgb
        8
    bg7lgb  
       347 天前
    开缓冲区,把数据接收读进缓冲区,再进行解包。
    darkengine
        9
    darkengine  
       347 天前   ❤️ 1
    又到经典的 TCP 分包与粘包问题时间。
    yolee599
        10
    yolee599  
       346 天前 via Android
    1 楼正解,一般是用一个 512 或者 1024 的 buffer ,反复读取,用状态机,读取到数据的时候就取出来解析更新状态机
    emperinter
        11
    emperinter  
       346 天前
    发送端可以发送两次,一次发送数据长度用于数据接收判断,一次发送完整数据
    julyclyde
        12
    julyclyde  
       346 天前
    https://docs.python.org/zh-cn/3/library/asynchat.html
    虽然这个马上要淘汰了,不过还是建议学习一下
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1243 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 18:03 · PVG 02:03 · LAX 11:03 · JFK 14:03
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.