为了帮助我的工作,我正在开发一个基于TCPSocket的设备间通信模拟器,使用的是Python3.12(使用面向对象的方法). 基本上,服务器类型的通信通道和客户端类型的通信通道之间的区别仅仅是基于实例化套接字的方式:分别是服务器套接字侦听/接受连接请求,而客户端套接字主动连接到它们的端点. 一旦建立了连接,任何一方都可以开始传输一些东西,另一方接收并处理这些东西,然后进行响应(当然是在同一套接字对上). 如你所见.该模拟器有一个基于Tkinter的简单接口 您可以在一个栅格布局中创建最多4个通道,在本例中我们有两个:

enter image description here

当用户单击CONNECT按钮时,Frame类中该按钮的侦听器中会发生以下情况:

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)

然后,我实现了一个类型为Server的通道和一个类型为Client的通道.它们的构造函数基本上收集接收到的数据并创建一个主线程,其目的是创建套接字,然后:

1a)套接字客户端连接对端

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

2.)使用select.select进入主循环,并在其帧的文本区域中跟踪接收和发送的数据

以下是主线程客户端的代码

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}')

以下是主线程服务器的代码

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()}")

我不知道为什么,但这些帧/套接字似乎相互干扰或"共享数据". 或者,从其自身框架中的按钮断开并关闭通道也会导致另一个通道出错,或者另一个通道也会关闭/崩溃. 这两个框架/对象应该各自过着自己的生活,只要它们是相连的,就应该与它们的对应对象一起前进,相反,它们会相互干扰. 正如您从此屏幕截图中看到的:

enter image description here

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

<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>

仅在端口ChannelServerManager01上通道,但此数据的一部分在一个套接字客户端上接收,另一部分在另一个(右)套接字客户端上接收.这不是在右侧帧中呈现文本的问题,而且接收到的数据的日志(log)显示,一些数据在通道0中接收,一些其他数据在通道1中接收. 这一切为什么要发生?相反,我启动了模拟器的2个实例,每个实例只有一个通道,然后一切都运行得很好,但这违背了我们能够从单个窗口并行工作多达4个通道的目的. 你有什么主意吗?我第一次实现了ChannelServerManagerChannelClientManager,它们是从ChannelAbstractManager扩展而来的,具有通用的方法和数据 struct ,基于Python库ABC 然后我读到,在Python中继承与在Java中不同,所以我认为不同的实例共享一些属性.我删除了抽象类并复制了 两个类中的代码和资源,但这并没有解决问题. 有什么建议吗?

推荐答案

然后我读到,在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__方法(构造函数)中直接为它们赋值即可.您声明的所有这些变量实际上都是class attributes,因此像您所怀疑的那样在所有实例之间共享.

这可能并不明显,因为当您执行self.establishedConn = ...时,您正在创建一个覆盖class属性可见性的实例属性,因此您实际上永远不会访问共享值.

只有一个例外:

    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 = [] # Change here.
        ...

class ChannelServerManager():

    def __init__(self, channelFrame, channelId, port):
        self.eventMainThread = Event()
        self.socketsInOut = [] # And here.
        self.socketServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        ...

为了节省您调试另一个众所周知的Python Wart的时间,default arguments are also shared between calls.因此,永远不要声明带有可变默认参数的方法,比如def foo(items=[]): ....

Python相关问答推荐

Locust请求中的Python和参数

Odoo 14 hr. emergency.public内的二进制字段

将特定列信息移动到当前行下的新行

如何让程序打印新段落上的每一行?

用Python解密Java加密文件

优化器的运行顺序影响PyTorch中的预测

使用密钥字典重新配置嵌套字典密钥名

不能使用Gekko方程'

ModuleNotFoundError:没有模块名为x时try 运行我的代码''

提高算法效率的策略?

使用字典或列表的值组合

当单元测试失败时,是否有一个惯例会抛出许多类似的错误消息?

我对这个简单的异步者的例子有什么错误的理解吗?

将CSS链接到HTML文件的问题

有了Gekko,可以创建子模型或将模型合并在一起吗?

以极轴表示的行数表达式?

PYTHON中的selenium不会打开 chromium URL

如何在Quarto中的标题页之前创建序言页

为什么在生成时间序列时,元组索引会超出范围?

为什么在更新Pandas 2.x中的列时,数据类型不会更改,而在Pandas 1.x中会更改?