首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Distributions
Ubuntu
Fedora
CentOS
中文资源站
网易开源镜像站
V2EX  ›  Linux

socket 下 recv 数据的问题

  •  
  •   moxiaowei · 145 天前 · 1795 次点击
    这是一个创建于 145 天前的主题,其中的信息可能已经有所发展或是发生改变。
        def recvMessage(self, sockHandle):#读取来自客户端的数据
            strings = b""
            getNullTime = 0
            client = self.dictSocketHandle[sockHandle]
            num = 1
            totalLen = 0
            while True:
                try:
                    print("第"+str(num)+"次读取数据")
                    data = client.recv(1024)  # 这儿如果没有拿够 1024 个字节的数据,那么会循环回来拿,但是,如果发现没有数据能拿到,socket 会自动中止,扔出一个异常,代码就结束执行,所以需要 try 一下。
                    print(len(data))
                    totalLen += len(data)
                    if len(data) == 0:  # 通道断开或者 close 之后,就会一直收到空字符串。 而不是所谓的-1 或者报异常。这个跟 C 和 java 等其他语言很不一样。
                        self.epollHandle.modify(sockHandle, select.EPOLLHUP | select.EPOLLET)
                        break
                    # print("本次接收到的数据........", data)
                    strings = strings + data
                    getNullTime = 0
    
                except IOError as err:
                    if err.errno == 11:  # 发生 Resource temporarily unavailable 错误 错误码为 11,意为:数据尚未准备好,需要等待
                        if getNullTime >= 7:
                            break
                        else:
                            getNullTime = getNullTime + 1
                            print("第" + str(getNullTime) + "次获取到空数据,继续尝试中.......")
                            time.sleep(0.1)
                        # getNullTime = getNullTime + 1
                        # print("第" + str(getNullTime) + "次获取到空数据,继续尝试中.......")
                    else:
                        print("读取数据,未知 IO 错误")
                        self.epollHandle.modify(sockHandle, select.EPOLLHUP | select.EPOLLET)
                        break
                except:
                    print("未知错误")
                    self.epollHandle.modify(sockHandle, select.EPOLLHUP | select.EPOLLET)
                    break
                num += 1
            print("数据总长度", totalLen)
            return strings
    

    socket 接收数据,数据量稍大,就会断开接收,然后该 socket 会立马再次接收,是什么问题?

    第 75 次读取数据
    2896
    第 76 次读取数据
    4344
    第 77 次读取数据
    7240
    第 78 次读取数据
    第 1 次获取到空数据,继续尝试中.......
    第 79 次读取数据
    第 2 次获取到空数据,继续尝试中.......
    第 80 次读取数据
    第 3 次获取到空数据,继续尝试中.......
    第 81 次读取数据
    数据总长度 755856
    帧为:204
    Unknown opcode %#x.12
    解析数据帧暂时不用的状态
    第 1 次读取数据
    10240
    第 2 次读取数据
    10240
    第 3 次读取数据
    10240
    第 4 次读取数据
    10240
    第 5 次读取数据
    

    我很确定,此时没有第二个 socket 进来,我只是单纯发了一张 1.8M 的图片而已,小的文件是可以的,不知道为啥!!!求教

    23 回复  |  直到 2019-05-28 08:41:31 +08:00
        1
    swulling   145 天前 via iPhone
    socket 是流,你这个 recv 返回空,就认为断开…那自然是不行的。

    你需要先定义一个应用层协议,比如有起始帧加长度,或者有结束帧。
        2
    swulling   145 天前 via iPhone
    另外从你返回 errno 为 11,应该是前面来了非阻塞模式吧,然后加立马加了一个 sleep …

    这不还是 block 了么,为啥要用非阻塞模式…
        3
    robot1   145 天前
    还是没有弄懂流式协议,所以代码就写不对,建议跟一遍<<UNIX 网络编程>>
        4
    firebroo   145 天前
    getNullTime >= 7 这种试探性手法怎么可以用上来。。
        5
    mywaiting   145 天前   ♥ 2
    虽然我不知道贴主写的是什么鬼,但是我觉得很多讨论 TCP 粘包 的同志正在赶来的路上,然后说贴主写的代码导致粘包了~ [手动狗头~]
        6
    cshlxm   145 天前
    def recvdata(self,array):

    view =memoryview(array).cast('B')
    while len(view)>0:
    try:
    nsent=self.socket.recv_into(view)
    view = view[nsent:]
    except socket.error as err:
    self.logger.error('%s connection error %s ,will reconnect '%(self.ip,err))
    self.reconnect()
        7
    moxiaowei   145 天前
    @swulling 生产服务器用阻塞模式,会不会效率太低,一个 socket 一直卡住咋办?
        8
    moxiaowei   145 天前
    @swulling 这个 sleep 是我实在无奈才加上的,其实跟不加一个效果
        9
    swulling   145 天前 via iPhone
    @moxiaowei 非阻塞你加 sleep 和重试就没意义啥,非阻塞读不到就退出是符合预期的,你需要有别的方法之后来继续。


    错误的非阻塞远不如正确的阻塞模式。你还是老老实实写阻塞模式,多开几个线程就好了。
        10
    moxiaowei   145 天前
    具体代码我扔到了 github 上,有没有大神帮我看看问题到底在哪! https://github.com/tangwei123/webSocketServer
        11
    moxiaowei   145 天前
    @firebroo 这个不是试探,如果连续多次接收不到数据,则证明本次接收完毕,可以对接收到的数据进行处理了!
        12
    momocraft   145 天前
    没读到数据凭什么就证明本次接收完毕,要是有人把线拔了呢
        13
    BingoXuan   145 天前 via Android
    1.不需要 sleep,会堵塞
    2.不要 epollet,边缘触发一旦你不去处理,epoll 不会重新通知你
        14
    BingoXuan   145 天前 via Android
    @momocraft
    这是 epoll 的处理,空字符代表关闭,收到对端发送的空字符会忽略
        15
    jason94   145 天前
    我们之前 tcp 连接用 tornado 写的,问题比较少。
        16
    liuminghao233   145 天前
    我觉得是 EPOLLET 的问题

    没用 python 写过网络程序
    但觉得 python+epoll 组合有点奇怪
    就像写 golang 用 epoll 一样莫名其妙
    用 py,go 就是为了写起来爽一点,要是写得跟 c 一样了,那要来干嘛
    我 c++ asio+协程都比这东西爽太多
        17
    moxiaowei   145 天前
    @BingoXuan 是的
        18
    moxiaowei   145 天前
    @BingoXuan 肯定要用 epolllet 的边缘出发,代码能保证每个请求都被处理
        19
    sujin190   145 天前
    if getNullTime >= 7 有啥作用,except 出 errno.EWOULDBLOCK, errno.EAGAIN 就表示数据还未准备好需要稍等通知啊,这个很正常啊,网络栈都是组合好数据一次可以读取多个 ip 包的,那么从内核 copy 到用户空间肯定比网络栈处理速度快啊,所以网络栈出 errno.EWOULDBLOCK, errno.EAGAIN 是正常的,没必要重试,下次如果数据准备好了,再调用 epoll 时又会得到通知
        20
    BingoXuan   145 天前
    @moxiaowei
    我觉得你不应该再重试,如果 errno 为 11 时候,代表着没有新数据了。那么你应该跳出去,等待新 epollin 事件。

    对我而言,堵塞 socket 是通过循环不断去读取数据。为了避免线程堵死,会加上 timeout。通过捕捉 timeout 异常和设定重试次数确保堵塞一定事件就放弃,避免线程堵死。当引入了 epoll 就应该变为等待 epoll 事件而不是等待数据。
        21
    moxiaowei   145 天前
    @BingoXuan 我的 socket 都是非阻塞的模式,数据缓存区有多少读多少
        22
    BingoXuan   145 天前
    @moxiaowei
    既然如此,为什么在 errno=11 时候,你还要继续 recv 呢?这时候缓存区已经没有数据了。你已经完成处理 epoll 的事件的所有东西了。你应该跳出去等待下一次 epoll 事件。
        23
    moxiaowei   144 天前
    @BingoXuan 其实我是以防万一的
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2215 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 25ms · UTC 10:21 · PVG 18:21 · LAX 03:21 · JFK 06:21
    ♥ Do have faith in what you're doing.