Python:多线程套接字服务器在客户端意外停止时无休止地运行

时间:2018-04-16 06:00:03

标签: python multithreading sockets

我创建了一个多线程套接字服务器,使用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()

1 个答案:

答案 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()返回某些内容时,它可能意味着以下五种情况之一:

  1. 客户端已关闭连接但未返回任何内容
  2. 只有一条完整的消息,您收到了
  3. 只有部分消息。要么是因为您在客户端传输了所有数据之前读取了套接字,要么是因为客户端发送了超过2048个字节(即使您的客户端从未发送超过2048个字节,恶意客户端肯定会尝试这个)
  4. 有多条消息在等待,而您收到的所有消息都
  5. 为4,但最后一条消息是部分消息。
  6. 大多数套接字编程错误都与此有关。程序员预计2会发生(就像你现在一样),但他们不满足3-5。您应该分析收到的内容并采取相应的行动。如果数据似乎比完整消息少,请将其存储在某处并等待显示更多数据。当出现更多数据时,请连接这些数据,看看您现在是否有完整的消息。当您从此缓冲区解析完整的消息时,检查缓冲区以查看是否有更多数据 - 下一条消息的第一部分,如果您的客户端速度很快且服务器速度很慢,则甚至会有更多完整的消息。如果您处理消息然后擦除缓冲区,则可能还从下一条消息中擦除了字节。