为了帮助我的工作,我正在开发一个基于TCPSocket的设备间通信模拟器,使用的是Python3.12(使用面向对象的方法).
基本上,服务器类型的通信通道和客户端类型的通信通道之间的区别仅仅是基于实例化套接字的方式:分别是服务器套接字侦听/接受连接请求,而客户端套接字主动连接到它们的端点.
一旦建立了连接,任何一方都可以开始传输一些东西,另一方接收并处理这些东西,然后进行响应(当然是在同一套接字对上).
如你所见.该模拟器有一个基于Tkinter
的简单接口
您可以在一个栅格布局中创建最多4个通道,在本例中我们有两个:
当用户单击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()}")
我不知道为什么,但这些帧/套接字似乎相互干扰或"共享数据". 或者,从其自身框架中的按钮断开并关闭通道也会导致另一个通道出错,或者另一个通道也会关闭/崩溃. 这两个框架/对象应该各自过着自己的生活,只要它们是相连的,就应该与它们的对应对象一起前进,相反,它们会相互干扰. 正如您从此屏幕截图中看到的:
通过医疗设备(即服务器),我正在发送此数据
<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>
仅在端口ChannelServerManager
01上通道,但此数据的一部分在一个套接字客户端上接收,另一部分在另一个(右)套接字客户端上接收.这不是在右侧帧中呈现文本的问题,而且接收到的数据的日志(log)显示,一些数据在通道0中接收,一些其他数据在通道1中接收.
这一切为什么要发生?相反,我启动了模拟器的2个实例,每个实例只有一个通道,然后一切都运行得很好,但这违背了我们能够从单个窗口并行工作多达4个通道的目的.
你有什么主意吗?我第一次实现了ChannelServerManager
和ChannelClientManager
,它们是从ChannelAbstractManager
扩展而来的,具有通用的方法和数据 struct ,基于Python库ABC
然后我读到,在Python中继承与在Java中不同,所以我认为不同的实例共享一些属性.我删除了抽象类并复制了
两个类中的代码和资源,但这并没有解决问题.
有什么建议吗?