mirror of
https://github.com/Lost-MSth/Arcaea-server.git
synced 2026-02-09 01:07:27 +08:00
Rearrange Link Play module
- Try to ensure thread safety by using TCP socket
This commit is contained in:
@@ -38,7 +38,12 @@ class Constant:
|
|||||||
DOWNLOAD_LINK_PREFIX = Config.DOWNLOAD_LINK_PREFIX
|
DOWNLOAD_LINK_PREFIX = Config.DOWNLOAD_LINK_PREFIX
|
||||||
|
|
||||||
LINK_PLAY_UNLOCK_LENGTH = 512 # Units: bytes
|
LINK_PLAY_UNLOCK_LENGTH = 512 # Units: bytes
|
||||||
LINK_PLAY_TIMEOUT = 10 # Units: seconds
|
LINK_PLAY_TIMEOUT = 5 # Units: seconds
|
||||||
|
|
||||||
|
LINK_PLAY_HOST = '127.0.0.1' if Config.SET_LINK_PLAY_SERVER_AS_SUB_PROCESS else Config.LINK_PLAY_HOST
|
||||||
|
LINK_PLAY_TCP_PORT = Config.LINK_PLAY_TCP_PORT
|
||||||
|
LINK_PLAY_UDP_PORT = Config.LINK_PLAY_UDP_PORT
|
||||||
|
LINK_PLAY_AUTHENTICATION = Config.LINK_PLAY_AUTHENTICATION
|
||||||
|
|
||||||
COURSE_STAMINA_COST = 4
|
COURSE_STAMINA_COST = 4
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
from base64 import b64encode
|
import socket
|
||||||
|
from base64 import b64decode, b64encode
|
||||||
|
|
||||||
from core.error import ArcError, Timeout
|
from core.error import ArcError, Timeout
|
||||||
|
|
||||||
from .constant import Constant
|
from .constant import Constant
|
||||||
from .user import UserInfo
|
from .user import UserInfo
|
||||||
|
|
||||||
|
socket.setdefaulttimeout(Constant.LINK_PLAY_TIMEOUT)
|
||||||
|
|
||||||
|
|
||||||
def get_song_unlock(client_song_map: dict) -> bytes:
|
def get_song_unlock(client_song_map: dict) -> bytes:
|
||||||
'''处理可用歌曲bit,返回bytes'''
|
'''处理可用歌曲bit,返回bytes'''
|
||||||
@@ -82,9 +85,8 @@ class Room:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class LocalMultiPlayer:
|
class RemoteMultiPlayer:
|
||||||
def __init__(self, conn=None) -> None:
|
def __init__(self) -> None:
|
||||||
self.conn = conn
|
|
||||||
self.user: 'Player' = None
|
self.user: 'Player' = None
|
||||||
self.room: 'Room' = None
|
self.room: 'Room' = None
|
||||||
|
|
||||||
@@ -93,30 +95,44 @@ class LocalMultiPlayer:
|
|||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
return dict(self.room.to_dict(), **self.user.to_dict())
|
return dict(self.room.to_dict(), **self.user.to_dict())
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def tcp(data: str) -> str:
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||||
|
sock.connect((Constant.LINK_PLAY_HOST,
|
||||||
|
Constant.LINK_PLAY_TCP_PORT))
|
||||||
|
sock.sendall(bytes(data + "\n", "utf-8"))
|
||||||
|
try:
|
||||||
|
received = str(sock.recv(1024), "utf-8").strip()
|
||||||
|
except socket.timeout:
|
||||||
|
raise Timeout(
|
||||||
|
'Timeout when waiting for data from link play server.', status=400)
|
||||||
|
# print(received)
|
||||||
|
return received
|
||||||
|
|
||||||
def data_swap(self, data: tuple) -> tuple:
|
def data_swap(self, data: tuple) -> tuple:
|
||||||
self.conn.send(data)
|
|
||||||
if self.conn.poll(Constant.LINK_PLAY_TIMEOUT):
|
received = self.tcp(Constant.LINK_PLAY_AUTHENTICATION +
|
||||||
self.data_recv = self.conn.recv()
|
'|' + '|'.join([str(x) for x in data]))
|
||||||
if self.data_recv[0] != 0:
|
|
||||||
raise ArcError('Link Play error.',
|
self.data_recv = received.split('|')
|
||||||
self.data_recv[0], status=400)
|
if self.data_recv[0] != '0':
|
||||||
else:
|
raise ArcError('Link Play error.',
|
||||||
raise Timeout(
|
int(self.data_recv[0]), status=400)
|
||||||
'Timeout when waiting for data from local udp server.', status=400)
|
|
||||||
|
|
||||||
def create_room(self, user: 'Player' = None) -> None:
|
def create_room(self, user: 'Player' = None) -> None:
|
||||||
'''创建房间'''
|
'''创建房间'''
|
||||||
if user is not None:
|
if user is not None:
|
||||||
self.user = user
|
self.user = user
|
||||||
user.select_user_one_column('name')
|
user.select_user_one_column('name')
|
||||||
self.data_swap((1, self.user.name, self.user.song_unlock))
|
self.data_swap((1, self.user.name, b64encode(
|
||||||
|
self.user.song_unlock).decode('utf-8')))
|
||||||
self.room = Room()
|
self.room = Room()
|
||||||
self.room.room_code = self.data_recv[1]
|
self.room.room_code = self.data_recv[1]
|
||||||
self.room.room_id = self.data_recv[2]
|
self.room.room_id = int(self.data_recv[2])
|
||||||
self.room.song_unlock = self.user.song_unlock
|
self.room.song_unlock = self.user.song_unlock
|
||||||
self.user.token = self.data_recv[3]
|
self.user.token = int(self.data_recv[3])
|
||||||
self.user.key = self.data_recv[4]
|
self.user.key = b64decode(self.data_recv[4])
|
||||||
self.user.player_id = self.data_recv[5]
|
self.user.player_id = int(self.data_recv[5])
|
||||||
|
|
||||||
def join_room(self, room: 'Room' = None, user: 'Player' = None) -> None:
|
def join_room(self, room: 'Room' = None, user: 'Player' = None) -> None:
|
||||||
'''加入房间'''
|
'''加入房间'''
|
||||||
@@ -127,13 +143,13 @@ class LocalMultiPlayer:
|
|||||||
|
|
||||||
self.user.select_user_one_column('name')
|
self.user.select_user_one_column('name')
|
||||||
self.data_swap(
|
self.data_swap(
|
||||||
(2, self.user.name, self.user.song_unlock, room.room_code))
|
(2, self.user.name, b64encode(self.user.song_unlock).decode('utf-8'), room.room_code))
|
||||||
self.room.room_code = self.data_recv[1]
|
self.room.room_code = self.data_recv[1]
|
||||||
self.room.room_id = self.data_recv[2]
|
self.room.room_id = int(self.data_recv[2])
|
||||||
self.room.song_unlock = self.data_recv[6]
|
self.room.song_unlock = b64decode(self.data_recv[6])
|
||||||
self.user.token = self.data_recv[3]
|
self.user.token = int(self.data_recv[3])
|
||||||
self.user.key = self.data_recv[4]
|
self.user.key = b64decode(self.data_recv[4])
|
||||||
self.user.player_id = self.data_recv[5]
|
self.user.player_id = int(self.data_recv[5])
|
||||||
|
|
||||||
def update_room(self, user: 'Player' = None) -> None:
|
def update_room(self, user: 'Player' = None) -> None:
|
||||||
'''更新房间'''
|
'''更新房间'''
|
||||||
@@ -142,7 +158,7 @@ class LocalMultiPlayer:
|
|||||||
self.data_swap((3, self.user.token))
|
self.data_swap((3, self.user.token))
|
||||||
self.room = Room()
|
self.room = Room()
|
||||||
self.room.room_code = self.data_recv[1]
|
self.room.room_code = self.data_recv[1]
|
||||||
self.room.room_id = self.data_recv[2]
|
self.room.room_id = int(self.data_recv[2])
|
||||||
self.room.song_unlock = self.data_recv[5]
|
self.room.song_unlock = b64decode(self.data_recv[5])
|
||||||
self.user.key = self.data_recv[3]
|
self.user.key = b64decode(self.data_recv[3])
|
||||||
self.user.player_id = self.data_recv[4]
|
self.user.player_id = int(self.data_recv[4])
|
||||||
|
|||||||
1
latest version/linkplay_server/__init__.py
Normal file
1
latest version/linkplay_server/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .main import link_play
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import os
|
from os import urandom
|
||||||
from cryptography.hazmat.primitives.ciphers import (
|
from cryptography.hazmat.primitives.ciphers import (
|
||||||
Cipher, algorithms, modes
|
Cipher, algorithms, modes
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def encrypt(key, plaintext, associated_data):
|
def encrypt(key, plaintext, associated_data):
|
||||||
iv = os.urandom(12)
|
iv = urandom(12)
|
||||||
encryptor = Cipher(
|
encryptor = Cipher(
|
||||||
algorithms.AES(key),
|
algorithms.AES(key),
|
||||||
modes.GCM(iv, min_tag_length=12),
|
modes.GCM(iv, min_tag_length=12),
|
||||||
30
latest version/linkplay_server/config.py
Normal file
30
latest version/linkplay_server/config.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from telnetlib import AUTHENTICATION
|
||||||
|
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
'''
|
||||||
|
Link Play server configuration
|
||||||
|
'''
|
||||||
|
|
||||||
|
'''
|
||||||
|
服务器地址、端口号、校验码
|
||||||
|
Server address, port and verification code
|
||||||
|
'''
|
||||||
|
HOST = '0.0.0.0'
|
||||||
|
UDP_PORT = 10900
|
||||||
|
TCP_PORT = 10901
|
||||||
|
AUTHENTICATION = 'my_link_play_server'
|
||||||
|
'''
|
||||||
|
--------------------------------------------------
|
||||||
|
'''
|
||||||
|
|
||||||
|
TIME_LIMIT = 3600000
|
||||||
|
|
||||||
|
COMMAND_INTERVAL = 1000000
|
||||||
|
|
||||||
|
COUNTDOWM_TIME = 3999
|
||||||
|
|
||||||
|
PLAYER_PRE_TIMEOUT = 3000000
|
||||||
|
PLAYER_TIMEOUT = 20000000
|
||||||
|
|
||||||
|
LINK_PLAY_UNLOCK_LENGTH = 512
|
||||||
253
latest version/linkplay_server/main.py
Normal file
253
latest version/linkplay_server/main.py
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
import base64
|
||||||
|
import random
|
||||||
|
import socketserver
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from os import urandom
|
||||||
|
|
||||||
|
# import binascii
|
||||||
|
from .aes import decrypt, encrypt
|
||||||
|
from .config import Config
|
||||||
|
from .udp_class import Player, Room, bi
|
||||||
|
from .udp_parser import CommandParser
|
||||||
|
|
||||||
|
# token: {'key': key, 'room': Room, 'player_index': player_index, 'player_id': player_id}
|
||||||
|
link_play_data = {}
|
||||||
|
room_id_dict = {} # 'room_id': Room
|
||||||
|
room_code_dict = {} # 'room_code': Room
|
||||||
|
player_dict = {} # 'player_id' : Player
|
||||||
|
clean_timer = 0
|
||||||
|
lock = threading.RLock()
|
||||||
|
|
||||||
|
|
||||||
|
def random_room_code():
|
||||||
|
# 随机生成房间号
|
||||||
|
re = ''
|
||||||
|
for _ in range(4):
|
||||||
|
re += chr(random.randint(65, 90))
|
||||||
|
for _ in range(2):
|
||||||
|
re += str(random.randint(0, 9))
|
||||||
|
|
||||||
|
return re
|
||||||
|
|
||||||
|
|
||||||
|
def unique_random(dataset, length=8, random_func=None):
|
||||||
|
'''无重复随机,且默认非0'''
|
||||||
|
if random_func is None:
|
||||||
|
x = bi(urandom(length))
|
||||||
|
while x in dataset or x == 0:
|
||||||
|
x = bi(urandom(length))
|
||||||
|
else:
|
||||||
|
x = random_func()
|
||||||
|
while x in dataset:
|
||||||
|
x = random_func()
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
def clear_player(token):
|
||||||
|
# 清除玩家信息和token
|
||||||
|
del player_dict[link_play_data[token]['player_id']]
|
||||||
|
del link_play_data[token]
|
||||||
|
|
||||||
|
|
||||||
|
def clear_room(room):
|
||||||
|
# 清除房间信息
|
||||||
|
room_id = room.room_id
|
||||||
|
room_code = room.room_code
|
||||||
|
del room_id_dict[room_id]
|
||||||
|
del room_code_dict[room_code]
|
||||||
|
del room
|
||||||
|
|
||||||
|
|
||||||
|
def memory_clean(now):
|
||||||
|
# 内存清理
|
||||||
|
lock.acquire()
|
||||||
|
|
||||||
|
clean_room_list = []
|
||||||
|
clean_player_list = []
|
||||||
|
for token in link_play_data:
|
||||||
|
room = link_play_data[token]['room']
|
||||||
|
if now - room.timestamp >= Config.TIME_LIMIT:
|
||||||
|
clean_room_list.append(room.room_id)
|
||||||
|
|
||||||
|
if now - room.players[link_play_data[token]['player_index']].last_timestamp // 1000 >= Config.TIME_LIMIT:
|
||||||
|
clean_player_list.append(token)
|
||||||
|
|
||||||
|
for room_id in room_id_dict:
|
||||||
|
if now - room_id_dict[room_id].timestamp >= Config.TIME_LIMIT:
|
||||||
|
clean_room_list.append(room_id)
|
||||||
|
|
||||||
|
for room_id in clean_room_list:
|
||||||
|
if room_id in room_id_dict:
|
||||||
|
clear_room(room_id_dict[room_id])
|
||||||
|
|
||||||
|
for token in clean_player_list:
|
||||||
|
clear_player(token)
|
||||||
|
|
||||||
|
lock.release()
|
||||||
|
|
||||||
|
|
||||||
|
class UDP_handler(socketserver.BaseRequestHandler):
|
||||||
|
def handle(self):
|
||||||
|
client_msg, server = self.request
|
||||||
|
try:
|
||||||
|
token = client_msg[:8]
|
||||||
|
iv = client_msg[8:20]
|
||||||
|
tag = client_msg[20:32]
|
||||||
|
ciphertext = client_msg[32:]
|
||||||
|
if int.from_bytes(token, byteorder='little') in link_play_data:
|
||||||
|
user = link_play_data[bi(token)]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
plaintext = decrypt(user['key'], b'', iv, ciphertext, tag)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return None
|
||||||
|
# print(binascii.b2a_hex(plaintext))
|
||||||
|
|
||||||
|
commands = CommandParser(
|
||||||
|
user['room'], user['player_index']).get_commands(plaintext)
|
||||||
|
|
||||||
|
if user['room'].players[user['player_index']].player_id == 0:
|
||||||
|
clear_player(bi(token))
|
||||||
|
temp = []
|
||||||
|
for i in commands:
|
||||||
|
if i[:3] == b'\x06\x16\x12':
|
||||||
|
temp.append(i)
|
||||||
|
commands = temp
|
||||||
|
# 处理不能正确被踢的问题
|
||||||
|
|
||||||
|
for i in commands:
|
||||||
|
iv, ciphertext, tag = encrypt(user['key'], i, b'')
|
||||||
|
# print(binascii.b2a_hex(i))
|
||||||
|
|
||||||
|
server.sendto(token + iv + tag[:12] +
|
||||||
|
ciphertext, self.client_address)
|
||||||
|
|
||||||
|
|
||||||
|
class TCP_handler(socketserver.StreamRequestHandler):
|
||||||
|
def handle(self):
|
||||||
|
self.data = self.rfile.readline().strip()
|
||||||
|
|
||||||
|
message = self.data.decode('utf-8')
|
||||||
|
# print(message)
|
||||||
|
data = message.split('|')
|
||||||
|
if data[0] != Config.AUTHENTICATION:
|
||||||
|
self.wfile.write(b'No authentication')
|
||||||
|
return None
|
||||||
|
|
||||||
|
global clean_timer
|
||||||
|
now = round(time.time() * 1000)
|
||||||
|
if now - clean_timer >= Config.TIME_LIMIT:
|
||||||
|
clean_timer = now
|
||||||
|
memory_clean(now)
|
||||||
|
|
||||||
|
self.wfile.write(data_swap(data[1:]).encode('utf-8'))
|
||||||
|
|
||||||
|
|
||||||
|
def data_swap(data: list) -> str:
|
||||||
|
# data: list[str] = [command, ...]
|
||||||
|
if data[0] == '1':
|
||||||
|
# 开房
|
||||||
|
# data = ['1', name, song_unlock, ]
|
||||||
|
# song_unlock: base64 str
|
||||||
|
name = data[1]
|
||||||
|
song_unlock = base64.b64decode(data[2])
|
||||||
|
|
||||||
|
key = urandom(16)
|
||||||
|
room_id = unique_random(room_id_dict)
|
||||||
|
|
||||||
|
room = Room()
|
||||||
|
room.room_id = room_id
|
||||||
|
room_id_dict[room_id] = room
|
||||||
|
|
||||||
|
player_id = unique_random(player_dict, 3)
|
||||||
|
player = Player()
|
||||||
|
player.player_id = player_id
|
||||||
|
player.set_player_name(name)
|
||||||
|
player_dict[player_id] = player
|
||||||
|
|
||||||
|
player.song_unlock = song_unlock
|
||||||
|
room.song_unlock = song_unlock
|
||||||
|
room.host_id = player_id
|
||||||
|
room.players[0] = player
|
||||||
|
room.player_num = 1
|
||||||
|
|
||||||
|
room_code = unique_random(room_code_dict, random_func=random_room_code)
|
||||||
|
room.room_code = room_code
|
||||||
|
room_code_dict[room_code] = room
|
||||||
|
|
||||||
|
token = room_id
|
||||||
|
player.token = token
|
||||||
|
|
||||||
|
link_play_data[token] = {'key': key,
|
||||||
|
'room': room,
|
||||||
|
'player_index': 0,
|
||||||
|
'player_id': player_id}
|
||||||
|
|
||||||
|
return '|'.join([str(x) for x in (0, room_code, room_id, token, base64.b64encode(key).decode('utf-8'), player_id)])
|
||||||
|
|
||||||
|
elif data[0] == '2':
|
||||||
|
# 入房
|
||||||
|
# data = ['2', name, song_unlock, room_code]
|
||||||
|
# song_unlock: base64 str
|
||||||
|
room_code = data[3].upper()
|
||||||
|
|
||||||
|
if room_code not in room_code_dict:
|
||||||
|
# 房间号错误
|
||||||
|
return '1202'
|
||||||
|
|
||||||
|
room = room_code_dict[room_code]
|
||||||
|
if room.player_num == 4:
|
||||||
|
# 满人
|
||||||
|
return '1201'
|
||||||
|
elif room.state != 2:
|
||||||
|
# 无法加入
|
||||||
|
return '1205'
|
||||||
|
|
||||||
|
name = data[1]
|
||||||
|
song_unlock = base64.b64decode(data[2])
|
||||||
|
|
||||||
|
key = urandom(16)
|
||||||
|
token = unique_random(link_play_data)
|
||||||
|
player_id = unique_random(player_dict, 3)
|
||||||
|
|
||||||
|
player = Player()
|
||||||
|
player.player_id = player_id
|
||||||
|
player.set_player_name(name)
|
||||||
|
player.token = token
|
||||||
|
player_dict[player_id] = player
|
||||||
|
player.song_unlock = song_unlock
|
||||||
|
room.update_song_unlock()
|
||||||
|
for i in range(4):
|
||||||
|
if room.players[i].player_id == 0:
|
||||||
|
room.players[i] = player
|
||||||
|
player_index = i
|
||||||
|
break
|
||||||
|
link_play_data[token] = {'key': key,
|
||||||
|
'room': room,
|
||||||
|
'player_index': player_index,
|
||||||
|
'player_id': player_id}
|
||||||
|
|
||||||
|
return '|'.join([str(x) for x in (0, room_code, room.room_id, token, base64.b64encode(key).decode('utf-8'), player_id, base64.b64encode(room.song_unlock).decode('utf-8'))])
|
||||||
|
|
||||||
|
elif data[0] == '3':
|
||||||
|
# 房间信息更新
|
||||||
|
# data = ['3', token]
|
||||||
|
token = int(data[1])
|
||||||
|
if token in link_play_data:
|
||||||
|
r = link_play_data[token]
|
||||||
|
return '|'.join([str(x) for x in (0, r['room'].room_code, r['room'].room_id, base64.b64encode(r['key']).decode('utf-8'), r['room'].players[r['player_index']].player_id, base64.b64encode(r['room'].song_unlock).decode('utf-8'))])
|
||||||
|
else:
|
||||||
|
return '108'
|
||||||
|
|
||||||
|
|
||||||
|
def link_play(ip: str = Config.HOST, udp_port: int = Config.UDP_PORT, tcp_port: int = Config.TCP_PORT):
|
||||||
|
udp_server = socketserver.ThreadingUDPServer((ip, udp_port), UDP_handler)
|
||||||
|
tcp_server = socketserver.ThreadingTCPServer((ip, tcp_port), TCP_handler)
|
||||||
|
|
||||||
|
threads = [threading.Thread(target=udp_server.serve_forever), threading.Thread(
|
||||||
|
target=tcp_server.serve_forever)]
|
||||||
|
[t.start() for t in threads]
|
||||||
|
[t.join() for t in threads]
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from .udp_config import Config
|
from .config import Config
|
||||||
|
|
||||||
|
|
||||||
def b(value, length=1):
|
def b(value, length=1):
|
||||||
@@ -38,7 +38,7 @@ class Player:
|
|||||||
self.last_timestamp = 0
|
self.last_timestamp = 0
|
||||||
self.extra_command_queue = []
|
self.extra_command_queue = []
|
||||||
|
|
||||||
self.song_unlock = b'\x00' * Config.LINK_PLAY_UNLOCK_LENGTH
|
self.song_unlock: bytes = b'\x00' * Config.LINK_PLAY_UNLOCK_LENGTH
|
||||||
|
|
||||||
self.start_command_num = 0
|
self.start_command_num = 0
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from .udp_class import Room, bi
|
from .udp_class import Room, bi
|
||||||
from .udp_config import Config
|
from .config import Config
|
||||||
from .udp_sender import CommandSender
|
from .udp_sender import CommandSender
|
||||||
|
|
||||||
|
|
||||||
@@ -311,7 +311,7 @@ class CommandParser:
|
|||||||
self.room.command_queue_length += 1
|
self.room.command_queue_length += 1
|
||||||
self.room.command_queue.append(x.command_12(self.player_index))
|
self.room.command_queue.append(x.command_12(self.player_index))
|
||||||
|
|
||||||
if self.room.state == 3:
|
if self.room.state == 3 or self.room.state == 2:
|
||||||
self.room.state = 1
|
self.room.state = 1
|
||||||
self.room.song_idx = 0xffff
|
self.room.song_idx = 0xffff
|
||||||
# self.room.command_queue_length += 1
|
# self.room.command_queue_length += 1
|
||||||
@@ -142,13 +142,15 @@ def main():
|
|||||||
except:
|
except:
|
||||||
app.logger.warning('Initialization error!')
|
app.logger.warning('Initialization error!')
|
||||||
|
|
||||||
if Config.UDP_PORT and Config.UDP_PORT != '':
|
if Config.LINK_PLAY_HOST and Config.SET_LINK_PLAY_SERVER_AS_SUB_PROCESS:
|
||||||
from server.multiplayer import conn2
|
from linkplay_server import link_play
|
||||||
from udpserver.udp_main import link_play
|
|
||||||
process = [Process(target=link_play, args=(
|
process = [Process(target=link_play, args=(
|
||||||
conn2, Config.HOST, int(Config.UDP_PORT)))]
|
Config.LINK_PLAY_HOST, int(Config.LINK_PLAY_UDP_PORT), int(Config.LINK_PLAY_TCP_PORT)))]
|
||||||
[p.start() for p in process]
|
[p.start() for p in process]
|
||||||
app.logger.info("UDP server is running...")
|
app.logger.info("Link Play UDP server is running on " +
|
||||||
|
Config.LINK_PLAY_HOST + ':' + str(Config.LINK_PLAY_UDP_PORT) + " ...")
|
||||||
|
app.logger.info("Link Play TCP server is running on " +
|
||||||
|
Config.LINK_PLAY_HOST + ':' + str(Config.LINK_PLAY_TCP_PORT) + " ...")
|
||||||
tcp_server_run()
|
tcp_server_run()
|
||||||
[p.join() for p in process]
|
[p.join() for p in process]
|
||||||
else:
|
else:
|
||||||
|
|||||||
9
latest version/run_linkplay_server.py
Normal file
9
latest version/run_linkplay_server.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import linkplay_server
|
||||||
|
|
||||||
|
os.chdir(sys.path[0])
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
linkplay_server.link_play()
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
from multiprocessing import Pipe
|
|
||||||
|
|
||||||
from core.error import ArcError
|
from core.error import ArcError
|
||||||
from core.linkplay import LocalMultiPlayer, Player, Room
|
from core.linkplay import Player, RemoteMultiPlayer, Room
|
||||||
from core.sql import Connect
|
from core.sql import Connect
|
||||||
from flask import Blueprint, request
|
from flask import Blueprint, request
|
||||||
from setting import Config
|
from setting import Config
|
||||||
@@ -11,24 +9,23 @@ from .func import arc_try, success_return
|
|||||||
|
|
||||||
bp = Blueprint('multiplayer', __name__, url_prefix='/multiplayer')
|
bp = Blueprint('multiplayer', __name__, url_prefix='/multiplayer')
|
||||||
|
|
||||||
conn1, conn2 = Pipe()
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/me/room/create', methods=['POST']) # 创建房间
|
@bp.route('/me/room/create', methods=['POST']) # 创建房间
|
||||||
@auth_required(request)
|
@auth_required(request)
|
||||||
@arc_try
|
@arc_try
|
||||||
def room_create(user_id):
|
def room_create(user_id):
|
||||||
if not Config.UDP_PORT or Config.UDP_PORT == '':
|
if not Config.LINK_PLAY_HOST:
|
||||||
raise ArcError('The local udp server is down.', 151, status=404)
|
raise ArcError('The link play server is unavailable.', 151, status=404)
|
||||||
|
|
||||||
with Connect() as c:
|
with Connect() as c:
|
||||||
x = LocalMultiPlayer(conn1)
|
x = RemoteMultiPlayer()
|
||||||
user = Player(c, user_id)
|
user = Player(c, user_id)
|
||||||
user.get_song_unlock(request.json['clientSongMap'])
|
user.get_song_unlock(request.json['clientSongMap'])
|
||||||
x.create_room(user)
|
x.create_room(user)
|
||||||
r = x.to_dict()
|
r = x.to_dict()
|
||||||
r['endPoint'] = request.host.split(
|
r['endPoint'] = request.host.split(
|
||||||
':')[0] if Config.LINK_PLAY_HOST == '' else Config.LINK_PLAY_HOST
|
':')[0] if Config.LINK_PLAY_DISPLAY_HOST == '' else Config.LINK_PLAY_DISPLAY_HOST
|
||||||
r['port'] = int(Config.UDP_PORT)
|
r['port'] = int(Config.LINK_PLAY_UDP_PORT)
|
||||||
return success_return(r)
|
return success_return(r)
|
||||||
|
|
||||||
|
|
||||||
@@ -36,11 +33,11 @@ def room_create(user_id):
|
|||||||
@auth_required(request)
|
@auth_required(request)
|
||||||
@arc_try
|
@arc_try
|
||||||
def room_join(user_id, room_code):
|
def room_join(user_id, room_code):
|
||||||
if not Config.UDP_PORT or Config.UDP_PORT == '':
|
if not Config.LINK_PLAY_HOST:
|
||||||
raise ArcError('The local udp server is down.', 151, status=404)
|
raise ArcError('The link play server is unavailable.', 151, status=404)
|
||||||
|
|
||||||
with Connect() as c:
|
with Connect() as c:
|
||||||
x = LocalMultiPlayer(conn1)
|
x = RemoteMultiPlayer()
|
||||||
user = Player(c, user_id)
|
user = Player(c, user_id)
|
||||||
user.get_song_unlock(request.json['clientSongMap'])
|
user.get_song_unlock(request.json['clientSongMap'])
|
||||||
room = Room()
|
room = Room()
|
||||||
@@ -48,8 +45,8 @@ def room_join(user_id, room_code):
|
|||||||
x.join_room(room, user)
|
x.join_room(room, user)
|
||||||
r = x.to_dict()
|
r = x.to_dict()
|
||||||
r['endPoint'] = request.host.split(
|
r['endPoint'] = request.host.split(
|
||||||
':')[0] if Config.LINK_PLAY_HOST == '' else Config.LINK_PLAY_HOST
|
':')[0] if Config.LINK_PLAY_DISPLAY_HOST == '' else Config.LINK_PLAY_DISPLAY_HOST
|
||||||
r['port'] = int(Config.UDP_PORT)
|
r['port'] = int(Config.LINK_PLAY_UDP_PORT)
|
||||||
return success_return(r)
|
return success_return(r)
|
||||||
|
|
||||||
|
|
||||||
@@ -57,16 +54,16 @@ def room_join(user_id, room_code):
|
|||||||
@auth_required(request)
|
@auth_required(request)
|
||||||
@arc_try
|
@arc_try
|
||||||
def multiplayer_update(user_id):
|
def multiplayer_update(user_id):
|
||||||
if not Config.UDP_PORT or Config.UDP_PORT == '':
|
if not Config.LINK_PLAY_HOST:
|
||||||
raise ArcError('The local udp server is down.', 151, status=404)
|
raise ArcError('The link play server is unavailable.', 151, status=404)
|
||||||
|
|
||||||
with Connect() as c:
|
with Connect() as c:
|
||||||
x = LocalMultiPlayer(conn1)
|
x = RemoteMultiPlayer()
|
||||||
user = Player(c, user_id)
|
user = Player(c, user_id)
|
||||||
user.token = int(request.json['token'])
|
user.token = int(request.json['token'])
|
||||||
x.update_room(user)
|
x.update_room(user)
|
||||||
r = x.to_dict()
|
r = x.to_dict()
|
||||||
r['endPoint'] = request.host.split(
|
r['endPoint'] = request.host.split(
|
||||||
':')[0] if Config.LINK_PLAY_HOST == '' else Config.LINK_PLAY_HOST
|
':')[0] if Config.LINK_PLAY_DISPLAY_HOST == '' else Config.LINK_PLAY_DISPLAY_HOST
|
||||||
r['port'] = int(Config.UDP_PORT)
|
r['port'] = int(Config.LINK_PLAY_UDP_PORT)
|
||||||
return success_return(r)
|
return success_return(r)
|
||||||
|
|||||||
@@ -30,29 +30,27 @@ class Config():
|
|||||||
Allowed game versions
|
Allowed game versions
|
||||||
If it is blank, all are allowed.
|
If it is blank, all are allowed.
|
||||||
'''
|
'''
|
||||||
ALLOW_APPVERSION = ['3.12.6', '3.12.6c', '4.0.256', '4.0.256c', '4.0.255', '4.0.255c']
|
ALLOW_APPVERSION = ['3.12.6', '3.12.6c',
|
||||||
|
'4.0.256', '4.0.256c', '4.0.255', '4.0.255c']
|
||||||
'''
|
'''
|
||||||
--------------------
|
--------------------
|
||||||
'''
|
'''
|
||||||
|
|
||||||
'''
|
'''
|
||||||
--------------------
|
--------------------
|
||||||
联机功能的端口号,若为空,则默认不开启联机功能
|
联机功能相关设置,请确保与Link Play服务器端的设置一致
|
||||||
Port of your link play server
|
Setting of your link play server
|
||||||
If it is blank, link play will be unavailable.
|
Please ensure that the settings on the side of Link Play server are consistent.
|
||||||
'''
|
'''
|
||||||
UDP_PORT = '10900'
|
# SET_LINK_PLAY_SERVER_AS_SUB_PROCESS: If it is `True`, the link play server will run with the main server locally at the same time.
|
||||||
'''
|
SET_LINK_PLAY_SERVER_AS_SUB_PROCESS = True
|
||||||
--------------------
|
# LINK_PLAY_HOST: If it is blank, the link play feature will be disabled.
|
||||||
'''
|
LINK_PLAY_HOST = '0.0.0.0'
|
||||||
|
LINK_PLAY_UDP_PORT = 10900
|
||||||
'''
|
LINK_PLAY_TCP_PORT = 10901
|
||||||
--------------------
|
LINK_PLAY_AUTHENTICATION = 'my_link_play_server'
|
||||||
联机功能地址,留空则自动获取
|
# LINK_PLAY_DISPLAY_HOST: If it is blank, the host of link play server for the client will be obtained automatically.
|
||||||
Link Play address
|
LINK_PLAY_DISPLAY_HOST = ''
|
||||||
If left blank, it will be obtained automatically.
|
|
||||||
'''
|
|
||||||
LINK_PLAY_HOST = '' # ***.com
|
|
||||||
'''
|
'''
|
||||||
--------------------
|
--------------------
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
class Config:
|
|
||||||
TIME_LIMIT = 3600000
|
|
||||||
|
|
||||||
COMMAND_INTERVAL = 1000000
|
|
||||||
|
|
||||||
COUNTDOWM_TIME = 3999
|
|
||||||
|
|
||||||
PLAYER_PRE_TIMEOUT = 3000000
|
|
||||||
PLAYER_TIMEOUT = 20000000
|
|
||||||
|
|
||||||
LINK_PLAY_UNLOCK_LENGTH = 512
|
|
||||||
@@ -1,223 +0,0 @@
|
|||||||
import socketserver
|
|
||||||
import threading
|
|
||||||
import os
|
|
||||||
import random
|
|
||||||
import time
|
|
||||||
# import binascii
|
|
||||||
from . import aes
|
|
||||||
from .udp_parser import CommandParser
|
|
||||||
from .udp_class import Room, Player, bi
|
|
||||||
from .udp_config import Config
|
|
||||||
|
|
||||||
|
|
||||||
# token: {'key': key, 'room': Room, 'player_index': player_index, 'player_id': player_id}
|
|
||||||
link_play_data = {}
|
|
||||||
room_id_dict = {} # 'room_id': Room
|
|
||||||
room_code_dict = {} # 'room_code': Room
|
|
||||||
player_dict = {} # 'player_id' : Player
|
|
||||||
clean_timer = 0
|
|
||||||
|
|
||||||
|
|
||||||
def random_room_code():
|
|
||||||
# 随机生成房间号
|
|
||||||
re = ''
|
|
||||||
for _ in range(4):
|
|
||||||
re += chr(random.randint(65, 90))
|
|
||||||
for _ in range(2):
|
|
||||||
re += str(random.randint(0, 9))
|
|
||||||
|
|
||||||
return re
|
|
||||||
|
|
||||||
|
|
||||||
def clear_player(token):
|
|
||||||
# 清除玩家信息和token
|
|
||||||
del player_dict[link_play_data[token]['player_id']]
|
|
||||||
del link_play_data[token]
|
|
||||||
|
|
||||||
|
|
||||||
def clear_room(room):
|
|
||||||
# 清除房间信息
|
|
||||||
room_id = room.room_id
|
|
||||||
room_code = room.room_code
|
|
||||||
del room_id_dict[room_id]
|
|
||||||
del room_code_dict[room_code]
|
|
||||||
del room
|
|
||||||
|
|
||||||
|
|
||||||
def memory_clean(now):
|
|
||||||
# 内存清理
|
|
||||||
clean_room_list = []
|
|
||||||
clean_player_list = []
|
|
||||||
for token in link_play_data:
|
|
||||||
room = link_play_data[token]['room']
|
|
||||||
if now - room.timestamp >= Config.TIME_LIMIT:
|
|
||||||
clean_room_list.append(room.room_id)
|
|
||||||
|
|
||||||
if now - room.players[link_play_data[token]['player_index']].last_timestamp // 1000 >= Config.TIME_LIMIT:
|
|
||||||
clean_player_list.append(token)
|
|
||||||
|
|
||||||
for room_id in room_id_dict:
|
|
||||||
if now - room_id_dict[room_id].timestamp >= Config.TIME_LIMIT:
|
|
||||||
clean_room_list.append(room_id)
|
|
||||||
|
|
||||||
for room_id in clean_room_list:
|
|
||||||
if room_id in room_id_dict:
|
|
||||||
clear_room(room_id_dict[room_id])
|
|
||||||
|
|
||||||
for token in clean_player_list:
|
|
||||||
clear_player(token)
|
|
||||||
|
|
||||||
|
|
||||||
class UDPhandler(socketserver.BaseRequestHandler):
|
|
||||||
def handle(self):
|
|
||||||
client_msg, server = self.request
|
|
||||||
token = client_msg[:8]
|
|
||||||
iv = client_msg[8:20]
|
|
||||||
tag = client_msg[20:32]
|
|
||||||
ciphertext = client_msg[32:]
|
|
||||||
if int.from_bytes(token, byteorder='little') in link_play_data:
|
|
||||||
user = link_play_data[bi(token)]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
plaintext = aes.decrypt(user['key'], b'', iv, ciphertext, tag)
|
|
||||||
# print(binascii.b2a_hex(plaintext))
|
|
||||||
|
|
||||||
commands = CommandParser(
|
|
||||||
user['room'], user['player_index']).get_commands(plaintext)
|
|
||||||
|
|
||||||
if user['room'].players[user['player_index']].player_id == 0:
|
|
||||||
clear_player(bi(token))
|
|
||||||
temp = []
|
|
||||||
for i in commands:
|
|
||||||
if i[:3] == b'\x06\x16\x12':
|
|
||||||
temp.append(i)
|
|
||||||
commands = temp
|
|
||||||
# 处理不能正确被踢的问题
|
|
||||||
|
|
||||||
for i in commands:
|
|
||||||
iv, ciphertext, tag = aes.encrypt(user['key'], i, b'')
|
|
||||||
# print(binascii.b2a_hex(i))
|
|
||||||
|
|
||||||
server.sendto(token + iv + tag[:12] +
|
|
||||||
ciphertext, self.client_address)
|
|
||||||
|
|
||||||
|
|
||||||
def server_run(ip, port):
|
|
||||||
server = socketserver.ThreadingUDPServer((ip, port), UDPhandler)
|
|
||||||
server.serve_forever()
|
|
||||||
|
|
||||||
|
|
||||||
def data_swap(conn):
|
|
||||||
clean_timer = 0
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
data = conn.recv()
|
|
||||||
except EOFError:
|
|
||||||
break
|
|
||||||
|
|
||||||
now = round(time.time() * 1000)
|
|
||||||
if now - clean_timer >= Config.TIME_LIMIT:
|
|
||||||
clean_timer = now
|
|
||||||
memory_clean(now)
|
|
||||||
|
|
||||||
if data[0] == 1:
|
|
||||||
# 开房
|
|
||||||
key = os.urandom(16)
|
|
||||||
room_id = bi(os.urandom(8))
|
|
||||||
while room_id in room_id_dict and room_id == 0:
|
|
||||||
room_id = bi(os.urandom(8))
|
|
||||||
room = Room()
|
|
||||||
room.room_id = room_id
|
|
||||||
room_id_dict[room_id] = room
|
|
||||||
|
|
||||||
player_id = bi(os.urandom(3))
|
|
||||||
while player_id in player_dict and player_id == 0:
|
|
||||||
player_id = bi(os.urandom(3))
|
|
||||||
player = Player()
|
|
||||||
player.player_id = player_id
|
|
||||||
player.set_player_name(data[1])
|
|
||||||
player_dict[player_id] = player
|
|
||||||
|
|
||||||
player.song_unlock = data[2]
|
|
||||||
room.song_unlock = data[2]
|
|
||||||
room.host_id = player_id
|
|
||||||
room.players[0] = player
|
|
||||||
room.player_num = 1
|
|
||||||
|
|
||||||
room_code = random_room_code()
|
|
||||||
while room_code in room_code_dict:
|
|
||||||
room_code = random_room_code()
|
|
||||||
room.room_code = room_code
|
|
||||||
room_code_dict[room_code] = room
|
|
||||||
|
|
||||||
token = room_id
|
|
||||||
player.token = token
|
|
||||||
|
|
||||||
link_play_data[token] = {'key': key,
|
|
||||||
'room': room,
|
|
||||||
'player_index': 0,
|
|
||||||
'player_id': player_id}
|
|
||||||
|
|
||||||
conn.send((0, room_code, room_id, token, key, player_id))
|
|
||||||
|
|
||||||
elif data[0] == 2:
|
|
||||||
room_code = data[3].upper()
|
|
||||||
if room_code not in room_code_dict:
|
|
||||||
# 房间号错误
|
|
||||||
conn.send((1202, ))
|
|
||||||
else:
|
|
||||||
room = room_code_dict[room_code]
|
|
||||||
if room.player_num == 4:
|
|
||||||
# 满人
|
|
||||||
conn.send((1201, ))
|
|
||||||
elif room.state != 2:
|
|
||||||
# 无法加入
|
|
||||||
conn.send((1205, ))
|
|
||||||
else:
|
|
||||||
key = os.urandom(16)
|
|
||||||
token = bi(os.urandom(8))
|
|
||||||
|
|
||||||
player_id = bi(os.urandom(3))
|
|
||||||
while player_id in player_dict and player_id == 0:
|
|
||||||
player_id = bi(os.urandom(3))
|
|
||||||
player = Player()
|
|
||||||
player.player_id = player_id
|
|
||||||
player.set_player_name(data[1])
|
|
||||||
player.token = token
|
|
||||||
player_dict[player_id] = player
|
|
||||||
|
|
||||||
player.song_unlock = data[2]
|
|
||||||
|
|
||||||
room.update_song_unlock()
|
|
||||||
for i in range(4):
|
|
||||||
if room.players[i].player_id == 0:
|
|
||||||
room.players[i] = player
|
|
||||||
player_index = i
|
|
||||||
break
|
|
||||||
|
|
||||||
link_play_data[token] = {'key': key,
|
|
||||||
'room': room,
|
|
||||||
'player_index': player_index,
|
|
||||||
'player_id': player_id}
|
|
||||||
|
|
||||||
conn.send((0, room_code, room.room_id,
|
|
||||||
token, key, player_id, room.song_unlock))
|
|
||||||
elif data[0] == 3:
|
|
||||||
token = data[1]
|
|
||||||
if token in link_play_data:
|
|
||||||
r = link_play_data[token]
|
|
||||||
conn.send((0, r['room'].room_code, r['room'].room_id, r['key'],
|
|
||||||
r['room'].players[r['player_index']].player_id, r['room'].song_unlock))
|
|
||||||
else:
|
|
||||||
conn.send((108, ))
|
|
||||||
|
|
||||||
|
|
||||||
def link_play(conn, ip: str, port: int):
|
|
||||||
try:
|
|
||||||
server = threading.Thread(target=server_run, args=(ip, port))
|
|
||||||
data_exchange = threading.Thread(target=data_swap, args=(conn,))
|
|
||||||
server.start()
|
|
||||||
data_exchange.start()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
Reference in New Issue
Block a user