L'API socket est la norme de facto pour les communications TCP/IP et UDP/IP (c'est-à-dire le code réseau tel que nous le connaissons). Cependant, une de ses fonctions principales, accept()
est un peu magique.
Pour emprunter une définition semi-formelle:
accept () est utilisé côté serveur. Il accepte une tentative entrante reçue de créer une nouvelle connexion TCP à partir du client distant, et crée un nouveau socket associé à la paire d'adresses de socket de cette connexion.
En d'autres termes, accept
renvoie un nouveau socket via lequel le serveur peut communiquer avec le client nouvellement connecté. L'ancienne socket (sur laquelle accept
a été appelée) reste ouverte, sur le même port, à l'écoute des nouvelles connexions.
Comment fonctionne accept
? Comment est-il mis en œuvre? Il y a beaucoup de confusion sur ce sujet. De nombreuses personnes affirment qu'accepter ouvre un nouveau port et que vous communiquez avec le client via ce dernier. Mais ce n'est évidemment pas vrai, car aucun nouveau port n'est ouvert. Vous pouvez réellement communiquer via le même port avec différents clients, mais comment? Lorsque plusieurs threads appellent recv
sur le même port, comment les données savent-elles où aller?
Je suppose que c'est quelque chose dans le sens de l'adresse du client associée à un descripteur de socket, et chaque fois que des données transitent par recv
, elles sont acheminées vers la bonne socket, mais je ne suis pas sûr.
Ce serait formidable d'obtenir une explication approfondie du fonctionnement interne de ce mécanisme.
Votre confusion réside dans le fait de penser qu'un socket est identifié par Server IP: Server Port. En réalité, les prises sont identifiées de manière unique par un quatuor d'informations:
Client IP : Client Port
et Server IP : Server Port
Ainsi, bien que l'IP du serveur et le port du serveur soient constants dans toutes les connexions acceptées, les informations côté client lui permettent de garder une trace de tout ce qui se passe.
Exemple pour clarifier les choses:
Disons que nous avons un serveur à 192.168.1.1:80
et deux clients, 10.0.0.1
et 10.0.0.2
.
10.0.0.1
ouvre une connexion sur le port local 1234
et se connecte au serveur. Maintenant, le serveur a un socket identifié comme suit:
10.0.0.1:1234 - 192.168.1.1:80
À présent 10.0.0.2
ouvre une connexion sur le port local 5678
et se connecte au serveur. Maintenant, le serveur a deux sockets identifiés comme suit:
10.0.0.1:1234 - 192.168.1.1:80
10.0.0.2:5678 - 192.168.1.1:80
Juste pour ajouter à la réponse donnée par l'utilisateur "17 sur 26"
Le socket se compose en fait de 5 Tuple - (IP source, port source, IP de destination, port de destination, protocole). Ici, le protocole pourrait TCP ou UDP ou tout autre protocole de couche de transport. Ce protocole est identifié dans le paquet à partir du champ "protocole" dans le datagramme IP.
Ainsi, il est possible d'avoir différentes applications sur le serveur communiquant avec le même client sur exactement les mêmes 4-tuples mais différents dans le domaine du protocole. Par exemple
Apache côté serveur discutant (server1.com:880-client1:1234 sur TCP) et World of Warcraft parlant (server1.com:880-client1:1234 sur UDP)
Le client et le serveur géreront cela car le champ de protocole dans le paquet IP dans les deux cas est différent même si tous les 4 autres champs sont identiques.
Ce qui m'a dérouté quand j'ai appris cela, c'est que les termes socket
et port
suggèrent qu'ils sont quelque chose de physique, alors qu'en fait ce ne sont que des structures de données que le noyau utilise pour résumer les détails de la mise en réseau.
En tant que telles, les structures de données sont mises en œuvre pour pouvoir séparer les connexions des différents clients. Quant à comment ils sont implémentés, la réponse est soit a.) Cela n'a pas d'importance, le but de l'API sockets est précisément que l'implémentation ne devrait pas avoir d'importance ou b.) A juste un Regardez. Outre les livres Stevens hautement recommandés fournissant une description détaillée d'une implémentation, consultez la source sous Linux ou Solaris ou l'un des BSD.
Comme l'a dit l'autre gars, un socket est identifié de manière unique par un 4-Tuple (IP client, port client, IP serveur, port serveur).
Le processus serveur s'exécutant sur l'IP du serveur maintient une base de données (ce qui signifie que je me fiche du type de table/liste/arborescence/tableau/structure de données magiques qu'il utilise) de sockets actifs et écoute sur le port du serveur. Lorsqu'il reçoit un message (via la pile TCP/IP du serveur), il vérifie l'IP et le port client par rapport à la base de données. Si l'IP client et le port client se trouvent dans une entrée de base de données, le message est transmis à un gestionnaire existant, sinon une nouvelle entrée de base de données est créée et un nouveau gestionnaire est généré pour gérer ce socket.
Dans les premiers jours de l'ARPAnet, certains protocoles (FTP pour un) écoutaient un port spécifié pour les demandes de connexion et répondaient avec un port de transfert. D'autres communications pour cette connexion passeraient par le port de transfert. Cela a été fait pour améliorer les performances par paquet: les ordinateurs étaient plusieurs fois plus lents à l'époque.