removed server code (transferred to velnetserver repo)
parent
57310f180c
commit
1830235130
|
|
@ -1,54 +1,6 @@
|
|||
This is a text-based server. Text can be relatively efficient for servers, as long as you don't try to make it inefficient with long text values. Using base64 for messages greatly reduces the inefficiency, or even using binary for parts of the messages that could be longer.
|
||||
See VelNet for description of the server.
|
||||
|
||||
You start by making an ordinary tcp connection to the server.
|
||||
|
||||
The server relies upon usernames and passwords. Some out-of-band process is required to create these.
|
||||
|
||||
INCOMING MESSSAGES (sent by clients)
|
||||
|
||||
Join server - Provide a username and password. If valid, the server will create a new session for you, and send you message saying connected with the session id. It will mark that session as logged in. All subsequent commands require logged in. If you are in another session, it will end that session.
|
||||
|
||||
0:username:password\n (note, usernames and passwords cannot contain colons :s)
|
||||
|
||||
Get rooms - Assuming the session is logged in, you can get a list of public rooms. The server will retrieve the list of public rooms from its database, including who is in those rooms and send them to the client.
|
||||
|
||||
1:password\n
|
||||
|
||||
Join room - The server will put you in a valid room, or it will create a room and put you in it. You will start receiving messages from that room, starting with a room_joined message, and then all subsequent messages.
|
||||
|
||||
2:room:password\n
|
||||
|
||||
2:-1\n - Leave room, this should choose a new room owner or something
|
||||
|
||||
Send message - You can say "all", "others", or list specific users that should receive the message.
|
||||
3:0:Message\n - others in room
|
||||
3:1:Message\n - all in room
|
||||
3:2:Message\n - others in the room, at once, meaning order is maintained
|
||||
3:3:Message\n - all in room, order maintained
|
||||
|
||||
4:group:Message\n - message a specific group
|
||||
5:group:c1:c2:c3\n - specify a named group of clients (used with 4)
|
||||
|
||||
OUTGOING MESSAGES (send by server)
|
||||
|
||||
Session_started - you know you are logged in, subsequent commands will succeed
|
||||
0:[client_id]\n
|
||||
|
||||
rooms - a list of rooms on the server, with counts
|
||||
1:r1-N,r2-N...\n
|
||||
|
||||
2:user_id:room_joined\n - room joined
|
||||
2:user_id:\n - room left
|
||||
|
||||
3:user_id:Message\n - message from sender user_id
|
||||
|
||||
4:user_id\n - New master client
|
||||
|
||||
|
||||
|
||||
Unity-side.
|
||||
|
||||
The server is relatively stupid, by design, as it's just a relay (for now). The Unity side is pretty complex.
|
||||
By contrast to the server, the Unity side is pretty complex.
|
||||
|
||||
NetworkManager - Deals with instantiating NetworkPlayers, and dealing with join, left, new master client events. Also manages the list of NetworkObjects. It maintains a list of prefabs that can be instantiated, as well as the prefab to use for players.
|
||||
|
||||
|
|
@ -58,12 +10,9 @@ NetworkObject - Something that can be owned by a network player, who is responsi
|
|||
|
||||
Ownership of objects changes regularly. Any local networkplayer can send a message to take ownership of a networkid. That client immediately assumes ownership. The message is sent to the network, and will be ordered with any other ownership messages. So, if two clients try to get ownership at the same time, both will assume ownership, but one will happen after the other. This means that the one who was first will quickly lose ownership.
|
||||
|
||||
An interesting problem is what to do when new clients join. This is partially left up to the developer. Any objects that are instantiated (or scene objects that are deleted) will be automatically handled. (todo-include an instantiation packet).
|
||||
An interesting problem is what to do when new clients join. This is partially left up to the developer. Any objects that are instantiated (or scene objects that are deleted) will be automatically handled.
|
||||
|
||||
Clients can manage network traffic using messaging groups. This is especially useful for audio, where you can maintain a list of people close enough to you to hear audio
|
||||
|
||||
Todo:
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
import selectors
|
||||
import socket
|
||||
import types
|
||||
import time
|
||||
import sys
|
||||
import select
|
||||
import tty
|
||||
import termios
|
||||
def isData():
|
||||
return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])
|
||||
|
||||
|
||||
HOST = '127.0.0.1' # The server's hostname or IP address
|
||||
PORT = 80 # The port used by the server
|
||||
sel = selectors.DefaultSelector()
|
||||
i = 0
|
||||
message = ""
|
||||
def service_connection(key, mask, message):
|
||||
sock = key.fileobj
|
||||
data = key.data
|
||||
if mask & selectors.EVENT_READ:
|
||||
recv_data = sock.recv(1024) # Should be ready to read
|
||||
if recv_data:
|
||||
print('received', repr(recv_data),flush=True)
|
||||
|
||||
if mask & selectors.EVENT_WRITE:
|
||||
if message:
|
||||
data.outb += message
|
||||
message = ""
|
||||
print('sending', repr(data.outb))
|
||||
sent = sock.send(data.outb.encode('utf-8')) # Should be ready to write
|
||||
data.outb = data.outb[sent:]
|
||||
|
||||
def start_connection(host, port):
|
||||
server_addr = (host, port)
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.setblocking(False)
|
||||
sock.connect_ex(server_addr)
|
||||
events = selectors.EVENT_READ | selectors.EVENT_WRITE
|
||||
data = types.SimpleNamespace(outb='')
|
||||
sel.register(sock, events, data=data)
|
||||
|
||||
|
||||
start_connection(HOST, PORT)
|
||||
|
||||
old_settings = termios.tcgetattr(sys.stdin)
|
||||
|
||||
try:
|
||||
tty.setcbreak(sys.stdin.fileno())
|
||||
|
||||
temp_message = ""
|
||||
while 1:
|
||||
events = sel.select(timeout=None)
|
||||
for key, mask in events:
|
||||
if key.data is None:
|
||||
pass
|
||||
else:
|
||||
service_connection(key, mask, message)
|
||||
message = ""
|
||||
|
||||
if isData():
|
||||
c = sys.stdin.read(1)
|
||||
|
||||
if c == '\x1b': # x1b is ESC
|
||||
break
|
||||
temp_message = temp_message+c
|
||||
if c == '\n':
|
||||
message = temp_message
|
||||
temp_message = ""
|
||||
finally:
|
||||
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
|
||||
|
||||
|
||||
|
||||
|
||||
147
server.py
147
server.py
|
|
@ -1,147 +0,0 @@
|
|||
|
||||
import socket
|
||||
import selectors
|
||||
import types
|
||||
|
||||
def decodeMessage(message, addr):
|
||||
global last_client_id
|
||||
global rooms
|
||||
global clients
|
||||
global temporary_clients
|
||||
# first get the client sending the message
|
||||
client = temporary_clients[addr]
|
||||
decodedMessage = message.split(":")
|
||||
if len(decodedMessage) < 1: print("Invalid message received"); return
|
||||
messageType = decodedMessage[0]
|
||||
|
||||
if not client.logged_in and messageType == '0' and len(decodedMessage) == 3:
|
||||
client.username = decodedMessage[1]
|
||||
client.id = last_client_id
|
||||
last_client_id = last_client_id+1
|
||||
#probaby check password too against a database of some sort where we store lots of good stuff
|
||||
client.logged_in = True
|
||||
#add to this list too
|
||||
clients[client.id] = client
|
||||
print("sending back login success")
|
||||
client.outb += f"0:{client.id}:\n"
|
||||
elif client.logged_in:
|
||||
if messageType == '1':
|
||||
|
||||
response = "1:" + ",".join([room.name+"-"+str(len(room.clients)) for room in rooms.values()]) + "\n"
|
||||
client.outb += response
|
||||
if messageType == '2' and len(decodedMessage) > 1:
|
||||
#join or create a room
|
||||
roomName = decodedMessage[1]
|
||||
if roomName == '-1':
|
||||
#leave the room
|
||||
try:
|
||||
rooms[client.room].clients.remove(client)
|
||||
if(len(rooms[client.room].clients) == 0):
|
||||
del rooms[client.room]
|
||||
except Exception as e:
|
||||
print("not in room")
|
||||
client.room = ''
|
||||
else:
|
||||
if roomName in rooms:
|
||||
#join the room
|
||||
rooms[roomName].clients.append(client)
|
||||
else:
|
||||
#create the room and join
|
||||
rooms[roomName] = types.SimpleNamespace(name=roomName,clients=[client])
|
||||
client.room = roomName #client joins the room
|
||||
#send everyone in the room a message
|
||||
for client in rooms[roomName].clients:
|
||||
client.outb += f"2:{client.id}:1\n"
|
||||
if messageType == '3' and len(decodedMessage) > 2:
|
||||
subMessageType = decodedMessage[1]
|
||||
if subMessageType == '0':
|
||||
#send a message to everyone in the room
|
||||
for c in rooms[client.room].clients:
|
||||
c.outb += f"3:{client.id}:{decodedMessage[2]}\n"
|
||||
|
||||
elif subMessageType == '1':
|
||||
for c in rooms[client.room].clients:
|
||||
if client.id != c.id:
|
||||
c.outb += f"3:{client.id}:{decodedMessage[2]}\n"
|
||||
pass
|
||||
elif subMessageType == '2':
|
||||
#send a message to the client ids indicated
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def accept_wrapper(sock):
|
||||
conn, addr = sock.accept() # Should be ready to read
|
||||
print('accepted connection from', addr)
|
||||
conn.setblocking(False)
|
||||
client = types.SimpleNamespace(id=-1, addr=addr, inb='', outb='', logged_in=False, username='',room='') #surrogate for class
|
||||
events = selectors.EVENT_READ | selectors.EVENT_WRITE
|
||||
sel.register(conn, events, data=client)
|
||||
temporary_clients[addr] = client #add to the clients dictionary
|
||||
|
||||
|
||||
def service_connection(key, mask):
|
||||
sock = key.fileobj
|
||||
data = key.data
|
||||
|
||||
if mask & selectors.EVENT_READ:
|
||||
recv_data = sock.recv(1024) # Should be ready to read
|
||||
if recv_data:
|
||||
m = recv_data.decode("utf-8")
|
||||
messages = m.split("\n")
|
||||
if len(messages) > 1:
|
||||
messages[0]= data.inb + messages[0]
|
||||
data.inb = ""
|
||||
for message in messages[:-1]:
|
||||
decodeMessage(message, data.addr)
|
||||
data.inb += messages[-1]
|
||||
|
||||
else:
|
||||
print('closing connection to', data.addr)
|
||||
client = temporary_clients[data.addr]
|
||||
temporary_clients.pop(data.addr)
|
||||
if client.logged_in:
|
||||
clients.pop(client.id)
|
||||
if(client.room != ''):
|
||||
rooms[client.room].clients.remove(client)
|
||||
if(len(rooms[client.room].clients) == 0):
|
||||
del rooms[client.room]
|
||||
sel.unregister(sock)
|
||||
sock.close()
|
||||
if mask & selectors.EVENT_WRITE:
|
||||
if data.outb:
|
||||
print('echoing', data.outb, 'to', data.addr)
|
||||
sent = sock.send(data.outb.encode('utf-8')) # Should be ready to write
|
||||
data.outb = data.outb[sent:]
|
||||
|
||||
sel = selectors.DefaultSelector()
|
||||
host = '127.0.0.1'
|
||||
port = 80
|
||||
temporary_clients = {} #organized by addr
|
||||
clients = {} #clients is a dictionary organized by an increasing id number. For now, passwords are irrelevant
|
||||
rooms = {}
|
||||
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as lsock:
|
||||
lsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
|
||||
lsock.bind((host, port))
|
||||
lsock.listen()
|
||||
print('listening on', (host, port))
|
||||
lsock.setblocking(False)
|
||||
sel.register(lsock, selectors.EVENT_READ, data=None)
|
||||
|
||||
|
||||
|
||||
last_client_id = 0
|
||||
|
||||
while True:
|
||||
events = sel.select(timeout=None)
|
||||
for key, mask in events:
|
||||
if key.data is None:
|
||||
accept_wrapper(key.fileobj)
|
||||
else:
|
||||
service_connection(key, mask)
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
import selectors
|
||||
import socket
|
||||
import types
|
||||
import time
|
||||
HOST = '127.0.0.1' # The server's hostname or IP address
|
||||
PORT = 80 # The port used by the server
|
||||
sel = selectors.DefaultSelector()
|
||||
i = 0
|
||||
message = ""
|
||||
def service_connection(key, mask, message):
|
||||
sock = key.fileobj
|
||||
data = key.data
|
||||
if mask & selectors.EVENT_READ:
|
||||
recv_data = sock.recv(1024) # Should be ready to read
|
||||
if recv_data:
|
||||
print('received', repr(recv_data),flush=True)
|
||||
|
||||
if mask & selectors.EVENT_WRITE:
|
||||
if message:
|
||||
data.outb += message
|
||||
message = ""
|
||||
print('sending', repr(data.outb))
|
||||
sent = sock.send(data.outb.encode('utf-8')) # Should be ready to write
|
||||
data.outb = data.outb[sent:]
|
||||
|
||||
def start_connection(host, port):
|
||||
server_addr = (host, port)
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.setblocking(False)
|
||||
sock.connect_ex(server_addr)
|
||||
events = selectors.EVENT_READ | selectors.EVENT_WRITE
|
||||
data = types.SimpleNamespace(outb='')
|
||||
sel.register(sock, events, data=data)
|
||||
|
||||
|
||||
start_connection(HOST, PORT)
|
||||
while True:
|
||||
events = sel.select(timeout=None)
|
||||
for key, mask in events:
|
||||
if key.data is None:
|
||||
pass
|
||||
else:
|
||||
service_connection(key, mask, message)
|
||||
message = ""
|
||||
|
||||
i += 1
|
||||
if i % 10 == 0:
|
||||
message = f"hello {i}\n"
|
||||
|
||||
|
||||
|
|
@ -1,213 +0,0 @@
|
|||
|
||||
import socket
|
||||
from _thread import *
|
||||
import threading
|
||||
import types
|
||||
|
||||
rooms = {} #will be a list of room objects. #this must be locked when when adding or removing rooms
|
||||
rooms_lock = threading.Lock()
|
||||
client_dict = {} #will be a list of client objects. #this must be locked when adding or removing clients
|
||||
client_lock = threading.Lock()
|
||||
|
||||
HOST = ""
|
||||
PORT = 80
|
||||
|
||||
|
||||
def send_synced_room_message(roomName, message, exclude_client=None): #guaranteed to be received by all clients in order, mostly use for handling ownership
|
||||
rooms_lock.acquire()
|
||||
if roomName in rooms:
|
||||
room_lock = rooms[roomName].room_lock
|
||||
clients = rooms[roomName].clients
|
||||
else:
|
||||
rooms_lock.release()
|
||||
return
|
||||
rooms_lock.release()
|
||||
room_lock.acquire()
|
||||
for c in clients:
|
||||
if (exclude_client != None) and (c.id == exclude_client.id): continue
|
||||
send_client_message(c,message)
|
||||
room_lock.release()
|
||||
|
||||
def send_room_message(roomName, message, exclude_client=None): #not guaranteed to be received by all clients in order
|
||||
rooms_lock.acquire()
|
||||
if roomName in rooms:
|
||||
clients = rooms[roomName].clients
|
||||
else:
|
||||
rooms_lock.release()
|
||||
return
|
||||
rooms_lock.release()
|
||||
for c in clients:
|
||||
if (exclude_client != None) and (c.id == exclude_client.id): continue
|
||||
send_client_message(c,message)
|
||||
|
||||
def send_client_message(client, message):
|
||||
client.message_lock.acquire()
|
||||
client.message_queue.append(message)
|
||||
client.message_lock.release()
|
||||
client.message_ready.set()
|
||||
|
||||
def decode_message(client,message):
|
||||
global rooms
|
||||
global rooms_lock
|
||||
decodedMessage = message.split(":")
|
||||
if len(decodedMessage) < 1: print("Invalid message received"); return
|
||||
messageType = decodedMessage[0]
|
||||
|
||||
if not client.logged_in and messageType == '0' and len(decodedMessage) == 3:
|
||||
client.username = decodedMessage[1]
|
||||
#probaby check password too against a database of some sort where we store lots of good stuff
|
||||
client.logged_in = True
|
||||
send_client_message(client,f"0:{client.id}:\n")
|
||||
|
||||
elif client.logged_in:
|
||||
if messageType == '1':
|
||||
rooms_lock.acquire()
|
||||
response = "1:" + ",".join([room.name+"-"+str(len(room.clients)) for room in rooms.values()]) + "\n"
|
||||
rooms_lock.release()
|
||||
send_client_message(client,response)
|
||||
if messageType == '2' and len(decodedMessage) > 1:
|
||||
|
||||
#join or create a room
|
||||
|
||||
roomName = decodedMessage[1]
|
||||
if client.room == roomName: #don't join the same room
|
||||
pass
|
||||
elif (roomName == '-1') and client.room != '': #can't leave a room if you aren't in one
|
||||
#leave the room
|
||||
rooms_lock.acquire()
|
||||
try:
|
||||
|
||||
rooms[client.room].clients.remove(client)
|
||||
if(len(rooms[client.room].clients) == 0):
|
||||
del rooms[client.room]
|
||||
except Exception as e:
|
||||
print("not in room")
|
||||
rooms_lock.release()
|
||||
send_room_message(client.room, f"2:{client.id}:\n")
|
||||
send_client_message(client,f"2:{client.id}:\n")
|
||||
client.room = ''
|
||||
else: #join or create the room
|
||||
rooms_lock.acquire()
|
||||
if roomName in rooms:
|
||||
#join the room
|
||||
rooms[roomName].clients.append(client)
|
||||
else:
|
||||
#create the room and join
|
||||
rooms[roomName] = types.SimpleNamespace(name=roomName,clients=[client],room_lock=threading.Lock())
|
||||
rooms_lock.release()
|
||||
|
||||
if (client.room != '') and (client.room != roomName): #client left the previous room
|
||||
send_room_message(client.room, f"2:{client.id}:{roomName}:\n")
|
||||
|
||||
client.room = roomName #client joins the new room
|
||||
#send a message to the clients new room that they joined!
|
||||
send_room_message(roomName, f"2:{client.id}:{client.room}\n")
|
||||
|
||||
|
||||
|
||||
|
||||
if messageType == '3' and len(decodedMessage) > 2:
|
||||
subMessageType = decodedMessage[1]
|
||||
if subMessageType == '0':
|
||||
#send a message to everyone in the room (not synced)
|
||||
send_room_message(client.room,f"3:{client.id}:{decodedMessage[2]}\n",client)
|
||||
elif subMessageType == '1':
|
||||
send_room_message(client.room,f"3:{client.id}:{decodedMessage[2]}\n")
|
||||
elif subMessageType == '2':
|
||||
#send a message to everyone in the room (not synced)
|
||||
send_synced_room_message(client.room,f"3:{client.id}:{decodedMessage[2]}\n",client)
|
||||
elif subMessageType == '3':
|
||||
send_synced_room_message(client.room,f"3:{client.id}:{decodedMessage[2]}\n")
|
||||
|
||||
def client_read_thread(conn, addr, client):
|
||||
global rooms
|
||||
global rooms_lock
|
||||
global client_dict
|
||||
global client_lock
|
||||
buffer = bytearray()
|
||||
state = 0
|
||||
buffer_size = 0
|
||||
#valid messages are at least 2 bytes (size)
|
||||
|
||||
while client.alive:
|
||||
try:
|
||||
recv_data = conn.recv(1024)
|
||||
except Exception as e:
|
||||
client.alive = False
|
||||
client.message_ready.set()
|
||||
continue
|
||||
if not recv_data:
|
||||
client.alive = False
|
||||
client.message_ready.set() #in case it's waiting for a message
|
||||
else:
|
||||
buffer.extend(recv_data) #read
|
||||
if (state == 0) and (len(buffer) > 2):
|
||||
buffer_size = int.from_bytes(buffer[0:2], byteorder='big')
|
||||
state = 1
|
||||
if (state == 1) and (len(buffer) >= buffer_size):
|
||||
#we have a complete packet, process it
|
||||
message = buffer[2:buffer_size]
|
||||
decode_message(client,message)
|
||||
buffer = buffer[buffer_size:]
|
||||
state = 0
|
||||
|
||||
while not client.write_thread_dead:
|
||||
client.message_ready.set()
|
||||
pass
|
||||
#now we can kill the client, removing the client from the rooms
|
||||
client_lock.acquire()
|
||||
rooms_lock.acquire()
|
||||
if client.room != '':
|
||||
rooms[client.room].clients.remove(client)
|
||||
if(len(rooms[client.room].clients) == 0):
|
||||
del rooms[client.room]
|
||||
del client_dict[client.id] #remove the client from the list of clients...
|
||||
rooms_lock.release()
|
||||
client_lock.release()
|
||||
send_room_message(client.room, f"2:{client.id}:\n")
|
||||
print("client destroyed")
|
||||
def client_write_thread(conn, addr, client):
|
||||
while client.alive:
|
||||
|
||||
client.message_lock.acquire()
|
||||
for message in client.message_queue:
|
||||
try:
|
||||
conn.sendall(message.encode('utf-8'))
|
||||
except:
|
||||
break #if the client is dead the read thread will catch it
|
||||
client.message_queue = []
|
||||
client.message_lock.release()
|
||||
client.message_ready.wait()
|
||||
client.message_ready.clear()
|
||||
client.write_thread_dead = True
|
||||
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
|
||||
sock.bind((HOST, PORT))
|
||||
sock.listen()
|
||||
next_client_id = 0
|
||||
while True:
|
||||
|
||||
c, addr = sock.accept() #blocks until a connection is made
|
||||
client = types.SimpleNamespace(id=next_client_id,
|
||||
alive=True,
|
||||
message_queue=[],
|
||||
message_lock=threading.Lock(),
|
||||
inb='', #read buffer
|
||||
message_ready=threading.Event(),
|
||||
logged_in=False,
|
||||
username='',
|
||||
room='',
|
||||
write_thread_dead=False
|
||||
)
|
||||
client_lock.acquire()
|
||||
client_dict[next_client_id] = client
|
||||
client_lock.release()
|
||||
|
||||
next_client_id += 1
|
||||
|
||||
start_new_thread(client_read_thread, (c, addr, client))
|
||||
start_new_thread(client_write_thread, (c, addr, client))
|
||||
|
||||
|
||||
|
|
@ -1,254 +0,0 @@
|
|||
|
||||
import socket
|
||||
from _thread import *
|
||||
import threading
|
||||
import types
|
||||
|
||||
rooms = {} #will be a list of room objects. #this must be locked when when adding or removing rooms
|
||||
rooms_lock = threading.Lock()
|
||||
client_dict = {} #will be a list of client objects. #this must be locked when adding or removing clients
|
||||
client_lock = threading.Lock()
|
||||
|
||||
HOST = ""
|
||||
PORT = 3290
|
||||
|
||||
|
||||
def send_synced_room_message(roomName, message, exclude_client=None): #guaranteed to be received by all clients in order, mostly use for handling ownership
|
||||
rooms_lock.acquire()
|
||||
if roomName in rooms:
|
||||
room_lock = rooms[roomName].room_lock
|
||||
clients = rooms[roomName].clients
|
||||
else:
|
||||
rooms_lock.release()
|
||||
return
|
||||
rooms_lock.release()
|
||||
room_lock.acquire()
|
||||
for c in clients:
|
||||
if (exclude_client != None) and (c.id == exclude_client.id): continue
|
||||
send_client_message(c,message)
|
||||
room_lock.release()
|
||||
|
||||
def send_room_message(roomName, message, exclude_client=None): #not guaranteed to be received by all clients in order
|
||||
rooms_lock.acquire()
|
||||
if roomName in rooms:
|
||||
clients = rooms[roomName].clients
|
||||
else:
|
||||
rooms_lock.release()
|
||||
return
|
||||
rooms_lock.release()
|
||||
for c in clients:
|
||||
if (exclude_client != None) and (c.id == exclude_client.id): continue
|
||||
send_client_message(c,message)
|
||||
|
||||
def send_group_message(group, message):
|
||||
for client_id in group:
|
||||
if client_id in client_dict:
|
||||
client = client_dict[client_id] #not sure if we need to lock this...I've heard that basic dictionary access is thread safe
|
||||
send_client_message(client, message)
|
||||
|
||||
|
||||
def send_client_message(client, message):
|
||||
client.message_lock.acquire()
|
||||
client.message_queue.append(message)
|
||||
client.message_lock.release()
|
||||
client.message_ready.set()
|
||||
|
||||
def leave_room(client, clientDisconnected=False):
|
||||
global rooms
|
||||
global rooms_lock
|
||||
choseNewMaster = False
|
||||
newMasterId = -1
|
||||
rooms_lock.acquire()
|
||||
try:
|
||||
rooms[client.room].clients.remove(client)
|
||||
if(len(rooms[client.room].clients) == 0):
|
||||
del rooms[client.room]
|
||||
elif rooms[client.room].master == client:
|
||||
rooms[client.room].master = rooms[client.room].clients[0]
|
||||
newMasterId = rooms[client.room].master.id
|
||||
choseNewMaster = True
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print("not in room")
|
||||
rooms_lock.release()
|
||||
send_room_message(client.room, f"2:{client.id}:\n") #client not in the room anymore
|
||||
if not clientDisconnected:
|
||||
send_client_message(client,f"2:{client.id}:\n") #so send again to them
|
||||
else:
|
||||
client_lock.acquire()
|
||||
del client_dict[client.id] #remove the client from the list of clients...
|
||||
client_lock.release()
|
||||
if choseNewMaster:
|
||||
send_room_message(client.room,f"4:{newMasterId}\n")
|
||||
client.room = ""
|
||||
|
||||
def decode_message(client,message):
|
||||
global rooms
|
||||
global rooms_lock
|
||||
decodedMessage = message.split(":")
|
||||
if len(decodedMessage) < 1: print("Invalid message received"); return
|
||||
messageType = decodedMessage[0]
|
||||
|
||||
if not client.logged_in and messageType == '0' and len(decodedMessage) == 3:
|
||||
client.username = decodedMessage[1]
|
||||
#probaby check password too against a database of some sort where we store lots of good stuff
|
||||
client.logged_in = True
|
||||
send_client_message(client,f"0:{client.id}:\n")
|
||||
|
||||
elif client.logged_in:
|
||||
if messageType == '1':
|
||||
rooms_lock.acquire()
|
||||
response = "1:" + ",".join([room.name+"-"+str(len(room.clients)) for room in rooms.values()]) + "\n"
|
||||
rooms_lock.release()
|
||||
send_client_message(client,response)
|
||||
if messageType == '2' and len(decodedMessage) > 1:
|
||||
|
||||
#join or create a room
|
||||
print("request to join " + decodedMessage[1] + " from " + str(client.id))
|
||||
roomName = decodedMessage[1]
|
||||
if client.room == roomName: #don't join the same room
|
||||
print("Client trying to join the same room")
|
||||
pass
|
||||
elif (roomName == '-1') and client.room != '': #can't leave a room if you aren't in one
|
||||
#leave the room
|
||||
leave_room(client)
|
||||
elif roomName != '': #join or create the room
|
||||
|
||||
|
||||
if client.room != '':
|
||||
#leave that room
|
||||
leave_room(client)
|
||||
|
||||
masterId = -1
|
||||
rooms_lock.acquire()
|
||||
|
||||
if roomName in rooms:
|
||||
#join the room
|
||||
rooms[roomName].clients.append(client)
|
||||
masterId = rooms[roomName].master.id
|
||||
else:
|
||||
#create the room and join it as master
|
||||
rooms[roomName] = types.SimpleNamespace(name=roomName,clients=[client],master=client, room_lock=threading.Lock())
|
||||
masterId = client.id
|
||||
|
||||
current_clients = rooms[roomName].clients
|
||||
rooms_lock.release()
|
||||
|
||||
client.room = roomName #client joins the new room
|
||||
#send a message to the clients new room that they joined!
|
||||
send_room_message(roomName, f"2:{client.id}:{client.room}\n")
|
||||
|
||||
|
||||
for c in current_clients: #tell that client about all the other clients in the room
|
||||
if c.id != client.id:
|
||||
send_client_message(client,f"2:{c.id}:{client.room}\n")
|
||||
|
||||
send_client_message(client, f"4:{masterId}\n") #tell the client who the master is
|
||||
|
||||
|
||||
|
||||
|
||||
if messageType == '3' and len(decodedMessage) > 2:
|
||||
subMessageType = decodedMessage[1]
|
||||
if subMessageType == '0':
|
||||
#send a message to everyone else in the room (not synced)
|
||||
send_room_message(client.room,f"3:{client.id}:{decodedMessage[2]}\n",client)
|
||||
elif subMessageType == '1': #everyone including the client who sent it
|
||||
send_room_message(client.room,f"3:{client.id}:{decodedMessage[2]}\n")
|
||||
elif subMessageType == '2': #everyone but the client, ensures order within the room
|
||||
send_synced_room_message(client.room,f"3:{client.id}:{decodedMessage[2]}\n",client)
|
||||
elif subMessageType == '3': #everyone including the client, ensuring order
|
||||
send_synced_room_message(client.room,f"3:{client.id}:{decodedMessage[2]}\n")
|
||||
if messageType == '4' and len(decodedMessage) > 2:
|
||||
if decodedMessage[1] in client.groups:
|
||||
send_group_message(client.groups[decodedMessage[1]],f"3:{client.id}:{decodedMessage[2]}\n")
|
||||
if messageType == '5' and len(decodedMessage) > 2:
|
||||
#specify a new group of clients
|
||||
group_clients = []
|
||||
for c in decodedMessage[2:]:
|
||||
try: group_clients.append(int(c))
|
||||
except: pass
|
||||
client.groups[decodedMessage[1]] = group_clients
|
||||
print("got new group: " + str(client.groups))
|
||||
|
||||
def client_read_thread(conn, addr, client):
|
||||
global rooms
|
||||
global rooms_lock
|
||||
global client_dict
|
||||
global client_lock
|
||||
while client.alive:
|
||||
try:
|
||||
recv_data = conn.recv(1024)
|
||||
except Exception as e:
|
||||
client.alive = False
|
||||
client.message_ready.set()
|
||||
continue
|
||||
if not recv_data:
|
||||
client.alive = False
|
||||
client.message_ready.set() #in case it's waiting for a message
|
||||
else:
|
||||
m = recv_data.decode("utf-8")
|
||||
messages = m.split("\n")
|
||||
if len(messages) > 1:
|
||||
messages[0]= client.inb + messages[0]
|
||||
client.inb = ""
|
||||
for message in messages[:-1]:
|
||||
decode_message(client, message)
|
||||
client.inb += messages[-1]
|
||||
while not client.write_thread_dead:
|
||||
client.message_ready.set()
|
||||
pass
|
||||
#now we can kill the client, removing the client from the rooms
|
||||
|
||||
|
||||
leave_room(client,True)
|
||||
|
||||
print("client destroyed")
|
||||
def client_write_thread(conn, addr, client):
|
||||
while client.alive:
|
||||
|
||||
client.message_lock.acquire()
|
||||
for message in client.message_queue:
|
||||
try:
|
||||
conn.sendall(message.encode('utf-8'))
|
||||
except:
|
||||
break #if the client is dead the read thread will catch it
|
||||
client.message_queue = []
|
||||
client.message_lock.release()
|
||||
client.message_ready.wait()
|
||||
client.message_ready.clear()
|
||||
client.write_thread_dead = True
|
||||
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
|
||||
sock.bind((HOST, PORT))
|
||||
sock.listen()
|
||||
next_client_id = 0
|
||||
while True:
|
||||
|
||||
c, addr = sock.accept() #blocks until a connection is made
|
||||
c.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
client = types.SimpleNamespace(id=next_client_id,
|
||||
alive=True,
|
||||
message_queue=[],
|
||||
message_lock=threading.Lock(),
|
||||
inb='', #read buffer
|
||||
message_ready=threading.Event(),
|
||||
logged_in=False,
|
||||
username='',
|
||||
room='',
|
||||
groups={}, # a dictionary of groups that you may send to. groups are lists of user ids
|
||||
write_thread_dead=False
|
||||
)
|
||||
client_lock.acquire()
|
||||
client_dict[next_client_id] = client
|
||||
client_lock.release()
|
||||
|
||||
next_client_id += 1
|
||||
|
||||
start_new_thread(client_read_thread, (c, addr, client))
|
||||
start_new_thread(client_write_thread, (c, addr, client))
|
||||
|
||||
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
import socket
|
||||
import time
|
||||
from _thread import *
|
||||
import threading
|
||||
|
||||
server_addr = ('localhost', 3290)
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
sock.connect_ex(server_addr)
|
||||
|
||||
|
||||
|
||||
|
||||
def readThread(sock):
|
||||
|
||||
while True:
|
||||
data = sock.recv(1024)
|
||||
print(data.decode('utf-8'));
|
||||
|
||||
def writeThread(sock):
|
||||
i = 0
|
||||
sock.sendall('0::\n'.encode('utf-8'))
|
||||
sock.sendall('2:myroom\n'.encode('utf-8'))
|
||||
|
||||
|
||||
while True:
|
||||
sock.sendall(f'3:0:{"thasdl;fjasd;lfjasl;dfjal;skdjlask;dflasd;jkjfjkjfsfjfjakfjafjdfjakjflfjadjf;jfakdjfdjfakdjfsdj;ldjf;laskdflsdjfasdkjfkdjflskdjfskdjflkfjlkdjfskdjfkjfskdjf;kfjs;kfjadkfjas;ldfalsdkfsdkfjasdkjfasdkfjlkdjfkdjflkdjf;djfadkfjaldkfjalkfja;kfja;kfjadkfjadkfja;sdkfa;dkfj;dfkjaslkfjas;dkfs;dkfjsldfjasdfjaldfjaldkfj;lkj"}\n'.encode('utf-8'))
|
||||
i = i+1
|
||||
time.sleep(0.01)
|
||||
|
||||
|
||||
|
||||
start_new_thread(readThread,(sock,))
|
||||
start_new_thread(writeThread,(sock,))
|
||||
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
sock.close()
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
import socket
|
||||
import time
|
||||
from _thread import *
|
||||
import threading
|
||||
|
||||
server_addr = ('127.0.0.1', 80)
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect_ex(server_addr)
|
||||
|
||||
|
||||
|
||||
|
||||
def readThread(sock):
|
||||
|
||||
while True:
|
||||
data = sock.recv(1024)
|
||||
print(data.decode('utf-8'));
|
||||
|
||||
def writeThread(sock):
|
||||
i = 0
|
||||
sock.sendall('0::\n'.encode('utf-8'))
|
||||
sock.sendall('2:myroom\n'.encode('utf-8'))
|
||||
|
||||
|
||||
while True:
|
||||
sock.sendall(f'3:0:{"thasdl;fjasd;lfjasl;dfjal;skdjlask;dflasd;jkjfjkjfsfjfjakfjafjdfjakjflfjadjf;jfakdjfdjfakdjfsdj;ldjf;laskdflsdjfasdkjfkdjflskdjfskdjflkfjlkdjfskdjfkjfskdjf;kfjs;kfjadkfjas;ldfalsdkfsdkfjasdkjfasdkfjlkdjfkdjflkdjf;djfadkfjaldkfjalkfja;kfja;kfjadkfjadkfja;sdkfa;dkfj;dfkjaslkfjas;dkfs;dkfjsldfjasdfjaldfjaldkfj;lkj"}\n'.encode('utf-8'))
|
||||
i = i+1
|
||||
time.sleep(0.1)
|
||||
|
||||
|
||||
|
||||
start_new_thread(readThread,(sock,))
|
||||
start_new_thread(writeThread,(sock,))
|
||||
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
sock.close()
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue