Για να σου κάνω και πολύ ποιο εύκολα τα πράγματα, υπάρχει ένα τρομερό module το οποίο ονομάζεται select, και μπορεί να σου διαχειρηστή αυτόματα τα recv() και accept() χωρίς να κολλάει η ροή του προγραμματός σου και χωρίς να χρειαστεί να κάνεις ξεχωριστά proccesses.
Λοιπόν δες πως ακριβώς είναι ο κώδικας ενός server για chat χρησιμοιποιόντας την select.
import socket, select
#Creating the server socket.
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#Bind the server to the local machine with the port 1996.
server.bind( ("", 1996) )
#Start listening.
server.listen()
running = True
sockets = [server]
while running:
#Get next network activity.
readable, writeable, exceptional = select(sockets, [], sockets)
#Server is ready to accept.
if server in readable:
#Accept the cilent.
client, addr = server.accept()
#Append the client into the sockets list.
sockets.append(client)
#Elif there are other readable sockets except the server.
elif len(readable) > 0:
#Read the data.
data = readable[0].recv(1024)
#The connection with this client has closed.
#So remove this socket from the list.
if len(data) == 0:
readable[0].close()
sockets.remove(readable[0])
#If a client send the commanc /close
#then shut down the server.
elif bytes.decode( data ) == "/close":
running = False
#Send the data to all the clients.
else:
#Go though all clients.
for s in sockets:
#Send the message to all other clients
#except the one who send it and the server ofcourse.
if s is not readable[0] and s is not server:
s.send(data)
#Close the server socket.
server.close()
Ας καταλάβουμε τη κάνει η select:
Πρώτα παίρνει 3 ορίσματα (input, output, input). Στο input πρέπει να δώσεις μία λίστα η οποία περιέχει όλα τα sockets που είναι ανοιχτά κατά την διάρκεια τις σύνδεσης. Η λίστα πρέπει να περιέχει τουλάχιστον ένα socket αρχικά για αυτό την αρχικοποιώ με τον server ο οποίος είναι το πρώτο socket που δημιουργήτε. Το output είναι απλός μια άδεια λίστα.
Όταν ένα από τα inputs είναι έτοιμο να λάβει μήνυμα, τότε η selecτ επιστρέφει 3 λίστες.
Η πρώτη readable είναι μια λίστα που περιέχει όλα τα sockets από τα οποία είναι έτοιμα να τρέξουν την recv() ή εάν είναι ο server τότε είναι έτοιμος να κάνει accept() έναν νέο client.
Αυτό σημαίνει ότι η select μου εξασφαλίζει ότι οι μεθοδοι recv() και accept() δεν θα κολλήσουν την ροή του προγραμματός μου αφού τα readbable sockets είναι ήδη έτοιμα να το κάνουν αυτό.
Τo writeable είναι μια λίστα με όλα τα sockets στα οποία μπορώ να στείλω κάποιο μήνυμα.
Το exceptional είναι μία λίστα με όλα τα sockets τα οποία ανύψωσαν (όπως λέμε) μία εξέρεση (exception.)
Τώρα όπως βλέπεις και στον κώδικα κάνω τα εξής:
Πρώτα βλέπω εάν σην readable υπάρχει ο server. Eαν υπάρχει τότε αυτο συμένει ότι ένας client προσπαθεί να συνδεθεί στον server, άρα μπορώ να καλέσω την accept() χωρίς να κολλήσει το πρόγραμμα μου αφού ξέρω ότι ήδη ένας client προσπαθεί να συνδεθεί. Στην συνέχεια προσθέτο τον client στην λίστα sockets έτσι ώστε να μην τον χάσω και βέβαια η select να τον λάβει υπόψην της όταν την ξανά καλέσω στο επόμενο loop.
Εάν τώρα δεν υπάρχει ο server στην readable αλλά η readable έχει μέσα στοιχεία (αυτό το ελέγχο με το len(readable)>0 δηλαδή εάν το μέγεθος της είναι μεγαλύτερο του 0) τότε το πρώτο στοιχειο της θα είναι σίγουρα ο client ο οποίος έστειλε πρώτος μήνυμα. Άρα μπορώ να λάβω το μήνυμα με την recv() χωρίς καμία τύψη. Μόλις λάβω το μήνυμα τσεκάρω len(data) == 0. Όταν ένα socket λαμβάνει
0 bytes αυτό συμαίνει ότι έχει κλείση. Όταν συμβαίνει αυτό πρέπει να αφαιρέσω από την λίστα sockets αυτό το κλειστώ socket.
Εάν δεν είναι len(data) == 0 τότε μπορώ να κάνω ότι θέλω με την πληροφορία που έλαβα από τον
client. Εδώ πρώτα ελέγχω να δω εάν με έστειλε κάποια εντολή (command). Εάν η εντολή είναι /close τότε τερματίζω τον server. Διαφορετικά θεωρώ ότι η πληροφορία είναι απλός ένα μήνυμα και άρα την στέλνω σε όλλα τα sockets εκτός του server και του socket από τον οποίο πήρα το μήνυμα.
Ελπίζω να σου έδωσα να καταλάβεις πως δουλεύει και πόσο χρήσιμη είναι η select.
Τώρα χρησιμοποιόντας πάλι την select μπορείς να γράψεις τον κώδικα για τον client. Δοκιμασέ το και πες μου εάν κολλήσεις κάπου.
Σημείωση:
Να ξέρεις ότι η send() και recv() στέλνουν bytes. Άρα εάν εσύ θέλεις να στήλεις κάποια συμβολοσειρά θα πρέπει να την μετατρέπεις σε bytes και αντίστροφα.
Η μέθοδος str.encode(message) παίρνει ως όρισμα ένα string και σου επιστρέφει τα bytes.
Ενώ η bytes.decode(data) παίρνει ως όρισμα bytes και τα μετατρέπει σε string.
Άρα για να στείλεις μήνυμα:
server.send( str.encode("Για σου φίλε τη κάνεις;") )
ενώ για να λάβεις
message = bytes.decode( client.recv(1024) )