Grâce au modèle TCP/IP, nous pouvons envoyer des messages à nos proches et communiquer avec des personnes sur internet. Les données que nous envoyons sur internet doivent être exactes et parvenir à la personne visée à temps et intactes. Cela est possible grâce au modèle TCP/IP, dans lequel les données sont d'abord décomposées en paquets et envoyées sur internet. Une fois qu'elles ont atteint leur cible, elles sont réassemblées et envoyées au client. Dans cet article, je vais expliquer comment construire une connexion TCP/IP simple. Ensuite, nous la testerons en utilisant le module de test intégré de Python, unit test, et le framework de test Python appelé pytest.
Qu'est-ce qu'une connexion TCP/IP ?
Le protocole de contrôle de transmission/protocole Internet est connu sous l'acronyme TCP/IP. Il s'agit d'un ensemble de directives et de procédures utilisées pour connecter des périphériques réseau sur Internet. Il s'agit d'un protocole de communication de données qui permet aux ordinateurs et aux périphériques d'envoyer et de recevoir des données.
Un client ou une machine (le client) reçoit une prestation (telle que l'envoi d'une page web) d'un autre ordinateur (un serveur) sur le réseau lorsqu'il utilise le modèle de communication TCP/client-serveur IP.
La suite de normes TCP/IP est classée comme étant sans état dans son ensemble, ce qui implique que chaque demande du client est considérée comme unique car elle n'a aucune incidence sur les demandes précédentes. Le fait d'être sans état libère les chemins du réseau, ce qui permet une utilisation continue.
Comment fonctionne une connexion TCP/IP ?
Nous allons commencer par parler de la manière d'envoyer un paquet sur un port TCP/IP.
1. La première étape consiste à établir une relation/connexion
Lorsque deux ordinateurs veulent envoyer des données par TCP, ils doivent d'abord établir une connexion avec une poignée de main à trois voies.
Le bit SYN est réglé sur (SYN = "synchroniser ?") lorsque le premier ordinateur transmet un paquet. Le deuxième ordinateur répond en envoyant un message avec le bit ACK réglé sur (ACK = "acknowledge !"). Le premier ordinateur répond par un ACK.
En fait, les trois paquets qui constituent la poignée de main à trois voies ne contiennent souvent aucune donnée. Les ordinateurs sont prêts à recevoir des paquets contenant des données réelles une fois la poignée de main terminée.
2. Dans l'étape suivante, nous envoyons des paquets de données
Le destinataire doit toujours accuser réception d'un paquet de données envoyé par TCP. Le premier ordinateur délivre un paquet contenant des données et un numéro de séquence. Afin d'en accuser réception, le second ordinateur active le bit ACK et augmente le numéro d'accusé de réception de la taille des données qu'il a reçues.
Les numéros de séquence et d'accusé de réception sont contenus dans l'en-tête TCP, comme indiqué ci-dessous :
Lorsque le bit ACK est à 1, il indique que le numéro d'accusé de réception contenu dans l'en-tête TCP est valide.
Grâce à ces deux numéros (le numéro de séquence et le numéro d'accusé de réception), les ordinateurs peuvent savoir quelles données ont été reçues avec succès, quelles données ont été supprimées et quelles données ont été envoyées deux fois par inadvertance.
3. Nous déconnectons ensuite la connexion
Pour cesser d'envoyer ou de recevoir des données, chaque ordinateur peut interrompre la connexion.
En transmettant un paquet avec le bit FIN à 1 (FIN = fin), un ordinateur peut commencer le processus de coupure de la connexion. La réponse de l'autre ordinateur est un ACK et un autre FIN. Après un autre ACK de l'ordinateur initiateur, la connexion est fermée.
Comment construire le côté serveur ?
Je pense qu'à ce stade, nous devrions avoir compris qu'un serveur fait référence à un logiciel qui accepte les requêtes faites sur un réseau. Il existe différents types de serveurs :
- serveur DNS (TCP, UDP)
- serveur de courrier électronique
- serveur de fichiers
- serveur web (couramment utilisé dans les applications, à l'aide de requêtes GET, POST, etc.)
- serveur de base de données
- serveur proxy web, etc.
Dans cet exemple, nous établissons une simple connexion TCP/IP et nous vérifions que toutes les demandes adressées au serveur sont reçues.
Nous commençons par importer les modules nécessaires en exécutant les commandes suivantes :
import socket
import threading
Ensuite, nous spécifions le nœud et le numéro de port que nous voulons que notre serveur écoute les requêtes :
hôte = "0.0.0.0"
port= 5000
Ensuite, nous procédons à la création d'une connexion au serveur en liant l'hôte à un port donné :
host = "0.0.0.0"
port= 5000
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((host, port))
server.listen(5)
Le serveur. listen(5) signifie simplement que notre serveur ne peut écouter que 5 requêtes à la fois. Nous procédons à l'écriture d'une fonction qui serait responsable de la gestion de tous les clients qui envoient le SYN au serveur. Une fois que nous recevons de telles demandes, nous renvoyons ACK au client et fermons la connexion pour commencer à écouter une nouvelle demande, ce qui explique le fait que les demandes effectuées sont sans état.
def handle_client(client_socket) :
# affiche les données du client
request = client_socket.recv(1024)
print "[*] Received : %s" % request
# renvoie un paquet
client_socket.send("ACK")
client_socket.close()
Ensuite, nous écrivons un code indiquant à notre serveur que tant qu'un client est toujours connecté à ce serveur, il doit continuer à écouter les demandes et envoyer la réponse adéquate. Il est généralement recommandé de créer un serveur multithread car si les demandes sont nombreuses, le temps de réponse ralentit si notre serveur n'est pas threadé.
while True:
client, addr = server.accept()
print "[*] Accepted connection from : %s:%d" % (addr[0], addr[1])
# activer la fonction pour traiter les données du client
client_handler = threading.Thread(target = handle_client, args=(client,))
client_handler.start()
La totalité du code :
import socket
import threading
host = "0.0.0.0"
port= 5000
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((host, port))
server.listen(5)
print "[*] Listening on %s:%d" % (host, port)
# This is the thread we handle the data from the client
def handle_client(client_socket) :
# Affiche les données du client
request = client_socket.recv(1024)
print "[*] Received : %s" % request
# Renvoie un paquet
client_socket.send("ACK")
client_socket.close()
while True:
client, addr = server.accept()
print "[*] Accepted connection from : %s:%d" % (addr[0], addr[1])
# Active la fonction pour traiter les données du client
client_handler = threading.Thread(target = handle_client, args=(client,))
client_handler.start()
Comment construire le côté client ?
Un client désigne simplement les ordinateurs et les systèmes qui effectuent des requêtes sur un serveur.
Nous commençons par importer les bibliothèques nécessaires.
importation de la prise
Créer la connexion de socket nécessaire : Veuillez noter que pour pouvoir vous connecter au serveur, vous devez accéder à l'adresse IP de l'hôte et au port particulier sur lequel le serveur écoute.
host= "0.0.0.0"
port = 5000
# créer une connexion socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Ensuite, nous nous connectons au serveur :
client.connect((host, port))
Envoyez quelques requêtes au serveur :
client.send("SYN")
Obtenez une réponse et imprimez-la :
response = client.recv(1024)
print response
Compilation de l'ensemble du code :
import socket
host= "0.0.0.0"
port = 5000
# créer une connexion socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# laisser le client se connecter
client.connect((host, port))
# envoyer des données
client.send("SYN")
# recevoir des données
response = client.recv(1024)
print response
Pour s'assurer que notre application fonctionne comme prévu et ne soulève aucune erreur, nous pouvons la tester manuellement. Pour le code ci-dessus, c'est plus facile car nous n'avons pas beaucoup de fonctions à tester. Toutefois, s'il s'agissait de lignes de code, la tâche serait devenue fastidieuse. Dans une telle situation, nous devrions utiliser des frameworks de test tels que pytest, unittest, etc. pour automatiser le processus, ce que nous ferons dans la dernière partie de cet article.
Dans la barre de recherche de Windows, tapez cmd et ouvrez l'invite de commande de Windows. Naviguez vers le dossier où se trouvent vos fichiers client et serveur, par exemple : C:/Users/Sarima Chiorlu/Documents/Demo/server.py.
Une fois dans le répertoire où se trouve mon fichier, vous pouvez exécuter la commande suivante :
py server.py
Mon serveur devrait maintenant fonctionner et être capable de recevoir des connexions d'un système client. Ensuite, vous exécutez :
py client.py
Si tout est fait correctement, votre client devrait pouvoir commencer à écouter les connexions. Maintenant que nous avons fini de coder notre port tcp/ip, nous pouvons tester notre application. Cependant, avant de commencer tout test, comprenons d'abord pourquoi il est conseillé de tester notre application.
Pourquoi les tests sont-ils importants ?
Les tests sont l'un des aspects les plus importants du processus de développement de logiciels. Nous commettons tous des erreurs, et si certaines de ces erreurs ne sont pas critiques ou ne déclenchent pas de signaux d'alarme, d'autres le sont. Ces erreurs peuvent coûter cher, d'autres peuvent entraîner le déclin d'une organisation. C'est pourquoi nous devons tout tester pour nous assurer qu'il produit le résultat escompté.
Qu'est-ce qu'un test unitaire ?
Il est plus facile de créer et d'exécuter des tests pour votre code Python à l'aide de la bibliothèque standard Unit test.
Les tests unitaires peuvent vous aider à découvrir les failles de vos programmes et à éviter que des régressions ne se produisent lorsque votre code change au fil du temps. En général, les équipes de développement piloté par les tests peuvent trouver les tests unitaires utiles pour s'assurer que tout le code créé possède un ensemble de tests correspondant.
Dans cet article, nous allons utiliser le module de test unitaire de Python pour créer un test qui garantit que toutes les connexions réussies au serveur produisent les résultats requis.
Tester le port avec le test unitaire
Au début de cet article, on nous a dit que nous pouvions automatiser le processus de vérification de l'établissement d'une connexion. Nous allons maintenant voir comment nous pouvons le faire. Pour commencer, nous devons d'abord importer le module de test unitaire et le module socket.
import unittest
import socket
class Test_ServerResponse(unittest.TestCase) :
# Configurer la connexion du client
def setUp(self) :
self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.s.connect(('0.0.0.0', self.port))
def test_1(self) :
self.s.send('hello'.encode())
self.assertEqual(self.s.recv(1024).decode(), 'hello')
if __name__ == '__main__' :
unittest.main()
Dans le code ci-dessus, nous pouvons voir qu'en plus d'importer tous les modules nécessaires, nous avons dû configurer la connexion. Nous avons pris un extrait du client qui peut être vu dans la fonction setup et nous avons vérifié que la réponse : "hello" que nous obtenons est la même que les données qu'un client éventuel enverrait à notre serveur.
En général, l'utilisation du module de test unitaire en python est plus facile pour les cas de test simples. Cependant, dans le cas de fonctions multiples, il devient fastidieux à utiliser. C'est pourquoi nous utilisons le framework pytest qui possède un ensemble plus riche de fonctions intégrées et nécessite moins de code pour obtenir le même résultat.
Conclusion
Cet article explique comment créer une connexion serveur-client de base en Python et comment tester cette connexion à l'aide du module de test intégré "unit test" et de la bibliothèque de test pytest. Nous avons pu passer en revue les principes fondamentaux de la mise en réseau et l'importance des tests.