Close socket when game ends - python

I wrote a script in blender game and I use sockets, I have a Server.blend and a client.blend.
this is my Server's Constructor:
class Server:
def __init__(self, host="127.0.0.1", port= 9238):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket.setblocking(False)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind((host, port))
and this is my client's:
class Client:
def __init__(self, server_ip="127.0.0.1", server_port= 9238):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket.setblocking(False)
self.serv_addr = (server_ip, server_port)
The problem is that I don't know when the client is going to exit the game, so I can't close his socket- what keeps the used port open so i can't use the current port again.
I have a dictionary which contains all the addresses of all the clients, so I tried to send a message to all the addresses and in case that the client disconnected, i won't be able to send the message and use and exception to remove the address from the list (and it's avatar etc..):
def Check_For_Disconnect(self):
for addr in self.addr_user:
try:
self.socket.sendto(b"You are connected!" , addr)
except socket.error:
scene = logic.getCurrentScene()
for obj in scene.objects:
if str(obj) == "Text" and obj == self.addr_user[addr].name:
obj.delete()
del self.addr_user[addr]
I suppose that I don't reach the exception because the client's socket is still open so the message arrives properly.
Does anyone have any idea how I can around this problem?

The client should send some info about exiting the game, thus the server knows exactly when to close the socket. So the process is triggered by the client side.

I found a solution: I don't know when the client is going to exit the game, so I can't close his socket, what I do know is that just when the client runs his game- he can send messages to the server. so as long as the server on air he asks from the client for "connected" message. Every time that the server doesn't get a message from the client, he counts it. Now it's up to you how many counts to do until you sure that the client disconnected.
This is my receive method:
def receive(self):
while True:
for k in self.addr_connected:
if self.addr_connected[k] > 50:
self.Remove_Client(k)
break
try:
data, addr = self.socket.recvfrom(1024)
if not addr in self.addr_user:
user= User(data.decode())
scene = logic.getCurrentScene()
spawner = scene.objects['Spawner']
avatar = scene.addObject("Avatar", spawner)
avatar.children[0]['Text'] = user.name
avatar['user']= user
self.addr_user[addr] = user
self.addr_connected[addr] = 0
else:
user= self.addr_user[addr]
try:
user.keyboard.updateState(pickle.loads(data))
except:
data = data.decode()
if data == "I am connected":
self.addr_connected[addr] = 0
for k in self.addr_connected:
if k != addr:
self.addr_connected[k] += 1
except socket.error:
for k in self.addr_connected:
self.addr_connected[k] += 1
break

Related

Python socket accept() does not accept a connection

A quite basic problem, but I can't find my mistake. I bascially used this tutorial code to implement my own client-server program: Client sends some data, the server displays it (instead of echoing data like in the tutorial)
The echoing tutorial code works, but my adjusted code to print data on the server doesn't. I added some delimiter mechanism to detect the entire message. My code:
Server.py:
class ThreadedServer:
def __init__(self, host, port):
self.host = host
self.port = port
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind((self.host, self.port))
print("Server bound on Port "+str(port))
def listen(self):
self.sock.listen(5)
while True:
print("Waiting for incoming connections...")
client, address = self.sock.accept()
client.settimeout(60)
print("Starting Working thread.")
threading.Thread(target=self.listenToClientDelimiter, args=(client, address)).start()
def listenToClientDelimiter(self, client, address):
print("Connect to client "+address)
length = None
buffer = ""
while True:
data = client.recv(1024)
print("Received raw data: "+str(data))
if not data:
break # connection closed
buffer += data
while True:
if length is None:
if LENGTH_DELIMITER not in buffer:
break # delimiter not found, wait for next data package
length_str, _, buffer = buffer.partition(LENGTH_DELIMITER)
length = int(length_str)
if len(buffer) < length:
break # wait until full length got received before we proceed
message = buffer[:length]
buffer = buffer[length:]
length = None
# PROCESS MESSAGE HERE
print(message)
client.close()
if __name__ == "__main__":
ThreadedServer('', 65432).listen()
Client.py:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(("127.0.0.1", 65432))
connect_user_cmd = "18:CONNECT_USER;Peter"
print("Trying to send data: "+connect_user_cmd.decode())
print(s.send(connect_user_cmd))
I run the server, then the client. The output from the server:
Server bound on Port 65432
Waiting for incoming connections...
As you see, I expect some log messages and of course my sent message. The client outputs this:
Trying to send data: 18:CONNECT_USER;Peter
21
Process finished with exit code 0
This gets outputted from the client even when I don't start the server. The weird thing is that the echoing part did indeed work. Could someone hint me into the right direction? Thank you!

Socket Messaging Client/Server: Client not sending message

I am trying to create a simple chat program using Python and Socket. The server handles the connection between two clients. Whenever I run the program, for one of the clients, every message after the first one does not send. The message does not even reach the server.
This is on Windows, Python3. I've integrated threading so messages can be sent and received at the same time; however, the problem persists.
Server.py
conns = [] # list that stores both connections
def accept(sk):
while True:
conn, addr = sk.accept()
conns.append(conn)
print(addr, "has connected")
t = threading.Thread(target=accept, args=(sk,))
t.start() #start accept thread
def send(conn):
while True:
message = conn.recv(2048)
print(message)
for conn in conns:
conn.send(message) #sends message to every connection
print("Sent message")
t = threading.Thread(target=send, args=(conn,))
t.start() #start threading for send
Client.py
def recvMessages(s):
while True:
message = s.recv(2048)
print(message)
message = message.decode()
messages.append(message)
os.system("cls")
for message in messages:
print(message)
def sendMessage(s):
while True:
message = input()
message = message.encode()
s.send(message)
s = socket.socket()
host = socket.gethostname()
port = 8080
s.connect((host, port))
messages = []
print("Connected")
connected = True
threading.Thread(target=sendMessage, args=(s,)).start()
threading.Thread(target=recvMessages, args=(s,)).start()
All the messages should be sent from both clients, but one client can never send multiple messages, the other works fine.
Your server code is missing it's socket, and accept is not being run in your example, you also have invalid indentation as James pointed out, next time provide a minimal reproducible example.
I also cleaned up your files a bit, as you followed some bad practice, specifically with broadcasting the messages to all clients "def send" which actually receives, avoid confusing naming :)
in your server code you also sent only to one connection (which in your example doesn't exist) which it should be running the receive and send each time a new message is received
server.py
import socket
import threading
conns = [] # list that stores both connections
def accept(sk):
while True:
conn, addr = sk.accept()
conns.append(conn)
print(addr, "has connected")
# receive from new client
receive_t = threading.Thread(target=receive, args=(conn,))
receive_t.start() # start threading for send
def send(s, msg):
# change message ..
s.send(msg)
def broadcast(msg):
for conn in conns:
send(conn, msg)
def receive(conn):
try:
while True:
message = conn.recv(2048)
if not message: # detects if socket is dead, by testing message
print("client sent Nothing, removing")
conns.remove(conn)
break
broadcast(message) # send to all clients
except ConnectionResetError as e:
print('Could not send must be disconnected ')
conns.remove(conn) # remove dead socket
# added missing socket
sock = socket.socket()
sock.bind(('127.0.0.1', 8080))
sock.listen(1) # needs to bind and listen
t = threading.Thread(target=accept, args=(sock,))
t.start() # start accept thread
client.py
import os
import socket
import threading
messages = []
def recvMessages(s):
while True:
message = s.recv(2048)
message = message.decode()
print('new message= ', message)
messages.append(message)
os.system("cls")
for message in messages:
print(message)
def send_message(s):
while True:
message = input()
message = message.encode()
s.send(message)
s = socket.socket()
host = '127.0.0.1'
port = 8080
s.connect((host, port))
print("Connected")
connected = True
# added missing receive
receive = threading.Thread(target=recvMessages, args=(s,)) # missed receive thread in example
receive.start()
send_message(s)

Multithreading sockets with a central relay-like server

I have previously managed to implement a client-server socket script which relays messages between a single client and the server and I'm now trying to implement a multiple-client system.
More specifically, I would like to use the server as some sort of medium between two clients which retrieves information from one client and relays it to the other. I had tried to attach and send the port number of the receiving client and then extract it from the message on the server side. After that, I would try and send it to whatever socket with that port number but I ran into some trouble (as port numbers are determined at the point of sending I believe?) so now I am simply just trying to relay the sent message back to all clients. However, the problem is that the message is only being sent to the server and not being relayed to the desired client.
I had previously tried to implement a peer-to-peer system but I ran into trouble so I decided to take a step back and do this instead.
Server.py:
import socket, _thread, threading
import tkinter as tk
SERVERPORT = 8600
HOST = 'localhost'
class Server():
def __init__(self):
self.Connected = True
self.ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.ServerSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
self.ServerSocket.bind((HOST, SERVERPORT))
self.ServerSocket.listen(2)
self.Clients = []
def Listen(self):
print('Server is now running')
while self.Connected:
ClientSocket, Address = self.ServerSocket.accept()
self.Clients.append(Address)
print('\nNew user connected', Address)
t = threading.Thread(target=self.NewClient, args=(ClientSocket,
Address))
t.daemon = True
t.start()
self.Socket.close()
def NewClient(self, ClientSocket, Address):
while self.Connected:
if ClientSocket:
try:
ReceivedMsg = ClientSocket.recv(4096)
print('Message received from', Address, ':', ReceivedMsg)
self.Acknowledge(ClientSocket, Address)
if ReceivedMsg.decode('utf8').split()[-1] != 'message':
ReceiverPort = self.GetSendPort(ReceivedMsg)
self.SendToClient(ClientSocket,ReceivedMsg,ReceiverPort)
except:
print('Connection closed')
raise Exception
ClientSocket.close()
def Acknowledge(self, Socket, Address):
Socket.sendto(b'The server received your message', Address)
def GetSendPort(self, Msg):
MsgDigest = Msg.decode('utf8').split()
return int(MsgDigest[-1])
def SendToClient(self, Socket, Msg, Port):
Addr = (HOST, Msg)
for Client in self.Clients:
Socket.sendto(Msg, Client)
def NewThread(Func, *args):
if len(args) == 1:
t = threading.Thread(target=Func, args=(args,))
elif len(args) > 1:
t = threading.Thread(target=Func, args=args)
else:
t = threading.Thread(target=Func)
t.daemon = True
t.start()
t.join()
Host = Server()
NewThread(Host.Listen)
And the Client(.py):
import socket, threading
import tkinter as tk
Username = 'Ernest'
PORT = 8601
OtherPORT = 8602
SERVERPORT = 8600
HOST = '127.0.0.1'
class Client():
def __init__(self, Username):
self.Connected, self.Username = False, Username
self.Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
def Connect(self):
print('Trying to connect')
try:
self.Socket.connect((HOST, SERVERPORT))
self.Connected = True
print(self.Username, 'connected to server')
Msg = MsgUI(self.Username)
Msg.Display()
except Exception:
print('Could not connect to server')
raise Exception
def SendMsg(self):
if self.Connected:
Msg = '{} sent you a message {}'.format(self.Username, OtherPORT)
self.Socket.sendall(bytes(Msg, encoding='utf8'))
self.GetResponse()
def GetResponse(self, *args):
AckMsg = '\n{} received the message'.format(self.Username)
NMsg = '\n{} did not receive the message'.format(self.Username)
if self.Connected:
Msg = self.Socket.recv(4096)
print(Msg)
if Msg:
self.Socket.sendall(bytes(AckMsg, encoding='utf8'))
else:
self.Socket.sendall(bytes(NMsg, encoding='utf8'))
class MsgUI():
def __init__(self, Username):
self.Username = Username
self.entry = tk.Entry(win)
self.sendbtn = tk.Button(win, text='send', command=Peer.SendMsg)
def Display(self):
self.entry.grid()
self.sendbtn.grid()
win.mainloop()
win = tk.Tk()
Peer = Client(Username)
Peer.Connect()
I want a message to be sent whenever the user presses the send button in the tkinter window, but at the same time, it is continually 'listening' to see if it received any messages.
I also previously tried to run the GetResponse method in the Client in another thread and instead of if self.Connected I used while self.Connected and it still didn't work.
UPDATE
After some helpful comments, I have edited the two files as such:
The server now holds the two sockets for each client which is run first. The server file is imported into the client file as a module. Each client file is then run and each client runs a function in the server file, requesting to use the socket. If the request is allowed (i.e. no error was thrown), the socket is connected, added to a set of clients stored in the server file and then returned to the client file. The client then uses this socket to send and receive messages.
Server.py
import socket, _thread, threading
import tkinter as tk
SERVERPORT = 8600
HOST = 'localhost'
class Server():
def __init__(self):
self.Connected = True
self.ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.ServerSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
self.ServerSocket.bind((HOST, SERVERPORT))
self.ServerSocket.listen(2)
self.Clients = {}
def ConnectClient(self, Username, Port):
Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.Clients[Username] = [Socket, Port, False]
try:
self.Clients[Username][0].connect((HOST, SERVERPORT))
self.Clients[Username][2] = True
print('Opened port for user', Username)
return Socket
except Exception:
print('Could not open port for user', Username)
raise Exception
def Listen(self):
print('Server is now running')
while self.Connected:
ClientSocket, Address = self.ServerSocket.accept()
print('\nNew user connected', Address)
t = threading.Thread(target=self.NewClient, args=(ClientSocket,
Address))
t.daemon = True
t.start()
self.Socket.close()
def NewClient(self, ClientSocket, Address):
while self.Connected:
if ClientSocket:
try:
ReceivedMsg = ClientSocket.recv(4096)
if b'attempting to connect to the server' in ReceivedMsg:
ClientSocket.send(b'You are now connected to the server')
else:
print('Message received from', Address, ':',ReceivedMsg)
#self.Acknowledge(ClientSocket, Address)
ReceiverPort = self.GetSendPort(ReceivedMsg)
if ReceiverPort != None:
self.SendToClient(ClientSocket,ReceivedMsg,
ReceiverPort)
except:
print('Connection closed')
raise Exception
ClientSocket.close()
def Acknowledge(self, Socket, Address):
Socket.sendto(b'The server received your message', Address)
def GetSendPort(self, Msg):
MsgDigest = Msg.decode('utf8').split()
try:
Port = int(MsgDigest[-1])
except ValueError:
Port = None
return Port
def SendToClient(self, Socket, Msg, Port):
Addr = (HOST, Port)
Receiver = None
for Client, Vars in self.Clients.items():
if Vars[1] == Port:
Receiver = Client
self.Clients[Receiver][0].sendto(Msg, Addr)
def NewThread(Func, *args):
if len(args) == 1:
t = threading.Thread(target=Func, args=(args,))
elif len(args) > 1:
t = threading.Thread(target=Func, args=args)
else:
t = threading.Thread(target=Func)
t.daemon = True
t.start()
t.join()
Host = Server()
if __name__ == '__main__':
NewThread(Host.Listen)
And Client.py
import socket, threading, Server
import tkinter as tk
Username = 'Ernest'
PORT = 8601
OtherPORT = 8602
SERVERPORT = 8600
HOST = '127.0.0.1'
class Client():
def __init__(self, Username):
self.Connected, self.Username = False, Username
def Connect(self):
print('Requesting to connect to server')
try:
self.Socket = Server.Host.ConnectClient(self.Username, PORT)
self.Connected = Server.Host.Clients[self.Username][2]
Msg = '{} is attempting to connect to the server'.format(self.Username)
self.Socket.sendall(bytes(Msg, encoding='utf8'))
ReceivedMsg = self.Socket.recv(4096)
print(ReceivedMsg)
Msg = MsgUI(self.Username)
Msg.Display()
except Exception:
print('Could not connect to server')
raise Exception
def SendMsg(self):
try:
if self.Connected:
Msg = '{} sent you a message {}'.format(self.Username,OtherPORT)
self.Socket.sendall(bytes(Msg, encoding='utf8'))
self.GetResponse()
except Exception:
print('Connection closed')
raise Exception
def GetResponse(self, *args):
AckMsg = '\n{} received the message'.format(self.Username)
NMsg = '\n{} did not receive the message'.format(self.Username)
if self.Connected:
Msg = self.Socket.recv(4096)
print(Msg)
if Msg:
self.Socket.sendall(bytes(AckMsg, encoding='utf8'))
else:
self.Socket.sendall(bytes(NMsg, encoding='utf8'))
class MsgUI():
def __init__(self, Username):
self.Username = Username
self.entry = tk.Entry(win)
self.sendbtn = tk.Button(win, text='send', command=Peer.SendMsg)
def Display(self):
self.entry.grid()
self.sendbtn.grid()
win.mainloop()
win = tk.Tk()
Peer = Client(Username)
Peer.Connect()
Now the problem is more of a python and scope problem. When trying to relay the message back to the client, I was getting a KeyError as the Clients dictionary was still empty. When making the function call to the server in the client file, it's clear that the update to the dictionary happens in the client file rather than the server file - which is in a different instance. I need a method of changing the contents of the Clients dictionary that is called to action by the client file but takes effect in the server file.
Are you committed to multithreading? Threads don't run concurrently in python ( due to the GIL), and while they are one way to handle concurrent operations, they aren't the only way and usually they're not the best way, unless they're the only way. Consider this code, which doesn't handle failure cases well, but seems to work as a starting point.
import socket, select, Queue
svrsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
svrsock.setblocking(0)
svrsock.bind(('', 17654))
svrsock.listen(16)
client_queues = {}
write_ready=[] # we'll update this for clients only that have things in the queue
while client_queues.keys() + [svrsock] :
readable, writable, exceptional = select.select(client_queues.keys() + [svrsock] , write_ready, [])
for rd in readable:
if rd is svrsock: # reading listening socket == accepting connection
conn, addr = svrsock.accept()
print("Connection from {}".format(addr))
conn.setblocking(0)
client_queues[conn] = Queue.Queue()
else:
data = rd.recv(1024)
if data:
# TODO: send to all queues
print("Message from {}".format(rd.getpeername()))
for sock, q in client_queues.iteritems():
q.put("From {}: {}".format( rd.getpeername(), data))
if sock not in write_ready:
write_ready.append(sock)
for rw in writable:
try:
data = client_queues[rw].get_nowait()
rw.send(data)
except Queue.Empty:
write_ready.remove(rw)
continue
The concept is pretty simple. The server accepts connections; each connection (socket) is associated with a queue of pending messages. Each socket that's ready for reading is read from, and its message is added to each client's queue. The recipient client is added into the write_ready list of clients with data pending, if it's not already in there. Then each socket that's ready for writing has its next queued message written to it. If there are no more messages, the recipient is removed from the write_ready list.
This is very easy to orchestrate if you don't use multithreading because all coordination is inherent in the order of the application. With threads it would be more difficult and a lot more code, but probably not more performance due to the gil.
The secret to handling multiple I/O streams concurrently without multithreading is select. In principle it's pretty easy; we pass select() a list of possible sockets for reading, another list of possible sockets for writing, and a final list that for this simplified demo I completely ignore . The results of the select call will include one or more sockets that are actually ready for reading or writing, which allows me to block until one or more sockets are ready for activity. I then process all the sockets ready for activity every pass ( but they've already been filtered down to just those which wouldn't block).
There's a ton still to be done here. I don't cleanup after myself, don't track closed connections, don't handle any exceptions, and so on. but without having to worry about threading and concurrency guarantees, it's pretty easy to start addressing these deficiencies.
Here it is "in action". Here for the client side I use netcat, which is perfect for layer 3 testing without layer 4+ protocols ( in other words, raw tcp so to speak). It simply opens a socket to the given destination and port and sends its stdin through the socket and sends its socket data to stdout, which makes it perfect for demoing this server app!
I also wanted to point out, coupling code between server and client is inadvisable because you won't be able to roll out changes to either without breaking the other. It's ideal to have a "contract" so to speak between server and client and maintain it. Even if you implement the behavior of server and client in the same code base, you should use the tcp communications contract to drive your implementation, not code sharing. Just my 2 cents, but once you start sharing code you often start coupling server/client versions in ways you didn't anticipate.
the server:
$ python ./svr.py
Connection from ('127.0.0.1', 52059)
Connection from ('127.0.0.1', 52061)
Message from ('127.0.0.1', 52061)
Message from ('127.0.0.1', 52059)
Message from ('127.0.0.1', 52059)
First client ( 52059):
$ nc localhost 17654
hello
From ('127.0.0.1', 52061): hello
From ('127.0.0.1', 52059): hello
From ('127.0.0.1', 52059): hello
Second client:
$ nc localhost 17654
From ('127.0.0.1', 52061): hello
hello
From ('127.0.0.1', 52059): hello
hello
From ('127.0.0.1', 52059): hello
If you need more convincing on why select is way more compelling than concurrent execution, consider this: Apache is based on a threading model, in other words, the connections each get a worker thread . nginx is based on a select model, so you can see how much faster that can potentially be. Not to say that nginx is inherently better, as Apache benefits from the threading model because of its heavy use of modules to extend capabilities ( mod_php for example), whereas nginx doesn't have this limitation and can handle all requests from any thread. But the raw performance of nginx is typically considered far higher and far more efficient, and a big reason for this is that it avoids almost all the cpu context switches inherent in apache. It's a valid approach!
A word on scaling. Obviously, this wouldn't scale forever. Neither would a threading model; eventually you run out of threads. A more distributed and high throughput system would likely use a Pub/Sub mechanism of some kind, offloading the client connection tracking and message queueing from the server to a pub/sub data tier and allowing connections to be restored and queued data to be sent, as well as adding multiple servers behind a load balancer. Just throwing it out there. You might be pleasantly surprised how well select can scale ( cpu is so much faster than network anyway that it's likely not the bottleneck).

Best way to associate a player character with a server connection?

I'm attempting to write a simple multiplayer type text game. I currently have a simple chat server right now. It's working just like it should, echoing the message to all the people connected.
I can't figure out how to take my next step. I want to associate the client connected with a player object. The player objects house the available commands someone can type in, so in order to move forward with parsing input I have to be able to do this - I just don't know how.
Currently the player object is just a standard object class with a few properties like 'name', 'id', 'location', that has a list of commands available to them. If you need a code example of that I can provide one.
Any ideas?
import socket, select
from helpers.colorize import colorize
class Server(object):
def __init__(self, host, port):
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server.bind((host, port))
self.server.listen(1)
self.clients = [self.server]
def start(self):
while True:
read_sockets,write_sockets,error_sockets = select.select(self.clients, [], [])
for sock in read_sockets:
#new conn
if sock == self.server:
sockfd, addr = self.server.accept()
self.clients.append(sockfd)
print "Connection from %s %s" % addr
#looks like a message
else:
#data recieved, lets try and do something
try:
data = sock.recv(4096)
if data: #we have something, parse it
self.sendall(sock, data) #give it to everyone for now
except: #disconnection, remove from our lists
self.sendall(sock, ("%s %s disconnected" % addr))
print "%s %s disconnected." % addr
socket.close()
self.clients.remove(sock)
continue
self.server.close()
def send(self, sock, message):
sock.send(colorize(message))
def sendall(self, sock, message):
#Do not send the message to master socket and the client who has send us the message
for socket in self.clients:
if socket != self.server:
try :
socket.send(message)
except :
# broken socket connection may be, chat client pressed ctrl+c for example
socket.close()
CONNECTION_LIST.remove(socket)
if __name__ == "__main__":
s = Server('localhost', 2222)
s.start()
You can use a dict to map fds of client sockets to client objects.
When you receive some new messages, you can fetch the corresponding object from the dict and invoke methods on it.

Python Sockets, Advanced Chat Box

I want to create a server that handles a lot of clients at the same time (handles: receiving data from clients and sending data to all clients at the same time!!!)
Actually i'm trying to create a chat box. The program will work like this:
1) There's going to be a server which handles the clients.
2) More than one clients can join the server.
3) Clients send messages (Strings) to the server.
4) The server receive's the message from a client and then it send's it to all
the clients except the one he got it from.
And this is how the clients will communicate each other. No private messages available. When someone hits the enter all the clients will see the message on their screen.
The client module is easy to make, because the client communicates only with one socket (The Server).
The server module from the other hand is really complicated, i don't know how to do it (I also know about threads).
This is my atempt:
import socket, threading
class Server:
def __init__(self, ip = "", port = 5050):
'''Server Constructor. If __init__ return None, then you can use
self.error to print the specified error message.'''
#Error message.
self.error = ""
#Creating a socket object.
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#Trying to bind it.
try:
self.server.bind( (ip, port) )
pass
#Failed, because socket has been shuted down.
except OSError :
self.error = "The server socket has been shuted down."
return None
#Failed, because socket has been forcibly reseted.
except ConnectionResetError:
self.error = "The server socket has been forcibly reseted."
return None
#Start Listening.
self.server.listen()
#_____Other Variables_____#
#A flag to know when to shut down thread loops.
self.running = True
#Store clients here.
self.clients = []
#_____Other Variables_____#
#Start accepting clients.
thread = threading.thread(target = self.acceptClients)
thread.start()
#Start handling the client.
self.clientHandler()
#Accept Clients.
def acceptClients(self):
while self.running:
self.clients.append( self.server.accept() )
#Close the server.
self.server.close()
#Handle clients.
def clientHandler(self):
while self.running:
for client in self.clients:
sock = client[0]
addr = client[1]
#Receive at most 1 mb of data.
#The problem is that recv will block the loop!!!
data = sock.recv(1024 ** 2)
As you can see, i accept clients using a thread so the server.accept() won't block the program. And then i store the clients into a list.
But the problem is on the clientHandler. How am i going to recv from all
clients at the same time? The first recv will block the loop!!!
I also tried to start new threads (clientHandlers) for every new client
but the problem was the synchronization.
And what about the send? The server must send data to all the clients, so the clientHandler is not yet finished. But if i mix the methods recv and send then the problem become's more complicated.
So what is the proper and best way to do this?
I'd like to give me an example too.
Multithreading is great when the different clients are independant of each other: you write your code as if only one client existed and you start a thread for each client.
But here, what comes from one client must be send to the others. One thread per client will certainly lead to a synchronization nightmare. So let's call select to the rescue! select.select allows to poll a list of sockets and returns as soon as one is ready. Here you can just build a list containing the listening socket and all the accepted ones (that part is initially empty...):
when the listening socket is ready for read, accept a new socket and add it to the list
when another socket is ready for read, read some data from it. If you read 0 bytes, its peer has been shut down or closed: close it and remove it from the list
if you have read something from one accepted socket, loop on the list, skipping the listening socket and the one from which you have read and send data to any other one
Code could be (more or less):
main = socket.socket() # create the listening socket
main.bind((addr, port))
main.listen(5)
socks = [main] # initialize the list and optionaly count the accepted sockets
count = 0
while True:
r, w, x = select.select(socks, [], socks)
if main in r: # a new client
s, addr = main.accept()
if count == mx: # reject (optionaly) if max number of clients reached
s.close()
else:
socks.append(s) # appends the new socket to the list
elif len(r) > 0:
data = r[0].recv(1024) # an accepted socket is ready: read
if len(data) == 0: # nothing to read: close it
r[0].close()
socks.remove(r[0])
else:
for s in socks[1:]: # send the data to any other socket
if s != r[0]:
s.send(data)
elif main in x: # close if exceptional condition met (optional)
break
elif len(x) > 0:
x[0].close()
socks.remove(x[0])
# if the loop ends, close everything
for s in socks[1:]:
s.close()
main.close()
You will certainly need to implement a mechanism to ask the server to stop, and to test all that but it should be a starting place
This my final program and works like a charm.
Server.py
import socket, select
class Server:
def __init__(self, ip = "", port = 5050):
'''Server Constructor. If __init__ return None, then you can use
self.error to print the specified error message.'''
#Error message.
self.error = ""
#Creating a socket object.
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#Trying to bind it.
try:
self.server.bind( (ip, port) )
pass
#Failed, because socket has been shuted down.
except OSError :
self.error = "The server socket has been shuted down."
#Failed, because socket has been forcibly reseted.
except ConnectionResetError:
self.error = "The server socket has been forcibly reseted."
#Start Listening.
self.server.listen()
#_____Other Variables_____#
#A flag to know when to shut down thread loops.
self.running = True
#Store clients here.
self.sockets = [self.server]
#_____Other Variables_____#
#Start Handling the sockets.
self.handleSockets()
#Handle Sockets.
def handleSockets(self):
while True:
r, w, x = select.select(self.sockets, [], self.sockets)
#If server is ready to accept.
if self.server in r:
client, address = self.server.accept()
self.sockets.append(client)
#Elif a client send data.
elif len(r) > 0:
#Receive data.
try:
data = r[0].recv( 1024 )
#If the client disconnects suddenly.
except ConnectionResetError:
r[0].close()
self.sockets.remove( r[0] )
print("A user has been disconnected forcible.")
continue
#Connection has been closed or lost.
if len(data) == 0:
r[0].close()
self.sockets.remove( r[0] )
print("A user has been disconnected.")
#Else send the data to all users.
else:
#For all sockets except server.
for client in self.sockets[1:]:
#Do not send to yourself.
if client != r[0]:
client.send(data)
server = Server()
print("Errors:",server.error)
Client.py
import socket, threading
from tkinter import *
class Client:
def __init__(self, ip = "192.168.1.3", port = 5050):
'''Client Constructor. If __init__ return None, then you can use
self.error to print the specified error message.'''
#Error message.
self.error = ""
#Creating a socket object.
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#Trying to bind it.
try:
self.server.connect( (ip, port) )
pass
#Failed, because socket has been shuted down.
except OSError :
self.error = "The client socket has been shuted down."
return
#Failed, because socket has been forcibly reseted.
except ConnectionResetError:
self.error = "The client socket has been forcibly reseted."
return
#Failed, because socket has been forcibly reseted.
except ConnectionRefusedError:
self.error = "The server socket refuses the connection."
return
#_____Other Variables_____#
#A flag to know when to shut down thread loops.
self.running = True
#_____Other Variables_____#
#Start the GUI Interface.
def startGUI(self):
#Initialiazing tk.
screen = Tk()
screen.geometry("200x100")
#Tk variable.
self.msg = StringVar()
#Creating widgets.
entry = Entry( textvariable = self.msg )
button = Button( text = "Send", command = self.sendMSG )
#Packing widgets.
entry.pack()
button.pack()
screen.mainloop()
#Send the message.
def sendMSG(self):
self.server.send( str.encode( self.msg.get() ) )
#Receive message.
def recvMSG(self):
while self.running:
data = self.server.recv(1024)
print( bytes.decode(data) )
#New client.
main = Client()
print("Errors:", main.error)
#Start a thread with the recvMSG method.
thread = threading.Thread(target = main.recvMSG)
thread.start()
#Start the gui.
main.startGUI()
#Close the connection when the program terminates and stop threads.
main.running = False
main.server.close()
The program works fine exactly as i wanted.
But i have some more questions.
r, w, x = select.select(self.sockets, [], self.sockets)
r is a list which contains all the ready sockets.
But i did not undesrand what w and x are.
The first parameter is the sockets list, the second the accepted clients
and the third parameter what is it? Why am i giving again the sockets list?

Categories

Resources