多线程程序中的Python套接字与类/对象相互干扰

问题描述 投票:0回答:1

作为对我工作的帮助,我正在使用 Python 3.12(采用面向对象的方法)开发基于 TCP 套接字的设备之间的通信模拟器。 基本上,SERVER 类型通信通道与 CLIENT 类型之间的区别仅基于套接字实例化的方式:分别,服务器监听/接受连接请求,而客户端主动连接到其端点。 一旦建立连接,任何一方都可以开始传输某些内容,另一方接收并处理该内容,然后做出响应(当然这是在同一套接字对上)。 如你看到的。该模拟器有一个基于

Tkinter
的简单界面 您可以在网格布局中创建最多 4 个通道,在本例中我们有两个:

当用户单击

CONNECT
按钮时,框架类中该按钮的侦听器中会发生以下情况:

class ChannelFrame(tk.Frame):

        channel = None #istance of channel/socket type
        
        def connectChannel(self):
            port = self.textPort.get();
            if self.socketType.get() == 'SOCKET_SERVER':
                self.channel = ChannelServerManager(self,self.title,port)
            elif self.socketType.get() == 'SOCKET_CLIENT':
                ipAddress = self.textIP.get()
                self.channel = ChannelClientManager(self,self.title,ipAddress,port)

然后我有一个服务器类型的通道和一个客户端类型的通道的实现。他们的构造函数基本上收集接收到的数据并创建一个主线程,其目的是创建套接字,然后:

1a) 在套接字客户端的情况下连接到对方

1b) 在套接字服务器的情况下等待连接请求

2.) 使用

select.select
进入主循环,并在其框架的文本区域中跟踪接收和发送的数据

这是主线程Client的代码

class ChannelClientManager():

        establishedConn = None
        receivedData = None
        eventMainThread = None #set this event when user clicks on DISCONNECT button
        
        def threadClient(self):
            self.socketsInOut.clear()
            self.connected = False
            
            while True:
                if (self.eventMainThread.is_set()):
                    print(f"threadClient() --> ChannelClient {self.channelId}: Socket client requested to shut down, exit main loop")
                    break;
                
                
                if(not self.connected):
                    try :
                        self.establishedConn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                        self.establishedConn.connect((self.ipAddress, int(self.port)))
                        self.channelFrame.setConnectionStateChannel(True)
                        self.socketsInOut.append(self.establishedConn)
                        self.connected = True
                    #keep on trying to connect to my counterpart until I make it
                    except socket.error as err:
                        print(f'socket.error threadClient() --> ChannelClient {self.channelId}: Error while connecting to server: {err}')
                        time.sleep(0.5)
                        continue
                    except socket.timeout as sockTimeout:
                        print(f'socket.timeout threadClient() -->  ChannelClient {self.channelId}: Timeout while connecting to server: {sockTimeout}')
                        continue
                    except Exception as e:
                        print(f'Exception on connecting threadClient() -->  ChannelClient {self.channelId}: {e}')
                        continue
                    
                
                if(self.connected):
                    try:
                        
                        r, _, _ = select.select(self.socketsInOut, [], [], ChannelClientManager.TIMEOUT_SELECT)
                        
                        if len(r) > 0: #socket ready to be read with incoming data
                            for fd in r:
                                data = fd.recv(1)
                                if data:
                                    self.manageReceivedDataChunk(data)
                                else:
                                    print(f"ChannelClient {self.channelId}: Received not data on read socket, server connection closed")
                                    self.closeConnection()
                        else:
                            #timeout
                            self.manageReceivedPartialData()
                    except ConnectionResetError as crp:
                        print(f"ConnectionResetError threadClient() --> ChannelClient {self.channelId}: {crp}")
                        self.closeConnection()
                    except Exception as e:
                        print(f'Exception on selecting threadClient() -->  ChannelClient {self.channelId}: {e}')

这是主线程Server的代码

class ChannelServerManager():
    
    socketServer = None #user to listen/accept connections
    establishedConn = None #represents accepted connections with the counterpart
    receivedData = None
    eventMainThread = None
    socketsInOut = []

    def __init__(self, channelFrame, channelId, port):
        self.eventMainThread = Event()
        self.socketsInOut.clear()
        self.socketServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socketServer.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socketServer.bind(('', int(port))) #in ascolto qualsiasi interfaccia di rete, se metto 127.0.0.1 starebbe in ascolto solo sulla loopback
        self.socketServer.listen(1) #accepting one connection from client
        self.socketsInOut.append(self.socketServer)
        
        self.mainThread = Thread(target = self.threadServer)
        self.mainThread.start()


    def threadServer(self): 
            self.receivedData = ''
            
            while True:
                if (self.eventMainThread.is_set()):
                    print("threadServer() --> ChannelServer is requested to shut down, exit main loop\n")
                    break;
                
                try:
                    r, _, _ = select.select(self.socketsInOut, [], [], ChannelServerManager.TIMEOUT_SELECT)
                    
                    if len(r) > 0: #socket pronte per essere lette
                        for fd in r:
                            if fd is self.socketServer:
                                
                                #if the socket ready is my socket server, then we have a client wanting to connect --> let's accept it
                                clientsock, clientaddr = self.socketServer.accept()
                                self.establishedConn = clientsock
                                print(f"ChannelServer {self.channelId} is connected from client address {clientaddr}")
                                self.socketsInOut.append(clientsock)
                                self.channelFrame.setConnectionStateChannel(True)
                                self.receivedData = ''
                            elif fd is self.establishedConn:
                                data = fd.recv(1)
                                if not data:
                                    print(f"ChannelServer {self.channelId}: Received not data on read socket, client connection closed")
                                    self.socketsInOut.remove(fd)
                                    self.closeConnection()
                                else:
                                    self.manageReceivedDataChunk(data)
                    else: #timeout
                        self.manageReceivedPartialData()
                except Exception as e:
                    print(f"Exception threadServer() --> ChannelServer {self.channelId}: {traceback.format_exc()}")

我不知道为什么,但这个框架/插座似乎互相干扰或“共享数据”。 或者,从其自己框架中的按钮断开和关闭通道也会导致另一个通道出错,或者另一个通道也会关闭/崩溃。 这两个框架/物体应该各自过着自己的生活,只要它们连接在一起,就应该与对方一起前进,而不是互相干扰。 从这个截图中你可以看到:

通过医疗设备(即服务器),我正在发送此数据

<VT>MSH|^~\&|KaliSil|KaliSil|AM|HALIA|20240130182136||OML^O33^OML_O33|1599920240130182136|P|2.5<CR>PID|1||A20230522001^^^^PI~090000^^^^CF||ESSAI^Halia||19890522|M|||^^^^^^H|||||||||||||||<CR>PV1||I||||||||||||A|||||||||||||||||||||||||||||||<CR>SPM|1|072401301016^072401301016||h_san^|||||||||||||20240130181800|20240130181835<CR>ORC|NW|072401301016||A20240130016|saisie||||20240130181800|||^^|CP1A^^^^^^^^CP1A||20240130182136||||||A^^^^^ZONA<CR>TQ1|1||||||||0||<CR>OBR|1|072401301016||h_GLU_A^^T<CR>OBX|1|NM|h_GLU_A^^T||||||||||||||||<CR>BLG|D<CR><FS>

仅在端口 10001 上进行通道,但部分数据在一个套接字客户端上接收,其他部分在另一个(右侧)套接字客户端上接收。这不是在右帧中渲染文本的问题,而且接收数据的日志显示,一些数据是在通道 0 中接收的,而其他数据是在通道 1 中接收的。 为什么会出现这种情况?相反,我启动了 2 个模拟器实例,每个实例只有一个通道,然后一切都运行良好,但这违背了我们能够从单个窗口并行处理最多 4 个通道的目的。 你有什么想法?我第一次实现了

ChannelServerManager
ChannelClientManager
,从
ChannelAbstractManager
扩展而来,具有通用方法和数据结构,基于Python库
ABC
然后我读到Python中的继承与Java中的继承不同,所以我认为不同的实例共享一些属性。我删除了抽象类并复制 两个类中的代码和资源,但这还没有解决。 有什么建议吗?

python java multithreading sockets tcp
1个回答
0
投票

然后我读到Python中的继承与Java中的继承不同

谢谢,这是查找问题的好提示!我认为您正在看到由这种 Java 主义引起的问题:

class ChannelClientManager():

    establishedConn = None
    receivedData = None
    eventMainThread = None #set this event when user clicks on DISCONNECT button
        
...

class ChannelServerManager():
    
    socketServer = None #user to listen/accept connections
    establishedConn = None #represents accepted connections with the counterpart
    receivedData = None
    eventMainThread = None
    socketsInOut = []

    def __init__(self, channelFrame, channelId, port):
        ...

在Python中,你不需要提前声明你的属性,你应该直接在你的

__init__
方法(构造函数)中分配给它们。您声明的所有这些变量实际上是类属性,因此在所有实例之间共享。

这可能并不明显,因为当您这样做时

self.establishedConn = ...
,您也创建了一个覆盖类属性可见性的实例属性,因此您永远不会真正访问共享值。

有一个例外:

socketsInOut = [] def __init__(self, channelFrame, channelId, port): ... self.socketsInOut.clear() ... self.socketsInOut.append(self.socketServer)
因为您从未分配过

self.socketsInOut

,所以所有实例都访问(共享)类属性。


修复方法是删除所有不必要的属性“声明”,并为

socketsInOut

 添加一个缺失的属性:

class ChannelClientManager(): def threadClient(self): self.socketsInOut = [] ... class ChannelServerManager(): def __init__(self, channelFrame, channelId, port): self.eventMainThread = Event() self.socketsInOut = [] # Change here self.socketServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ...
    
© www.soinside.com 2019 - 2024. All rights reserved.