我创建了一个多线程套接字服务器,使用python将许多客户端连接到服务器。如果客户端由于异常而意外停止,则服务器会不间断运行。有没有办法在服务器中单独杀死该特定线程,其余运行
服务器:
class ClientThread(Thread):
def __init__(self,ip,port):
Thread.__init__(self)
self.ip = ip
self.port = port
print("New server socket thread started for " + ip + ":" + str(port))
def run(self):
while True :
try:
message = conn.recv(2048)
dataInfo = message.decode('ascii')
print("recv:::::"+str(dataInfo)+"::")
except:
print("Unexpected error:", sys.exc_info()[0])
Thread._stop(self)
tcpServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcpServer.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
tcpServer.bind((TCP_IP, 0))
tcpServer.listen(10)
print("Port:"+ str(tcpServer.getsockname()[1]))
threads = []
while True:
print( "Waiting for connections from clients..." )
(conn, (ip,port)) = tcpServer.accept()
newthread = ClientThread(ip,port)
newthread.start()
threads.append(newthread)
for t in threads:
t.join()
客户端:
def Main():
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((host,int(port)))
while True:
try:
message = input("Enter Command")
s.send(message.encode('ascii'))
except Exception as ex:
logging.exception("Unexpected error:")
break
s.close()
答案 0 :(得分:1)
对于一个非常非常长的答案感到抱歉,但这里有。
您的代码存在很多问题。首先,您的客户端实际上并没有关闭套接字,因为s.close()
永远不会被执行。您的循环在break
处被中断,其后面的任何内容都将被忽略。因此,为了良好的编程,请更改这些语句的顺序,但这与您的问题无关。
您的服务器代码在很多方面都是错误的。正如它目前所写,它永远不会退出。你的线程也不能正常工作。我修复了你的代码,使它成为一个工作的多线程服务器,但它仍然没有退出,因为我不知道什么是让它退出的触发器。但是让我们从主循环开始:
while True:
print( "Waiting for connections from clients..." )
(conn, (ip,port)) = tcpServer.accept()
newthread = ClientThread(conn, ip,port)
newthread.daemon = True
newthread.start()
threads.append(newthread) # Do we need this?
for t in threads:
t.join()
我已经将conn
传递给您的客户端线程,其原因在一瞬间变得明显。但是,您的while True
循环永远不会中断,因此您永远不会进入加入线程的for循环。如果您的服务器要无限期运行,这根本不是问题。只需删除for循环,这部分就可以了。您不需要为了加入它们而加入线程。连接线程只允许程序阻塞,直到线程执行完毕。
另一个补充是newthread.daemon = True
。这会将您的线程设置为daemonic,这意味着它们将在主线程退出时立即退出。现在,即使存在活动连接,您的服务器也会响应control + c。
如果您的服务器永远不会结束,也不需要将主循环中的线程存储到threads
列表中。这个列表不断增长,因为每次客户端连接和断开连接时都会添加一个新条目,这会泄漏内存,因为您没有使用threads
列表。我保持原样,但仍然没有退出无限循环的机制。
然后让我们继续你的线程。如果要简化代码,可以使用函数替换运行部件。在这种情况下没有必要继承Thread,但是这样可以保持你的结构:
class ClientThread(Thread):
def __init__(self,conn, ip,port):
Thread.__init__(self)
self.ip = ip
self.port = port
self.conn = conn
print("New server socket thread started for " + ip + ":" + str(port))
def run(self):
while True :
try:
message = self.conn.recv(2048)
if not message:
print("closed")
try:
self.conn.close()
except:
pass
return
try:
dataInfo = message.decode('ascii')
print("recv:::::"+str(dataInfo)+"::")
except UnicodeDecodeError:
print("non-ascii data")
continue
except socket.error:
print("Unexpected error:", sys.exc_info()[0])
try:
self.conn.close()
except:
pass
return
首先,我们将conn
存储到self.conn
。您的版本使用了conn
变量的全球版本。当您与服务器有多个连接时,这会导致意外结果。 conn
实际上是在accept时为客户端连接创建的新套接字,这对每个线程都是唯一的。这是服务器区分客户端连接的方式。它们侦听已知端口,但是当服务器接受连接时,accept会为该特定连接创建另一个端口并将其返回。这就是为什么我们需要将此传递给线程,然后从self.conn
而不是全局conn
读取。
您的服务器"挂了"客户端连接错误,因为没有机制在您的循环中检测到这一点。如果客户端关闭连接,socket.recv()
不会引发异常但不返回任何内容。这是您需要检测的条件。我相当确定你甚至不需要尝试/除了这里但它没有伤害 - 但你需要添加你期望的异常。在这种情况下,使用未声明的except
捕获所有内容是错误的。您还有另一个声明可能引发异常。如果你的客户端发送了一些无法使用ascii编解码器解码的内容,你会得到UnicodeDecodeError
(尝试这里没有错误处理,telnet到你的服务器端口,并将一些希伯来语或日语复制到连接中,看看会发生什么)。如果您刚刚捕获了所有内容并将其视为套接字错误,那么您现在只需因为无法解析消息而输入代码的线程结尾部分。通常我们只是忽略"非法"消息并继续。我添加了这个。如果你想在收到" bad"消息,只需将self.conn.close()
和return
添加到此异常处理程序中。
然后,当您确实遇到套接字错误 - 或者客户端已关闭连接时,您将需要关闭套接字并退出该线程。你将在套接字上调用close()
- 将它封装在try /中,因为你不关心它是否因为不再存在而失败。
如果您要退出线程,只需return
循环run()
。执行此操作时,您的线程将按顺序退出。就如此容易。
然后还有另一个潜在的问题,如果您不仅打印消息,而且正在解析它们并对您收到的数据执行某些操作。这个我不解决,但留给你。
TCP套接字传输数据,而不是消息。构建通信协议时,不能假设当recv
返回时,它将返回单个消息。当您的recv()
返回某些内容时,它可能意味着以下五种情况之一:
大多数套接字编程错误都与此有关。程序员预计2会发生(就像你现在一样),但他们不满足3-5。您应该分析收到的内容并采取相应的行动。如果数据似乎比完整消息少,请将其存储在某处并等待显示更多数据。当出现更多数据时,请连接这些数据,看看您现在是否有完整的消息。当您从此缓冲区解析完整的消息时,检查缓冲区以查看是否有更多数据 - 下一条消息的第一部分,如果您的客户端速度很快且服务器速度很慢,则甚至会有更多完整的消息。如果您处理消息然后擦除缓冲区,则可能还从下一条消息中擦除了字节。