目录
本篇主要阐述Client的实现内容。
在使用Client时,我的需求比较简单,主要有三点:
- 每一个独立进程中,仅有一个socket连接;
- 使用时,使用类方法进行调用,使用者不管理实例;
- 订阅时,传入函数进行回调。
因此,为了实现以上需求,我使用单例模式、工厂模式来进行实现。
一、单例模式
class Client:
...
CLIENT = Client()
python中实现单例很简单,只要设定一个全局的变量即可实现。
有了单例,我们就可以持有一个socket,而不额外创建消耗性能。
二、工厂模式
工厂模式可以将对象的管理将有类自己管理,这样我们只需要使用相应的方法而无需创建自己的对象,这样一方面可以方便使用,另一方面也减去了调用者可能存在的多份socket创建问题。
@staticmethod
def publish(key, value):
pass
@staticmethod
def subscribe(key, handle=None):
pass
@staticmethod
def clean():
pass
- publish:发布消息;
- subscribe:订阅消息;
- clean:清理内存。
三、创建socket
当我们在进行任何一种订阅或发布行为时,我们需要进行一次socket的创建来连接broker,因此我们需要在订阅和发布方法中去执行socket连接的方法。
@staticmethod
def publish(key, value):
global CLIENT
CLIENT.run() # 去创建socket
@staticmethod
def subscribe(key, handle=None):
global CLIENT
CLIENT.run() # 去创建socket
3.1 run初始化所需变量
def run(self):
if self.event.is_set():
self.event.clear()
if self.reset_event.is_set():
self.reset_event.clear()
if not self.socket:
self.set_up()
3.2 set_up初始化socket所需变量
def set_up(self):
if not self.reset_thread or not self.reset_thread.is_alive():
self.reset_thread = None
self.reset_thread = threading.Thread(target=self.__reset_handle, args=())
self.reset_thread.start()
self.__socket_generator()
3.3 socket创建
def __run_broker(self):
is_restart = False # 当broker重启成功后,leader重新交由broker进行管理的标识
while not self.event.is_set():
self.is_opening = True # 防止订阅发布时,重复进入该方法
is_open = check_broker_active()
if is_open:
break
else:
is_restart = True # 当broker未运行,即需要重启
if self.is_leader or self.first_init: # 只有leader或第一次运行才启动broker
run_broker()
time.sleep(5)
self.first_init = False # 非第一次启动标识
self.is_opening = False # 当前不是broker启动过程中
if is_restart:
self.is_leader = False # 启动时,leader重新赋值
def __socket_generator(self):
if self.is_opening: # 防止订阅发布多次进入启动broker循环
return
self.__run_broker() # 尝试启动broker
if not self.socket:
try:
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.connect(("127.0.0.1", PORT))
except Exception as e:
print(f"创建socket {e}")
for key in self.send_msg_dic: # 如果存在订阅,则去重新订阅
data = {"type": "sub", "key": key}
data = json.dumps(data)
data = data.encode()
try:
self.socket.sendall(data)
except Exception as e:
pass
if not self.receive_thread or not self.receive_thread.is_alive():
self.receive_thread = None
self.receive_thread = threading.Thread(target=self.__receive_handle, args=())
self.receive_thread.start()
if not self.send_msg_thread or not self.send_msg_thread.is_alive():
self.send_msg_thread = None
self.send_msg_thread = threading.Thread(target=self.__send_msg_handle, args=())
self.send_msg_thread.start()
在socket初始化时,我们在下方进行了重新订阅,这是因为只有在broker关闭时,socket才会变为空,这个时候我们的订阅关系其实还存在,所以得重新进行一次订阅。
四、监测broker并重新运行
4.1 receive线程
def __receive_handle(self):
while not self.event.is_set():
try:
res = self.socket.recv(2048)
except Exception as e:
self.event.set()
self.reset_queue.put("reset") # 接收不到数据就表示断开了,去重启broker
break
if res:
try:
res = res.decode()
res = json.loads(res)
except:
continue
if res.get("type") == "pub":
self.send_msg_queue.put(res) # 当接收到的数据是订阅内容,则发送给相应的订阅方法
elif res.get("type") == "leader":
self.is_leader = True # 当为leader数据,则去设置为leader
4.2 reset线程
def __reset_handle(self):
while not self.reset_event.is_set():
if not self.reset_queue.empty():
self.reset_queue.get() # 收到reset指令
if self.socket:
self.socket.close() # 关闭socket
self.socket = None # socket去置空
self.run() # 重新启动
五、清理内存
def clear(self):
if self.event:
self.event.set()
if self.reset_event:
self.reset_event.set()
if self.reset_thread:
self.reset_thread = None
if self.send_msg_dic:
self.send_msg_dic.clear()
if self.reset_queue:
self.reset_queue.queue.clear()
if self.send_msg_queue:
self.send_msg_queue.queue.clear()
if self.send_msg_thread:
self.send_msg_thread = None
if self.receive_thread:
self.receive_thread = None
if self.heart_thread:
self.heart_thread = None
if self.socket:
self.socket.close()
self.socket = None
当不需要订阅或发布时,可以利用清理内存将闲置的socket关闭。
六、待优化内容
- 粘包问题,目前以最简单的json字符串发送,很容易造成粘包无法解析;
- 没有任何订阅和发布时,broker进行关闭问题;
- 其他问题。