作为对我工作的帮助,我正在使用 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中的继承不同
谢谢,这是查找问题的好提示!我认为您正在看到由这种 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)
...