如何在Python中的另一个线程中终止socket.recv()函数?

20

我有一个等待连接的主线程。它会生成客户端线程,以回传来自客户端(在本例中为telnet)的响应。但是,假设我想在一段时间后关闭所有套接字和所有线程,例如在1个连接之后。

我该怎么办?如果我从主线程中执行 clientSocket.close(),它将无法停止进行 recv 操作。只有在我通过 telnet 先发送一些东西后,它才会失败并停止进行其他发送和接收操作。

我的代码看起来像这样:

# Echo server program
import socket
from threading import Thread
import time

class ClientThread(Thread):
    def __init__(self, clientSocket):
            Thread.__init__(self)
            self.clientSocket = clientSocket

    def run(self):
            while 1:
                    try:
                            # It will hang here, even if I do close on the socket
                            data = self.clientSocket.recv(1024)
                            print "Got data: ", data
                            self.clientSocket.send(data)
                    except:
                            break

            self.clientSocket.close()

HOST = ''
PORT = 6000
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serverSocket.bind((HOST, PORT))
serverSocket.listen(1)

clientSocket, addr = serverSocket.accept()
print 'Got a new connection from: ', addr
clientThread = ClientThread(clientSocket)
clientThread.start()

time.sleep(1)

# This won't make the recv in the clientThread to stop immediately,
# nor will it generate an exception
clientSocket.close()

由于CPython拥有全局解释器锁,因此您无法使用线程实现它。请参阅http://docs.python.org/c-api/init.html#threads。 - badp
4个回答

28

我知道这是一个旧帖子,Samuel很可能早就解决了他的问题。然而,我遇到了同样的问题并在谷歌搜索时找到了这篇文章。我找到了一个解决方案,并认为值得添加。

您可以在socket类上使用shutdown方法。它可以防止进一步的发送、接收或两者同时发生。

socket.shutdown(socket.SHUT_WR)

上述代码示例防止了未来的发送操作。

请参阅Python文档获取更多信息。


10
我不确定你所要求的是否可行,但这似乎并不必要。如果没有数据可读,请不要从套接字中读取;使用 select.select 检查套接字是否有数据可用。
data = self.clientSocket.recv(1024)
print "Got data: ", data
self.clientSocket.send(data)

转换成类似这样的内容:

r, _, _ = select.select([self.clientSocket], [], [])
if r:
    data = self.clientSocket.recv(1024)
    print "Got data: ", data
    self.clientSocket.send(data)

编辑:如果您想防范套接字已关闭的可能性,请捕获socket.error

do_read = False
try:
    r, _, _ = select.select([self.clientSocket], [], [])
    do_read = bool(r)
except socket.error:
    pass
if do_read:
    data = self.clientSocket.recv(1024)
    print "Got data: ", data
    self.clientSocket.send(data)

1
但是使用select也会出现同样的问题。如果我关闭socket,它在执行select.select()时会报错“坏文件描述符”。我看到你的解决方案出现在我发布我的解决方案之前。你对我那里使用超时的方法有什么想法? - Samuel Skånberg
我尝试使用select now做同样的事情。它的效果和使用超时一样好,但只有在我启动线程后立即关闭套接字时才有效。如果我执行time.sleep(1),它将失败。 - Samuel Skånberg

3
我找到了一个使用超时的解决方案。这将会打断recv(实际上在超时之前,这非常好):
# Echo server program
import socket
from threading import Thread
import time


class ClientThread(Thread):
    def __init__(self, clientSocke):
        Thread.__init__(self)
        self.clientSocket = clientSocket

    def run(self):
        while 1:
            try:
                data = self.clientSocket.recv(1024)
                print "Got data: ", data
                self.clientSocket.send(data)
            except socket.timeout: 
                # If it was a timeout, we want to continue with recv
                continue
            except:
                break

        self.clientSocket.close()

HOST = ''
PORT = 6000
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serverSocket.bind((HOST, PORT))
serverSocket.listen(1)

clientSocket, addr = serverSocket.accept()
clientSocket.settimeout(1)

print 'Got a new connection from: ', addr
clientThread = ClientThread(clientSocket)
clientThread.start()

# Close it down immediatly 
clientSocket.close()

在C#中有一个非常有用的方法叫做WaitAny,我一直在为此目的使用它(如果有其他事件需要处理,则停止等待套接字数据)。Python确实缺乏这样的功能:( 赞成您的变体。但是,如果有很多线程(而且超时只有一秒钟),我认为它会稍微加重CPU负担... - sunsay

2

我必须为下面的评论道歉。之前@Matt Anderson的评论是正确的。是我在尝试时犯了一个错误,导致了我下面的帖子。

使用timeout并不是一个非常好的解决方案。它可能看起来只是短暂地唤醒再回到睡眠状态,但是我曾经见过它极大地影响了应用程序的性能。您有一个操作,大部分时间都希望阻塞直到有数据可用,因此永远休眠。然而,如果您想出于某些原因中止操作,例如关闭应用程序,则关键问题是如何退出。对于套接字,可以使用select,并在两个套接字上进行监听:主要套接字和特殊的关闭套接字。不过,创建关闭套接字有点麻烦。您必须创建它。您必须获取侦听套接字以接受它。您必须跟踪此管道的两端。我在Synchronized Queue类中遇到了同样的问题。不过,在那里,您至少可以将虚拟对象插入队列中以唤醒get()。这要求虚拟对象不像您的正常数据。有时我希望Python有类似于Windows API的WaitForMultipleObjects。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接