Python Sockets, Advanced Chat Box
Solution 1:
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 = 0whileTrue:
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 listeliflen(r) > 0:
data = r[0].recv(1024) # an accepted socket is ready: readiflen(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 socketif s != r[0]:
s.send(data)
elif main in x: # close if exceptional condition met (optional)breakeliflen(x) > 0:
x[0].close()
socks.remove(x[0])
# if the loop ends, close everythingfor 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
Solution 2:
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.defhandleSockets(self):
whileTrue:
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.eliflen(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.iflen(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.defstartGUI(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.defsendMSG(self):
self.server.send( str.encode( self.msg.get() ) )
#Receive message.defrecvMSG(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?
Post a Comment for "Python Sockets, Advanced Chat Box"