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

Python socket recv 老是收不全数据怎么办?

  •  
  •   userlol · 2018-09-17 15:52:07 +08:00 · 8512 次点击
    这是一个创建于 2262 天前的主题,其中的信息可能已经有所发展或是发生改变。

    部分代码:

    #平台限制,只能用 Python2.x 实现
    import socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('0.0.0.0',80)
    sock.listen(1)
    

    下面是 recv 应该收到的完整数据(HTTP 请求):

    POST /data/fetchdata HTTP/1.1
    Host: www.demo.com
    Accept: application/json
    Accept-Encoding: gzip, deflate
    Content-Length: 8
    Cookie: debug=1
    Connection: closed
    
    result=1
    

    一开始为了接收数据写的的代码:

    buf=''
    While True:
        buf = buf + client.recv(1024)
        if not buf or len(buf)==0:
        	break #结果是从来不 break
    

    再试

    buf = client.recv(4096)
    

    这个只能收到一部分数据(HTTP response body 收不到)。这里有个问题,无论把 buffer 调整多大,第一次 recv 的返回值总是 HTTP response header 部分的数据,很奇怪

    后来试了

    buf = client.recv(1024)  #成功接收 HTTP response header
    print "123"
    buf = client.recv(1024)  #应该接收 HTTP response body,但是一直 blocking
    print "456"
    

    这样的,发现 456 根本不输出。

    快速 Google 之后也没发现什么适合的解决方法,唯一一个靠谱点的是用 try except 配合自定义 timeout 来检测是否 blocking,缺点就是在网络环境比较恶劣的情况下(比如我现在的)也会丢数据。

    向大家求教,谢谢!

    第 1 条附言  ·  2018-09-17 21:42:08 +08:00
    谢谢各位,不一一回了,已经做好了,回复有空再看。本来打算找无限 recv,recv 不到数据被动想办法断开的办法的,刚才做好了,是从 HTTP 协议入手的,代码根据协议规定 recv 到一定次数主动跳出,避免 recv 到空数据而进入 blocking 状态。
    第 2 条附言  ·  2018-09-17 21:45:58 +08:00
    PS:
    虽然问题解决了,但是很好奇有时候第一次 recv(8192 或者 1024)时数据只能收到 HTTP request header,body 还得再获取一次,body 是很短的,更奇怪的是,第二次 recv 只收到了几字节的 body,第三次 recv 才把剩下的几个字节收全,很奇怪。
    13 条回复    2018-09-18 09:37:19 +08:00
    neoblackcap
        1
    neoblackcap  
       2018-09-17 17:38:29 +08:00   ❤️ 1
    你自己不 parse 就想读出来? tcp 又不保证你一次就收到全部数据
    userlol
        2
    userlol  
    OP
       2018-09-17 17:44:03 +08:00
    @neoblackcap 求教,循环执行 recv 收所有数据没关系,但是如果最后一次数据收空了,recv 就又进入等待状态了,这个怎么解决?
    Tyanboot
        3
    Tyanboot  
       2018-09-17 18:10:59 +08:00 via Android   ❤️ 1
    @userlol 这就是流的处理问题了。HTTP 还行,可以读一部分就解析一部分,拿到 content-length 之后就立刻再算你还需要接受多少。
    neoblackcap
        4
    neoblackcap  
       2018-09-17 18:26:16 +08:00 via iPhone   ❤️ 1
    @userlol 如 3 楼所说,你必须先循环读出头部,然后才按长度去读剩下的部分。
    还有就是你读到尾部,也有可能是一部分尾部,一部分是另外一个请求,记得将他们分开
    neoblackcap
        5
    neoblackcap  
       2018-09-17 18:27:57 +08:00 via iPhone
    而且你想性能好些应该用 epoll 来监听 socket 是否可读,用另外一个线程去读
    sampeng
        6
    sampeng  
       2018-09-17 18:28:04 +08:00
    content-length 就是干这个用的。。。
    miniliuke
        7
    miniliuke  
       2018-09-17 19:07:17 +08:00 via Android   ❤️ 1
    http 应该是短链接( http2 除外,我不太了解),你循环读把数据拼接起来就行,直到服务器关闭连接结束......一般一次连接不会传两个 http 数据的......你直接读到 socket 关闭就行
    misaka19000
        8
    misaka19000  
       2018-09-17 19:31:28 +08:00   ❤️ 1
    最近刚刚用 go 写过一个程序,里面包含了对 HTTP 请求的简单解析,楼主可以参考下,Python 实现起来也差不多

    https://gist.github.com/RitterHou/e77b38acdd30d30ee3b9f19004940d3c
    ysc3839
        9
    ysc3839  
       2018-09-17 20:06:34 +08:00 via Android   ❤️ 1
    buf=''
    While True:
    . recv = client.recv(1024)
    . if not recv or len(recv) == 0:
    . break
    . else:
    . buf += recv
    Jamy
        10
    Jamy  
       2018-09-17 20:22:31 +08:00   ❤️ 1
    >buf = client.recv(1024) #应该接收 HTTP response body,但是一直 blocking
    >print "456"
    这说明缓冲区里没数据了,
    检查一下发送端是不是没发送完整.
    需要注意下.send 方法可能会只发送部分数据,要自己检查返回值.
    pabupa
        11
    pabupa  
       2018-09-17 20:40:01 +08:00   ❤️ 1
    lcj2class
        12
    lcj2class  
       2018-09-17 20:50:37 +08:00 via Android   ❤️ 2
    之前总结过这类情况,可参考:
    https://liujiacai.net/blog/2016/10/31/socket-programming/
    BingoXuan
        13
    BingoXuan  
       2018-09-18 09:37:19 +08:00 via Android
    tcp 又不保证你的数据能快速按顺序到达缓存区,所以一直接收直到识别到 eof 或特定结束符就可以了。我还遇过收数据收到前面一部分数据,后面死活收不了,一关闭就收到剩下的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3489 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 11:00 · PVG 19:00 · LAX 03:00 · JFK 06:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.