-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathserver.py
129 lines (105 loc) · 3.87 KB
/
server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import sys
import copy
import time
import socket, socket_util
from struct import *
from game import Game
import server_receive
import server_send
import _thread as thread
version = b'\x01'
header_fmt = '!cIc'
header_len = calcsize(header_fmt)
opcode_to_function = {
b'\x01': server_receive.create_request,
b'\x02': server_receive.move_request,
b'\x03': server_receive.restart_request,
}
client_to_player = {}
SECS_PER_TICK = 0.1
# Number of seconds of lag to impose on all connections
SECS_DELAY = 1
def game_handler(lock, game):
"""Game loop.
Args:
lock: _thread.lock object
game: game object
Waits SECS_PER_TICK in between advancing each game state.
Imposes SECS_DELAY of artificial lag in sending to each client.
"""
past_game_states = []
while True:
time.sleep(SECS_PER_TICK)
with lock:
game.tick()
game_copy = copy.deepcopy(game)
past_game_states.append(game_copy)
# If true, we've queued states for SECS_DELAY and can begin sending the old states
if len(past_game_states) > SECS_DELAY // SECS_PER_TICK:
game_to_send = past_game_states[0]
past_game_states = past_game_states[1:] # Remove sent game state from list of game states
# Broadcast game state to clients
for conn, player in client_to_player.items():
# Only send to the client if the player exists -- this matters when we impose
# our artificial lag (the server will send an old state in which the client does
# not yet exist, which will confuse the clients)
if player.username in game_to_send.players:
server_send.send_game(conn, game_to_send)
def disconnect(conn, lock, game):
"""Removes player from game and cleans up thread.
Args:
conn: socket of corersponding player
lock: _thread.lock object for the game state
game: game state to remove player from
Side effects:
Removes player from game and removes from client_to_player dictionary.
Exits thread.
"""
print('Disconnecting player...')
with lock:
game.remove_player(client_to_player[conn])
del client_to_player[conn]
thread.exit()
def client_handler(conn, lock, game):
"""Handles requests sent from the client to the server.
Args:
conn: connection on which to listen for messages.
lock: lock object
game: game the client is in.
"""
while True:
try:
msg = socket_util.recvall(conn, header_len)
except:
disconnect(conn, lock, game)
header = unpack(header_fmt, msg[:header_len])
if header[0] != version: # Check header version.
thread.exit()
# header[1] is message length
body_packed = socket_util.recvall(conn, header[1])
if header[1] != len(body_packed):
thread.exit()
opcode = header[2]
with lock:
opcode_to_function[opcode](conn, body_packed, game, client_to_player)
def main():
"""Initializes game, starts a thread to handle it, and binds a socket to listen.
"""
if(len(sys.argv) != 3):
print("Usage 'python server.py <host> <port>'. Example: python server.py 10.252.215.26 8090")
sys.exit()
game = Game()
lock = thread.allocate_lock()
thread.start_new_thread(game_handler, (lock, game))
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
host = str(sys.argv[1])
port = int(sys.argv[2])
sock.bind((host, port))
sock.listen(5)
print(f'Listening for connections on {host}:{port}...')
while True:
conn, addr = sock.accept()
print(f'Picked up new client {addr}')
thread.start_new_thread(client_handler, (conn, lock, game))
if __name__ == '__main__':
main()