环境:
ubuntu18.04 + python3.7
在公司项目中,需要用到mqtt进行通信,所有开发了下面的架构
优点:
1、很好的进行不同主题和内人的区分
2、线程操作做了互锁,每个消息独立线程处理
3、很方便的提取消息内容
由三个文件组成:dao.py server.py config.py
架构请自行参考,博客为了记录项目实战的代码
server.py
# coding: utf-8
import paho.mqtt.client as mqtt
from config import PROJECT_CODE, HOST, PORT
from dao import ServerDao
from threading import Lock
import random
class Server:
def __init__(self):
self.user_list = []
self.online_user = []
self.online_player = []
self.client = mqtt.Client()
self.client.on_connect = self.on_connect
self.client.on_message = self.on_message
self.client.connect(HOST, PORT, 60)
self.lock = Lock()
self.loop_num = 0
# handle_func的keys()为要订阅的主题列表
self.handle_func = {'login': self.login, 'register': self.register,
'chat': self.chat, 'is_cancel': self.is_cancel,
'cancel': self.cancel, 'find_room': self.find_room}
def start_loop(self):
# 用线程锁来控制同时仅能一个loop_forever
if self.loop_num == 0:
self.lock.acquire()
print('获得锁!')
self.loop_num = 1
self.client._thread_terminate = False
self.client.loop_forever()
def stop_loop(self):
# 停止这个线程
if self.loop_num == 1:
self.lock.release()
print('解锁!!')
self.client._thread_terminate = True
self.loop_num = 0
def on_connect(self, client, userdata, flags, rc):
if rc == 0:
print("Connected successfully ")
for topic in self.handle_func.keys():
client.subscribe(topic)
def on_message(self, client, userdata, msg):
# 规定传入数据均为dict的形式
data = eval(msg.payload.decode('utf-8'))
if ServerDao.check_publish_topic(msg.topic, data):
if msg.topic in self.handle_func.keys():
func = self.handle_func[msg.topic]
func(data)
def cancel(self, data):
msg = []
while data["machine_id"] in self.online_user:
self.online_user.remove(data["machine_id"])
print("self.online_user = ", self.online_user)
for room_id, player in enumerate(self.online_player):
red_id, black_id, is_cancel = player
if red_id == data["machine_id"] or black_id == data["machine_id"]:
msg.append([black_id, "", "cancel_ok", "", ""])
msg.append([red_id, "", "cancel_ok", "", ""])
ServerDao.publish_cancel_room(msg)
self.online_player[room_id][-1] = True
self.online_player.remove(room_id)
print("self.user_list = ", self.user_list)
print("self.online_player = ", self.online_player)
while red_id in self.online_user and black_id in self.online_user:
try:
self.online_user.remove(red_id)
self.online_user.remove(black_id)
except:
pass
def is_cancel(self, data):
is_cancel_room = False
if data["machine_id"] in self.online_user:
msg = 'True'
else:
msg = 'False'
print("self.online_user = ", self.online_user)
print(msg)
# for room_id, player in enumerate(self.online_player):
# red_id, black_id, is_cancel = player
# if red_id == data["machine_id"] or black_id == data["machine_id"]:
# is_cancel_room = is_cancel
# break
# ServerDao.publish_iscancel_room(str(is_cancel_room))
ServerDao.publish_iscancel_room(msg)
def find_room(self, data):
msg = []
if len(self.user_list) > 1:
for i, child_id in enumerate(self.user_list):
if i % 2 == 0:
room_player = [self.user_list[i], self.user_list[i - 1]]
black_id = self.user_list[i]
red_id = random.choice(room_player)
for i_ in room_player:
if i_ != red_id:
black_id = i_
msg.append([black_id, "black", "find_room", "", red_id])
msg.append([red_id, "red", "find_room", "", black_id])
ServerDao.publish_find_room(msg)
remove_room = []
for players in self.online_player:
if red_id in players and black_id in players:
remove_room.append(players)
for remove_ in remove_room:
self.online_player.remove(remove_)
self.online_player.append([red_id, black_id, False])
print("self.online_player = ", self.online_player)
self.user_list.remove(red_id)
self.user_list.remove(black_id)
def login(self, data):
pass
def register(self, data):
if data['data'] == PROJECT_CODE:
if data['machine_id'] not in self.user_list and 'GSCHESS' in data['machine_id']:
self.user_list.append(data['machine_id'])
self.online_user.append(data['machine_id'])
print("self.user_list = ", self.user_list)
msg = 'True'
else:
msg = 'False'
ServerDao.publish_register_msg(self.client, data['machine_id'], msg)
def chat(self, data):
pass
def all_notes(self, data):
pass
if __name__ == '__main__':
server = Server()
server.start_loop()
dao.py
# coding: utf-8
import time
import paho.mqtt.client as mqtt
from config import TOPIC_PARAMS, HOST, PORT
class ServerDao:
@staticmethod
def check_publish_topic(topic, data):
keys = data.keys()
for key in TOPIC_PARAMS[topic]:
if key not in keys:
print('publish is not enough')
return False
return True
@staticmethod
def publish_login_msg(client, return_topic, user_name, msg, token=None):
"""
回复给客户端的token
:param user_name:用户名
:param msg: 验证情况
:param token:
:return:
"""
# client = mqtt.Client()
# client.connect(HOST, PORT, 60)
# user_name可以去掉
data = {'user_name': user_name, 'login_msg': msg}
if token:
all_rooms = ChatRoomsModelDao.get_all_rooms()
data.update({'token': token, 'all_rooms': all_rooms})
client.publish(return_topic, str(data).encode(), 1)
print('publish login_msg to ', return_topic, ' succeed')
# client.loop()
@staticmethod
def publish_register_msg(client, user_name, msg):
"""
回复给客户端的token
:param user_name:用户名
:param msg: 插入情况
:return:
"""
# 分情况
data = {'user_name': user_name, 'register_msg': msg}
client.publish("register_msg", str(data).encode(), 1)
print('publish register_msg to ', user_name, ' succeed')
@staticmethod
def publish_cancel_room(data_):
client_ = mqtt.Client()
client_.connect(HOST, PORT, 60)
publish_data = {'cancel_msg': data_}
publish_data1 = {'chat_msg': {'machine_id': '', 'role': '', 'action': 'error', 'time': '', 'data': ''}}
# client_.publish('GSCHESS01001', str(publish_data).encode(), 1)
# client_.publish('GSCHESS01002', str(publish_data).encode(), 1)
for i in range(3):
client_.publish('cancel_msg', str(publish_data).encode(), 1)
time.sleep(0.2)
publish_data = {'error_msg': data_}
client_.publish('cancel_msg', str(publish_data).encode(), 1)
print('publish all notes to ', "cancel", ' successfully')
client_.loop()
@staticmethod
def publish_iscancel_room(data_):
client_ = mqtt.Client()
client_.connect(HOST, PORT, 60)
publish_data = {'is_cancel_msg': data_}
client_.publish('is_cancel_msg', str(publish_data).encode(), 1)
print('publish all notes to ', "iscancel", ' successfully')
client_.loop()
@staticmethod
def publish_find_room(data_):
"""
给客户端该房间的所有历史记录
:param return_topic: 标识一个客户端
:param room_name:
:return:
"""
client = mqtt.Client()
client.connect(HOST, PORT, 60)
publish_data = {'find_room_msg': data_}
for i in range(4):
client.publish('find_room_msg', str(publish_data).encode(), 1)
time.sleep(0.15)
print('publish all notes to ', "find_room", ' successfully')
client.loop()
@staticmethod
def publish_latest_note(return_topic, note_id):
"""
将最新发布的信息发送给所有订阅的客户端
:param return_topic: 标识房间
:param note_id:
:return:
"""
client = mqtt.Client()
client.connect(HOST, PORT, 60)
model = ChatNotesModelDao.query_one_note(note_id)
user_name = UserModelDao.get_name_by_user_id(model.user_id)
data = [user_name, model.message, model.time]
publish_data = {'latest_note': data}
client.publish(return_topic, str(publish_data).encode(), 1)
print('publish one note to ', return_topic, ' successfully')
client.loop()
@staticmethod
def publish_one_step_chess(return_topic, data_):
"""
将最新发布的信息发送给所有订阅的客户端
:param return_topic: 标识房间
:param note_id:
:return:
"""
client = mqtt.Client()
client.connect(HOST, PORT, 60)
publish_data = {'chess': data_}
client.publish(return_topic, str(publish_data).encode(), 1)
print('publish one note to ', return_topic, ' successfully')
client.loop()
@staticmethod
def publish_invalid_msg(client, return_topic):
"""
回复给客户端的token
:param client:
:param return_topic:
:return:
"""
# user_name可以去掉
msg = 'Token invalid '
data = {'error_msg': msg}
client.publish(return_topic, str(data).encode(), 1)
print('publish error_msg to ', return_topic, ' succeed')
config.py
# coding: utf-8
HOST = "1.tcp.cpolar.cn"
# HOST = "192.168.0.246"
PORT = 20029
# PORT = 1883
# 定义topic
# TOPIC = {'登录': 'login', '注册': 'register', '聊天': 'chat'}
# 登录时的token由客户端生成
login_params = ['user_name', 'user_pwd'] # user_room不确定要不要下
register_params = ['machine_id', 'role', 'action', 'time', 'data']
chat_params = ['machine_id', 'role', 'action', 'time', 'data']
all_notes_params = ['machine_id', 'role', 'action', 'time', 'data']
cancel_params = ['machine_id', 'role', 'action', 'time', 'data']
is_cancel_params = ['machine_id', 'role', 'action', 'time', 'data']
find_room_params = ['machine_id', 'role', 'action', 'time', 'data']
TOPIC_PARAMS = {'login': login_params, 'register': register_params,
'chat': chat_params, 'all_notes': all_notes_params,
'cancel': cancel_params, 'find_room': find_room_params,'is_cancel': is_cancel_params}
PROJECT_CODE = 'HELLO CHESS'
# 服务端发送给客户端的反馈格式为: topic: user_name, 成功时数据为{'token': 'xxxxx', 'all_rooms': []}
# 服务端发送给客户端一个房间所有数据: topic为房间名,数据为{'all_notes': data},
# data是[user_name, model.message, model.time]
# 服务端发送给客户端最新消息