Αυτός ο οδηγός επαιτεί την κατανόηση (σε μικρό επίπεδο) του module socket της python.
Ας αρχίσουμε με ένα μικρό παράδειγμα.
#Server Module
import socket
#Δημιουργία του αντικειμένου socket.
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind( ("192.168.1.10", 5555) ) #Δένω τον socket στον υπολογιστή μου.
sock.listen(1) #Λέω στον socket ότι περιμένω έναν client να συνδεθεί.
print("Παρακαλώ Περιμένετε. Αναμονή για την σύνδεση του client.") #Ένα μήνυμα προς τον χρήστη.
client, address = sock.accept() #Περιμένω να συνδεθεί ο client.
message_bytes = str.encode("Welcome to my Server!") #Μετατροπή μιας συμβολοσειράς σε bytes.
client.sendall( message_bytes ) #Αποστολή των bytes στον client.
#Client Module
import socket
#Δημιουργία του αντικειμένου socket.
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect( ("192.168.1.10", 5555) ) #Σύνδεση στον συγκεκριμένο υπολογιστή.
message_bytes = sock.recv(100) #Περιμένω να λάβω bytes από τον server.
message = bytes.decode( message_bytes ) #Μετατρέπω τα bytes σε συμβολοσειρά.
print(message) #Εμφανίζω το μήνυμα.
Αυτό που γίνετε στα προηγούμενα 2 modules είναι η δημιουργία ενός server στον οποίον συνδέετε
ένας client.Επιπλέον ο client δέχεται κάποια bytes από τον server τα οποία αποτελούν ένα μήνυμα.
Στο module του client όπως είδατε έχω πει να λάβει 100 bytes τα οποία είναι υπεραρκετά διότι
σίγουρα το μήνυμα "Welcome to my server!" δεν ξεπερνάει να 100 bytes.To πρόβλημα είναι τι γίνεται
αν θελήσω να στείλω ένα μεγαλύτερο μήνυμα; Πως θα γνωρίζει ο client πόσα ακριβώς bytes πρέπει να
"παραλάβει";
Προτού λύσουμε το παραπάνω πρόβλημα πρέπει πρώτα να μάθετε πως υπολογίζουμε τα bytes μιας
συμβολοσειράς.
Ο υπολογιστής χειρίζεται τις συμβολοσειρές ως λίστες χαρακτήρων, πχ η συμβολοσειρά "NIKOS"
αντιστοιχεί στην λίστα 'N','I','K','O','S'.Ο κάθε χαρακτήρας χρειάζεται 1 byte για την
αναπαράσταση του(αυτό ισχύει μόνο για τους λατινικούς χαρακτήρες, για το τι γίνεται σε άλλες γλώσσες θα το πούμε παρακάτω).Επομένως η παραπάνω συμβολοσειρά αποτελείτε από 5 bytes.
Άρα αν στέλναμε την παραπάνω συμβολοσειρά από τον server στον client θα γνωρίζαμε ότι στην
recv(5) "πρέπει να παραλάβουμε" 5 bytes.
Τώρα πίσω στο πρόβλημα μας.
Αν στέλναμε έναν έναν τους χαρακτήρες της συμβολοσειρά μας στον client θα είχαμε το εξής
πλεονέκτημα, θα γνωρίζαμε στα σίγουρα ότι ο client πρέπει να λαμβάνει 1 byte κάθε φορά.
Επομένως θα πρέπει με κάποιον τρόπο να στείλουμε όλους τους χαρακτήρες της συμβολοσειράς μας
έναν έναν κάθε φορά από τον server, ενώ στον client καθώς τους λαμβάνουμε θα πρέπει
να δημιουργούμε μια συμβολοσειρά που θα αποτελείτε από τους χαρακτήρες που λαμβάνουμε.
Είμαι σίγουρος ότι δεν θα καταλάβατε αρκετά καλά αυτά που είπα γιαυτό πρέπει να δείτε και
ένα παράδειγμα.
#Server Module
import socket
#Δημιουργία του αντικειμένου socket.
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind( ("192.168.1.10", 5555) ) #Δένω τον socket στον υπολογιστή μου.
sock.listen(1) #Λέω στον socket ότι περιμένω έναν client να συνδεθεί.
print("Παρακαλώ Περιμένετε. Αναμονή για την σύνδεση του client.") #Ένα μήνυμα προς τον χρήστη.
client, address = sock.accept() #Περιμένω να συνδεθεί ο client.
message = "Welcome to my Server!"
list_message = list(message) #Μετατρέπω την συμβολοσειρά μου σε λίστα.
length = len(list_message) #Υπολογίζω το μέγεθος της λίστας.
#Αυτό το loop στέλνει έναν έναν τους χαρακτήρες της συμβολοσειράς μου.
for i in range(0, length):
character_byte = str.encode( list_message[i] ) #Μετατρέπω τον συγκεκριμένο χαρακτήρα σε byte.
client.sendall( character_byte ) #Στέλνω το byte στον client.
continue
client.sendall( str.encode('\0') )#Στέλνω τον χαρακτήρα '\0' ως flag για να πω στον client
#Να σταματήσει να λαμβάνει bytes.
#Client Module
import socket
#Δημιουργία του αντικειμένου socket.
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect( ("192.168.1.10", 5555) ) #Σύνδεση στον συγκεκριμένο υπολογιστή.
message = ""
#Αυτό το loop λαμβάνει έναν έναν τους χαρακτήρες και δημιουργεί μια συμβολοσειρά με αυτούς.
while True:
byte = sock.recv(1) #Λαμβάνω ένα byte από τον server.
character = bytes.decode( byte ) #Μετατρέπω το byte σε χαρακτήρα.
#Τελειώνω το loop όταν λάβω τον χαρακτήρα '\0'
if character == '\0':
break
#Προσθέτω τον επόμενο χαρακτήρα στην συμβολοσειρά μου.
message += character
print(message) #Εμφανίζω το μήνυμα.
Ας εξηγήσουμε αναλυτικά τι γίνεται σε αυτά τα 2 modules.
Πρώτα από όλα μετατρέπουμε την συμβολοσειρά μας "Welcome to my Server!" σε λίστα.Η λίστα μας
θα είναι η εξής: 'W','e','l','c','o','m','e','','t','o','','m','y','','S','e','r','v','e','r','!'
Στην συνέχει παίρνουμε το μέγεθος της λίστας μας το οποίο είναι 21 χαρακτήρες.
Μετά δημιουργούμε ένα loop το οποίο τρέχει τόσες φορές όσο είναι το μέγεθος της λίστας μας.
Όταν το i = 0 τότε μετατρέπουμε τον χαρακτήρα list_message[0] o οποίος είναι ο 'W' σε byte
και τον στέλνουμε στον client.Όταν το i = 2 τότε κάνουμε την ίδια δουλεία για τον χαρακτήρα
'e'.Αυτό γίνεται μέχρι και ο τελευταίος χαρακτήρας μας '!' σταλθεί στον client.
Τέλος στέλνουμε τον χαρακτήρα '\0' για να πούμε στον client να σταματήσει να λαμβάνει bytes.
Ταυτόχρονα στο module του client γίνεται η εξής δουλειά.
Πρώτα ορίζουμε μία μεταβλητή συμβολοσειράς (message = "") η οποία είναι άδεια.
Στην συνέχεια δημιουργούμε ένα While loop το οποίο τρέχει μέχρι ο server να μας
στείλει τον χαρακτήρα '\0'.Πρώτα περιμένουμε να λάβουμε ένα byte.Στην συνέχεια μετατρέπουμε
το byte σε χαρακτήρα.Ο πρώτος χαρακτήρας που θα λάβουμε θα είναι βέβαια ο 'W'.
Μετά ελέγχουμε αν ο χαρακτήρας είναι ο '\0' (Δεν είναι αφού ο 'W' != '\0'), αν ναι σταματάμε διαφορετικά προσθέτουμε τον
χαρακτήρα μας στο message.Άρα μέχρι τώρα ο message = 'W'.Μετά ξανά τρέχει το loop και
περιμένουμε πάλι για να λάβουμε και άλλο byte.O επόμενος χαρακτήρας που θα λάβουμε θα είναι
ο 'e' επομένως δεν είναι ο '\0' άρα τον προσθέτουμε στο message.Άρα τώρα ο message = "We".
Αυτήν η δουλειά γίνεται μέχρι να λάβουμε τον χαρακτήρα '\0'.Ένα βήμα ποιο πριν λαμβάνουμε
τον χαρακτήρα '!' άρα ο message τώρα θα έχει γίνει message = "Welcome to my Server!".
Στην συνέχεια λαμβάνουμε τον '\0' άρα η if γίνεται αληθές και το loop τερματίζετε.
Ελπίζω να καταφέρατε να καταλάβετε πως δουλεύει.
Στην ουσία αυτός είναι ένας τρόπος που σκέφτηκα για να μπορώ να στέλνω strings μέσου sockets
χωρίς να γνωρίζω το μέγεθος τους.
Όπως είπα ποιο πριν οι χαρακτήρες αποτελούνται από 1 byte μόνο εάν είναι λατινικοί.
Άμα προσπαθήσετε να στείλετε ένα μήνυμα πχ με ελληνικούς χαρακτήρες το παραπάνω πρόγραμμα θα
κρασάρει. Αυτό συμβαίνει για τον εξής λόγο:
Όπως γνωρίζετε(η τουλάχιστον όσοι από εσάς γνωρίζετε), έχει δημιουργηθεί ένας κώδικας που ονομάζεται
utf-8 ο οποίος περιλαμβάνει την κωδικοποίηση όλων των χαρακτήρων από σχεδόν όλες τις γλώσσες του κόσμου.Στην ουσία όταν κάνετε str.encode() μετατρέπονται η χαρακτήρες σε bytes χρησιμοποιώντας
των κώδικα utf-8.Το ίδιο και όταν κάνετε bytes.decode() πάλι χρησιμοποιείτε ο κώδικας utf-8 για
την αποκωδικοποίσηση των bytes.Μέχρι τώρα γνωρίζετε ότι οι λατινικοί χαρακτήρες χρειάζονται
1 byte για την αναπαράσταση τους. Χαρακτήρες όμως από άλλες γλώσσες χρειάζονται περισσότερα
bytes για να αναπαρασταθούν. ΠΧ ο "λ" μπορεί να χρειάζεται 2 bytes για να αναπαρασταθεί (Δεν ξέρω
απλός δίνω ένα παράδειγμα.) Επομένως στο πρόγραμμα που φτιάξαμε εμείς περιμένουμε να λάβουμε ένα
byte άρα άμα ο χαρακτήρας μας είναι πχ ελληνικός και χρειάζεται 2 bytes ή και παραπάνω για να
αναπαρασταθεί εμείς θα λάβουμε μόνο το ένα byte με συνέπεια όταν ο bytes.decode() πάει να
αποκωδικοποήσει το byte που λάβαμε δεν θα γνωρίζει τ είναι.Άρα το πρόγραμμα μας θα μας πετάει
σφάλμα.H λύσει αυτού του προβλήματος είναι πολύ εύκολη.Ο κώδικας utf-8 μας λέει ότι για όλους τους
χαρακτήρες που μπορεί να αναπαραστήσει κανένας δεν θα ξεπερνάει τα 4 bytes.Επομένως άμα πάμε
στο module του client και στην recv() πούμε να περιμένει να λάβει 4 bytes αντί για 1 τότε το πρόβλημα λύθηκε.Από εμπειρία όμως έχω παρατηρήσει ότι στην recv πρέπει να βάζετε 4+1 bytes
δηλαδή 5.Δεν γνωρίζω τον λόγω αλλά πολλές φορές κρασάρει όταν περιμένω μόνο 4 bytes άρα για
καλό και για κακό βάλτε 5.
Κάποιοι θα μπορούσατε να με πείτε και που ξέρω εγώ αν ο χαρακτήρας αποτελείτε από 1, 2, 3
ή τέσσερα bytes και γιατί θέλω να λάβω 4 bytes στην recv.Εδώ πρέπει να σημειώσω ότι δεν έχει
σημασία αν λάβουμε λιγότερα bytes από αυτά που έχουμε πει στην recv να περιμένει να λάβει, πρόβλημα
θα υπάρχει να λάβουμε περισσότερα.
Εκτός από το παραπάνω πρόβλημα μπορεί να υπάρξει και ακόμα ένα.
Φανταστείτε ότι τα bytes που στέλνουμε στον client είναι μπάλες του τένις και ο server
αυτό που κάνει είναι να πετάει αυτές τις μπάλες όσο ποιο γρήγορα μπορεί (Εξαρτάτε από την
ταχύτητα του επεξεργαστή και την ταχύτητα του διαδικτύου).Ο client αυτό που προσπαθεί να κάνει είναι να πιάσει αυτές τις μπάλες.
Αν ο υπολογιστής του client είναι αρκετά αργός μπορεί να μην προλάβει να πιάσει μια μπάλα
και έτσι να την χάσει.Θα μπορούσατε να μου πείτε <ε δεν μπορεί ο μ@...ς να πάει και να την
μαζέψει όταν ο server σταματήσει να του πετάει μπάλες;>Όχι αυτό δεν γίνεται, άμα δεν την πιάσει
πάει την έχασε
.Λοιπών άμα χάσει τουλάχιστον μία μπάλα ο client τότε δημιουργείτε μεγάλο
πρόβλημα διότι χάνουμε μια πληροφορία για το τι αναπαριστούν όλα αυτά τα bytes.Το θέμα είναι
ότι όντος χάσαμε ένα byte αλλά στον client θα δημιουργηθεί μια πληροφορία ούτως η άλλος από
τα bytes που κατάφερε να πιάσει.Αυτήν η πληροφορία μπορεί είτε να μην αναγνωρίζεται από τον
υπολογιστή είτε να αναγνωρίζεται.Στην περίπτωση που δεν αναγνωρίζετε κρασάρει το πρόγραμμα.
Στην άλλη περίπτωση δεν κρασάρει αλλά σίγουρα η πληροφορία που αναπαριστούν αυτά τα bytes
δεν είναι αυτήν που προσπάθησε να μας στείλει ο server.Για παράδειγμα ο server μπορεί να προσπαθήσει
να μας στείλει την εξής πληροφορία "Γεια σου Νίκο" αλλά ο client να δει στην οθόνη του "Γεα ου κο"
η κάτι παρόμοιο γιατί δεν πρόλαβε να "πιάσει" τα bytes που αναπαριστούσαν το 'ι', 'σ', 'Ν', 'ί'.
Αυτό το πρόβλημα το έχω λύσει αναγκάζοντας τον server να στέλνει τα bytes ποιο αργά απ ότι μπορεί.
Δεν είναι και πολύ καλή λύσει αλλά αυτήν είναι που έχω βρει μέχρι τώρα.
Απλός κάντε import το module time της python και στην αρχή του for loop του server προσθέστε
την συνάρτηση time.sleep(seconds) και δώστε πόσα δευτερόλεπτα θέλετε να περιμένει πρώτου συνεχίσει.
Για παράδειγμα άμα βάλετε time.sleep(1) θα στέλνετε ένα byte το δευτερόλεπτο που στην ουσία για να
στείλετε μία συμβολοσειρά που αποτελείτε από 5 χαρακτήρες θα κάνει 5 δευτερόλεπτα να σταλθούν όλα.
Βέβαια αυτό δημιουργεί ακόμα ένα πρόβλημα το οποίο είναι η καθυστέρηση. Σίγουρα θα υπάρχει τρόπος
να στέλνεις τόσα bytes το δευτερόλεπτο ανάλογα με την ταχύτητα του διαδικτύου σου αλλά ακόμα δεν
το γνωρίζω.Αν κάποιος το γνωρίζει ας το απαντήσει.Τέλος πάντων εγώ βάζω time.sleep(0.1) και είμαι
καλυμμένος.
Αυτά λοιπόν παιδιά για αυτόν τον οδηγό.Ελπίζω να τα καταλάβατε.
Τα λέμε στον επόμενο