78

基本上,我已经在几个地方读过,它们socket.recv()将返回它可以读取的任何内容,或者一个表明对方已关闭的空字符串(官方文档甚至没有提到连接关闭时它返回的内容......伟大的!)。这对于阻塞套接字来说很好而且很花哨,因为我们知道recv()只有在实际有东西要接收时才返回,所以当它返回一个空字符串时,它一定意味着对方已经关闭了连接,对吧?

好的,好的,但是当我的套接字非阻塞时会发生什么?我已经搜索了一下(可能还不够,谁知道?)并且无法弄清楚如何判断对方何时使用非阻塞套接字关闭了连接。似乎没有方法或属性可以告诉我们这一点,并且将 的返回值recv()与空字符串进行比较似乎完全没用......只是我有这个问题吗?

举个简单的例子,假设我的套接字超时设置为 1.2342342 (你喜欢这里的任何非负数)秒,我打电话给socket.recv(1024),但对方在 1.2342342 秒期间没有发送任何内容。该recv()调用将返回一个空字符串,我不知道连接是否仍然存在......

4

4 回答 4

92

在没有可用数据的非阻塞套接字的情况下,recv 将抛出 socket.error 异常,并且异常的值将具有 EAGAIN 或 EWOULDBLOCK 的 errno。例子:

import sys
import socket
import fcntl, os
import errno
from time import sleep

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1',9999))
fcntl.fcntl(s, fcntl.F_SETFL, os.O_NONBLOCK)

while True:
    try:
        msg = s.recv(4096)
    except socket.error, e:
        err = e.args[0]
        if err == errno.EAGAIN or err == errno.EWOULDBLOCK:
            sleep(1)
            print 'No data available'
            continue
        else:
            # a "real" error occurred
            print e
            sys.exit(1)
    else:
        # got a message, do something :)

socket.settimeout(n)在您通过使用或的超时启用非阻塞行为的情况下,情况会有所不同socket.setblocking(False)。在这种情况下,仍然会引发 socket.error,但在超时的情况下,异常的伴随值始终是设置为“超时”的字符串。因此,要处理这种情况,您可以执行以下操作:

import sys
import socket
from time import sleep

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1',9999))
s.settimeout(2)

while True:
    try:
        msg = s.recv(4096)
    except socket.timeout, e:
        err = e.args[0]
        # this next if/else is a bit redundant, but illustrates how the
        # timeout exception is setup
        if err == 'timed out':
            sleep(1)
            print 'recv timed out, retry later'
            continue
        else:
            print e
            sys.exit(1)
    except socket.error, e:
        # Something else happened, handle error, exit, etc.
        print e
        sys.exit(1)
    else:
        if len(msg) == 0:
            print 'orderly shutdown on server end'
            sys.exit(0)
        else:
            # got a message do something :)

正如评论中所指出的,这也是一个更便携的解决方案,因为它不依赖于操作系统特定的功能来将套接字置于非阻塞模式。

有关详细信息,请参阅recv(2)python 套接字

于 2013-05-25T01:26:26.123 回答
9

很简单:ifrecv()返回 0 个字节;您将不会在此连接上收到更多数据。曾经。您仍然可以发送。

这意味着如果没有数据可用但连接仍然存在(另一端可能发送),您的非阻塞套接字必须引发异常(它可能取决于系统)。

于 2013-05-25T01:43:34.167 回答
8

当您使用recv连接时,select如果套接字已准备好从中读取但没有要读取的数据,则意味着客户端已关闭连接。

这是一些处理此问题的代码,还请注意recv在 while 循环中第二次调用时引发的异常。如果没有什么可读取的,则会抛出此异常,这并不意味着客户端已关闭连接:

def listenToSockets(self):

    while True:

        changed_sockets = self.currentSockets

        ready_to_read, ready_to_write, in_error = select.select(changed_sockets, [], [], 0.1)

        for s in ready_to_read:

            if s == self.serverSocket:
                self.acceptNewConnection(s)
            else:
                self.readDataFromSocket(s)

以及接收数据的函数:

def readDataFromSocket(self, socket):

    data = ''
    buffer = ''
    try:

        while True:
            data = socket.recv(4096)

            if not data: 
                break

            buffer += data

    except error, (errorCode,message): 
        # error 10035 is no data available, it is non-fatal
        if errorCode != 10035:
            print 'socket.error - ('+str(errorCode)+') ' + message


    if data:
        print 'received '+ buffer
    else:
        print 'disconnected'
于 2013-05-25T01:02:32.287 回答
2

只是为了完成现有的答案,我建议使用 select 而不是 nonblocking sockets。关键是非阻塞套接字使事情复杂化(可能除了发送),所以我想说根本没有理由使用它们。如果您经常遇到应用程序被阻塞等待 IO 的问题,我也会考虑在后台的单独线程中执行 IO。

于 2013-05-25T05:46:10.800 回答