9、匹配系统-thrift实现
可以先参考我以前写的:thrift实现多服务多线程的匹配系统
server
每一个都是单线程的,所以按照之前的逻辑实现按照战斗力进行匹配会发生阻塞,所以需要一个额外的模块来实现匹配。
thrift
开另外一个进程或者另外一个服务器开一进程来执行任务两个进程的通信。
-
通信方式
ip:port
-
匹配系统(每秒钟匹配一次,匹配期间,数组不发生变化,多线程(消息队列:缓存信息,匹配)。
匹配系统
thrift
生成代码
编写~/project/hyld/match_system/thrift/match.thrift
namespace py match_service
service Match {
i32 add_palyer(1: i32 score, 2: string uuid, 3: string username, 4: string photo, 5: string channel_name),
}
使用thrift
工具来创建,客户端和服务端需要的代码
# ~/project/hyld/match_system/src/
thrift --gen py ../thrift/match.thrift
mv gen-py/ match_server
touch __init__.py
vim main.py
如果没有安装包,执行pip install thrift
。
服务端
暴露出api
,实现在函数外面依旧可以访问channel
功能,即可以在匹配进程调用websocket
服务,让thrift
进程调用asgi
进程里的函数
~/project/hyld/asgi.py
import os
# 设置环境变量,越靠前越好
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hyld.settings')
django.setup()
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
from game.routing import websocket_urlpatterns
from channels.layers import get_channel_layer
# 实现在函数外面依旧可以访问channel功能,即可以在匹配进程调用websocket服务
channel_layer = get_channel_layer()
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AuthMiddlewareStack(URLRouter(websocket_urlpatterns))
})
匹配系统,将官方教程python例子的Server
代码复制过来
~/project/hyld/match_system/src/main.py
#! /usr/bin/env python3
import glob
import sys
# sys.path.append('gen-py')
# 将django的家目录加到import包里
sys.path.insert(0, glob.glob('../../')[0])
from match_server.match_service import Match
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer
# 线程安全(同步,读写不会发生冲突)的队列
from queue import Queue
from time import sleep
from threading import Thread
from hyld.asgi import channel_layer
# 并行变为串行
from asgiref.sync import async_to_sync
from django.core.cache import cache
# 消息队列异步接收玩家信息,匹配池在这里面取消息
queue = Queue()
class Player:
def __init__(self, score, uuid, username, photo, channel_name):
self.score = score
self.uuid = uuid
self.username = username
self.photo = photo
self.channel_name = channel_name
self.waiting_time = 0 # 等待时间,时间越长,条件越低
# 匹配池
class Pool:
def __init__(self):
self.players = []
def add_player(self, player):
print("add player:%s %d" % (player.username, player.score))
self.players.append(player)
def check_match(self, a, b):
dt = abs(a.score - b.score)
a_max_dif = a.waiting_time * 50
b_max_dif = b.waiting_time * 50
return dt <= a_max_dif and dt <= b_max_dif
def match_success(self, ps):
print("Match Success: %s %s %s" % (ps[0].username, ps[1].username, ps[2].username))
room_name = "room-%s-%s-%s" % (ps[0].uuid, ps[1].uuid, ps[2].uuid)
players = []
for p in ps:
async_to_sync(channel_layer.group_add)(room_name, p.channel_name)
players.append({
'uuid': p.uuid,
'username': p.username,
'photo': p.photo,
'hp': 100,
})
cache.set(room_name, players, 3600)
for p in ps:
async_to_sync(channel_layer.group_send)(
room_name,
{
'type': "group_send_event",
'event': "create_player",
'uuid': p.uuid,
'username': p.username,
'photo': p.photo,
}
)
def increase_waiting_time(self):
for player in self.players:
player.waiting_time += 1
def match(self):
while len(self.players) >= 3:
self.players = sorted(self.players, key=lambda p: p.score)
flag = False
for i in range(len(self.players) - 2):
a, b, c = self.players[i], self.players[i + 1], self.players[i + 2]
if self.check_match(a, b) and self.check_match(a, c) and self.check_match(b, c):
self.match_success([a, b, c])
self.players = self.players[:i] + self.players[i + 3:]
flag = True
break
if not flag:
break
self.increase_waiting_time()
# 接收客户端发来的消息
class MatchHandler:
def add_player(self, score, uuid, username, photo, channel_name):
player = Player(score, uuid, username, photo, channel_name)
queue.put(player)
return 0
# get_nowait有就返回,没有就抛出异常
def get_player_from_queue():
try:
return queue.get_nowait()
except:
return None
# 1. 匹配池从消息队列取玩家消息
# 2. 进行匹配服务
def worker():
pool = Pool()
while True:
player = get_player_from_queue()
if player:
pool.add_player(player)
else:
pool.match()
sleep(1)
if __name__ == '__main__':
handler = MatchHandler()
processor = Match.Processor(handler)
transport = TSocket.TServerSocket(host='127.0.0.1', port=9090)
tfactory = TTransport.TBufferedTransportFactory()
pfactory = TBinaryProtocol.TBinaryProtocolFactory()
# 匹配线程
# server.serve()是阻塞进程,所以匹配线程写在其上方
# daemon为守护进程,false的意思主进程关闭,该进程继续执行
Thread(target=worker, daemon=True).start()
# 接收消息线程
# 单线程版本
# server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)
# You could do one of these for a multithreaded server
server = TServer.TThreadedServer(
processor, transport, tfactory, pfactory)
# 多线程限制版本,可能最多只有十个线程
# server = TServer.TThreadPoolServer(
# processor, transport, tfactory, pfactory)
print('Starting the server...')
server.serve()
print('done.')
执行函数
chomd 764 main.py
./main.py
如果"/usr/bin/env: ‘python3\r’: No such file or directory"
错误,,则vi main.py
, 按下:
输入set ff=unix
客户端
~/project/hyld/game/consumers/multiplayer/index.py
重写 create_player
函数,参考地址thrift实现多服务多线程的匹配系统代码
from channels.generic.websocket import AsyncWebsocketConsumer
import json
from django.conf import settings
from django.core.cache import cache
from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from match_system.src.match_server.match_service import Match
from game.models.player.player import Player
# 数据库是串行操作,但这里都是并行函数,需要将数据库改为并行操作
from channels.db import database_sync_to_async
class MultiPlayer(AsyncWebsocketConsumer):
async def connect(self):
await self.accept()
async def disconnect(self, close_code): # 大概率断开连接时调用
if self.room_name:
await self.channel_layer.group_discard(self.room_name, self.channel_name)
async def create_player(self, data):
self.room_name = None
# Make socket
transport = TSocket.TSocket('localhost', 9090)
# Buffering is critical. Raw sockets are very slow
transport = TTransport.TBufferedTransport(transport)
# Wrap in a protocol
protocol = TBinaryProtocol.TBinaryProtocol(transport)
# Create a client to use the protocol encoder
client = Match.Client(protocol)
def db_get_player():
return Player.objects.get(user__username=data['username'])
player = await database_sync_to_async(db_get_player)()
# Connect!
transport.open()
# 具体业务代码
client.add_player(player.score, data['uuid'], data['username'], data['photo'], self.channel_name)
# Close!
transport.close()
async def move_to(self, data):
await self.channel_layer.group_send(
self.room_name,
{
'type': "group_send_event",
'event': "move_to",
'uuid': data['uuid'],
'tx': data['tx'],
'ty': data['ty'],
}
)
async def shoot_fireball(self, data):
await self.channel_layer.group_send(
self.room_name,
{
'type': "group_send_event",
'event': "shoot_fireball",
'uuid': data["uuid"],
'tx': data['tx'],
'ty': data['ty'],
'ball_uuid': data['ball_uuid'],
}
)
async def attack(self, data):
await self.channel_layer.group_send(
self.room_name,
{
'type': "group_send_event",
'event': "attack",
'attackee_uuid': data["attackee_uuid"],
'uuid': data['uuid'],
'x': data['x'],
'y': data['y'],
'angle': data['angle'],
'damage': data['damage'],
'ball_uuid': data['ball_uuid'],
}
)
async def blink(self, data):
await self.channel_layer.group_send(
self.room_name,
{
'type': "group_send_event",
'event': "blink",
'uuid': data['uuid'],
'tx': data['tx'],
'ty': data['ty'],
}
)
async def message(self, data):
await self.channel_layer.group_send(
self.room_name,
{
'type': "group_send_event",
'event': "message",
'uuid': data['uuid'],
'username': data['username'],
'text': data['text'],
}
)
async def group_send_event(self, data):
if not self.room_name:
keys = cache.keys('*%s*' % (data['uuid']))
if keys:
self.room_name = keys[0]
await self.send(text_data=json.dumps(data))
# 接收前端信息
async def receive(self, text_data):
data = json.loads(text_data)
event = data['event']
if event == "create_player":
await self.create_player(data)
elif event == "move_to":
await self.move_to(data)
elif event == "shoot_fireball":
await self.shoot_fireball(data)
elif event == "attack":
await self.attack(data)
elif event == "blink":
await self.blink(data)
elif event == "message":
await self.message(data)
~/project/hyld/game/models/player/player.py
数据库添加战斗力
score = models.IntegerField(default=1500)
更新数据库
python3 manage.py makemigrations
python3 manage.py migrate
重启服务器