[Python] I socket: un Server TCP-IP

Per terminare la mia prima introduzione ai socket, posto il compendio di un buon client TCP: un server TCP-IP scritto in Pyhton.

Disclaimer: ricordo che questo "esercizio" di programmazione è stato scritto in questo ottimo blog, dal quale ruberò sicuramente altri esempi dato che l'autore è davvero molto chiaro e competente.

socket

Posto anche il Link alle pagine di manuale sui sockets (python 2.7) tanto per non perdere l'abitudine (non si sa mai che qualcuno pensi che servano a qualcosa :P).

Come nell'articolo precedente, ecco l'indice:

Oggi, per concludere l'introduzione, scrivo e commento il server. _discalimer: _usando Archlinux, il mio interprete python di default è python 3.x, e necessito specificare l'interprete adeguato (pyhton2) per questo codice.

Il server

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python2

import socket
import sys
from thread import *

HOST = '' # se non metto nulla, significa *.*.*.*
PORT = int(sys.argv[1]) # la porta che preferisco (sopra la 1024)

Sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

print ' socket creato '
try:
    Sock.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind fallito, Errore: ' , int(msg[0]), '; messaggio: ', msg[1]
    sys.exit()

print 'socket bindato  a ip/porta '

Come per il client si importano le librerie (rigorosamente standard), e questa volta ne aggiungiamo una che servirà a gestire multiple connessioni in un'ottica di _thread per connection, _cioè un thread (una specie di porcesso [non me ne vogliano i programmatori]) per ogni connessione che un client effettua sul nostro server.

Viene poi impostato HOST, ovvero l'IP sul quale il server starà in ascolto (nel mi caso tutte le interfaccie possibili) e PORT per la porta di scolto (ricordate di utilizzare una porta oltre 1024 se il server gira senza permessi di root (auspicabile).

Prima di tutto creiamo il nostro socket con

Sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

che indica una variabile Sock composta da un socket con protoccolo di rete IP (socket.AF_NET) e di trasporto TCP (socket.SOCK_STREAM).

Ed in fine, dopo aver stampato a video il nosto successo, facciamo il bind (accoppiamo) il nostro socket a/agli IP selezionati in precedenza:

Sock.bind((HOST, PORT))

Anche questa volta il _bind _viene fatto bene, utilizzando il metodo standard per la gestione degli errori in python, [try/exept].

Gratificati del nostro socket appeso ai nostri IP, passiamo alla fase successiva:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python2

import socket
import sys
from thread import *

HOST = '' # se non metto nulla, significa *.*.*.*
PORT = int(sys.argv[1]) # la porta che preferisco (sopra la 1024)

Sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

print ' socket creato '
try:
Sock.bind((HOST, PORT))
except socket.error , msg:
print 'Bind fallito, Errore: ' , int(msg[0]), '; messaggio: ', msg[1]
sys.exit()

print 'socket bindato a ip/porta '

Sock.listen(10) # il socket e in ascolto e gestisce fino a 10 connessioni

La cosa si fa interessante, ed ora il socket non è solo bindato, ma è messo in ascolto, con un parametro "10" che definisce il numero di connessioni da gestire simultaneamente, prima di accodare le richieste.

Ed ecco che entra in gioco la libreria importata thread importata al principio, con la quale possiamo gestire le dieci connessioni concorrenti senza crushare il nostro povero server e, quindi, il socket. A differenza di una chiamata client dove all'aumentare delle connessioni aumentano i socket aperti per ogni connessione, il server qui mantiene un solo socket aperto che, grazie al multiplexing, gestisce più connessioni sullo stesso canale (come spiegato in questo illuminante articolo sui sockets).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/usr/bin/env python2

import socket
import sys
from thread import *

HOST = '' # se non metto nulla, significa *.*.*.*
PORT = int(sys.argv[1]) # la porta che preferisco (sopra la 1024)

Sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

print ' socket creato '
try:
Sock.bind((HOST, PORT))
except socket.error , msg:
print 'Bind fallito, Errore: ' , int(msg[0]), '; messaggio: ', msg[1]
sys.exit()

print 'socket bindato a ip/porta '

Sock.listen(10) # il socket e in ascolto e gestisce fino a 10 connessioni

def threadconn(conn):

   conn.send('hey, ecco un server pappagallo\n') # solo stringhe

   while True: # loop infinito

    # ora rispondiamo ai messaggi
       data = conn.recv(1024)

       reply = data + 'Tnis what u said\n'

       if not data:
           break

       conn.sendall(reply)

   conn.close() # usciamo dal loop una volta risposto

Aggiungiamo un po di carne al fuco, definendo una funzione chiamata threadconn che ci servirà per gestire le nostre multiple connessioni; come input, prenderemo la variabile conn che più avanti nel codice sarà la nostra connessione singola, e cominciamo inviando  un messaggio a chi si connette.

Il comando send invia dati sul socket, ma badate bene, perché è possibile inviare solo stringhe; poco sotto, comincia un loop infinito (while True) nel quale i dati ricevuti vengono salvati in una variabile (in blocchi da 1024 bytes) e rispediti al mittente attraverso il socket. Come protezione (non mi ancora ben chiaro come) viene implementato un meccanismo di uscita nel caso di buffer vuoto. Per terminare ogni "pezzo" di conversazione, la connessione viene chiusa (è importante!).

Al termine del codice, arriva il cuore del server che, sempre in maniera perpetua (while 1) gestisce le connessioni in entrata.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/usr/bin/env python2

import socket
import sys
from thread import *

HOST = '' # se non metto nulla, significa *.*.*.*
PORT = int(sys.argv[1]) # la porta che preferisco (sopra la 1024)

Sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

print ' socket creato '
try:
Sock.bind((HOST, PORT))
except socket.error , msg:
print 'Bind fallito, Errore: ' , int(msg[0]), '; messaggio: ', msg[1]
sys.exit()

print 'socket bindato a ip/porta '

Sock.listen(10) # il socket e in ascolto e gestisce fino a 10 connessioni

def threadconn(conn):

   conn.send('hey, ecco un server pappagallo\n') # solo stringhe

   while True: # loop infinito

    # ora rispondiamo ai messaggi
       data = conn.recv(1024)

       reply = data + 'Tnis what u said\n'

       if not data:
           break

       conn.sendall(reply)

   conn.close() # usciamo dal loop una volta risposto

while 1: # e ora aspettiamo le risposte..

   conn, addr = Sock.accept()

   print 'Connessione da', addr[0] , ':', str(addr[1])

   start_new_thread(threadconn ,(conn,))

Sock.close()

Dopo aver salvato le variabili, inizializziamo il thread col comando start_new_thread che prende come argomenti la funzione creata sopra:

start_new_thread(threadconn ,(conn,))

Per terminare in bellezza e perché siamo dei bravi scrptatori, chiudiamo il socket pirma di uscire dal porgramma.

Ed ecco che abbiamo creato un Client ed un server, che fanno da base per i più sfrenato sogni di gloria con python, i socket e la porgrammazione di rete!

Alla prossima..