首页 > 解决方案 > 当线程和服务器崩溃时,recv 命令无法正常工作

问题描述

我使用 Python 套接字库设置了一个 TCP 服务器,可以连接多个设备。由于我在这里使用线程,我定期接受新的传入连接并尝试在列表中招募消息。我的问题是,通常 recv 线程会卡在某个地方。有时,运行平稳的服务器有时会在第一天挂起。显示传入连接但未接收消息。c 在客户端,所以当我在进入 recv 命令之前打印每个线程连接时

Now on <socket.socket fd=8, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('172.31.15.96', 9680), raddr=('24.133.144.150', 56446)>
Now on <socket.socket fd=8, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('172.31.15.96', 9680), raddr=('24.133.144.150', 56446)>
Now on <socket.socket fd=8, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('172.31.15.96', 9680), raddr=('24.133.144.150', 56446)>
Now on <socket.socket fd=8, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('172.31.15.96', 9680), raddr=('24.133.144.150', 56446)>
Now on <socket.socket fd=8, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('172.31.15.96', 9680), raddr=('24.133.144.150', 56446)>
Now on <socket.socket fd=8, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('172.31.15.96', 9680), raddr=('24.133.144.150', 56446)>
Now on <socket.socket fd=8, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('172.31.15.96', 9680), raddr=('24.133.144.150', 56446)>
Now on <socket.socket fd=9, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('172.31.15.96', 9680), raddr=('24.133.144.150', 56456)>

在循环中,它继续显示相同的端口(可能超过一千次),直到它从手机发送一条新消息。我正在展示有问题的部分,因为我编写的代码在 recv 之后大约有 500 行。


import socket
import threading

host = '172.31.15.96'
port = 9680   

try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    print("socket created")
    s.bind((host, port))
    print("socket connected {} port".format(port))
    s.listen(250)
    print("socket listening")
    db.logHere.insert_one({"socket log": {"socket created and port opened": port, "Time": time()}})

except socket.error as msg:
    print("Error:", msg)
    db.logHere.insert_one(
        {"Socket Log": {"Error happened": msg, "Time": time()}})

clients = [] # this is list for connected client

def accept():
    while True:
        global clients
        client, addr = s.accept()
        print('Connection from:', addr)
        db.logHere.insert_one({"Connection Log": {"Connection ip from": str(addr[0])+":"+str(addr[1]), "Connection time": time()}, "Time": time()})
        clients.append(client)

def new_message():
    while True:
        for c in clients:
            print("Now on", c) # I just try to use this print if new message thread work or stopped
            try:
                message = c.recv(2048)
                if message:
                    #there is my split or sendall commands. I put if messages inside try advice from : Marquis of Lorne*
            except socket.error as err:
                print("socket error", err)
                c.close()

threading.Thread(target=accept).start()
threading.Thread(target=new_message).start()

我应该怎么做才能解决这个问题并能够正常操作每个传入的连接,即维护一个健康的服务器?我认为我的主要问题是我无法删除客户端列表中断开连接的客户端。尝试除了是不够的。我如何理解其中一个客户端断开连接?

**对于我不完整和不正确的语言使用,我深表歉意。我的母语不是英语。

标签: pythonmultithreadingsocketstcp

解决方案


这是对您的代码的最小修复,select用于仅读取具有可用数据的客户端套接字并从列表中删除已关闭的套接字:

...
def new_message():
    while True:
        # don't try to wait on a empty list...
        if len(clients) == 0:
            sleep(5)
        else:
            ready, _, _ = select.select(clients, [], [])
            # only loop on sockets having available data to read
            for c in ready:
                print("Now on", c) # I just try to use this print if new message thread work or stopped
                try:
                    message = c.recv(2048)
                    if message:
                        #there is my split or sendall commands. I put if messages inside try advice from : Marquis of Lorne*
                        print(message.decode())
                    else:
                        clients.remove(c)
                except socket.error as err:
                    ...

但实际上,您可以将侦听套接字包含在选择列表中,并完全丢弃一个线程:

...
clients = [s] # this is list for connected client AND the listening socket

##def accept():
##    while True:
##        global clients
##        client, addr = s.accept()
##        print('Connection from:', addr)
##        db.logHere.insert_one({"Connection Log": {"Connection ip from": str(addr[0])+":"+str(addr[1]), "Connection time": time()}, "Time": time()})
##        clients.append(client)

def new_message():
    while True:
        ready, _, _ = select.select(clients, [], [])
        for c in ready:                    # loop over ready sockets
            if c == s:                     # special processing for listening one
                try:
                    client, addr = s.accept()
                except socket.error:       # exit if the listening socket is closed
                    for x in clients:
                        x.close()
                    return 
                print('Connection from:', addr)
                db.logHere.insert_one({"Connection Log": {"Connection ip from": str(addr[0])+":"+str(addr[1]), "Connection time": time()}, "Time": time()})
                clients.append(client)
            else:
                print("Now on", c) # I just try to use this print if new message thread work or stopped
                try:
                    message = c.recv(2048)
                    if message:
                        #there is my split or sendall commands. I put if messages inside try advice from : Marquis of Lorne*
                        print(message.decode())
                    else:
                        clients.remove(c)
                except socket.error as err:
                    print("socket error", err)
                    c.close()

##threading.Thread(target=accept).start()
threading.Thread(target=new_message).start()

推荐阅读