Maison > Article > Opération et maintenance > qu'est-ce que le socket Linux
Socket, également connu sous le nom de socket, est un type de méthode de communication inter-processus (IPC) Linux. Il peut non seulement réaliser une communication inter-processus au sein du même hôte, mais également réaliser une communication inter-processus entre différents hôtes.
L'environnement d'exploitation de ce tutoriel : système linux5.9.8, ordinateur Dell G3.
La signification originale de socket est "socket". Dans le domaine de la communication informatique, socket est traduit par "socket". Il s'agit d'une convention ou d'une méthode de communication entre ordinateurs. Grâce à la convention socket, un ordinateur peut recevoir des données d’autres ordinateurs et peut également envoyer des données à d’autres ordinateurs.
Socket sous Linux
Socket est une méthode de communication inter-processus Linux (IPC, Inter Process Communication, pour plus de détails, veuillez vous référer à : Résumé des méthodes de communication inter-processus Linux). Par rapport aux autres méthodes IPC, l'avantage de Socket est qu'il peut non seulement réaliser une communication inter-processus au sein du même hôte, mais également réaliser une communication inter-processus entre différents hôtes. Selon les différents domaines de communication, il peut être divisé en deux types : le socket de domaine Unix et le socket de domaine Internet.
1. Socket de domaine Internet
Le socket de domaine Internet est utilisé pour implémenter la communication inter-processus sur différents hôtes. Dans la plupart des cas, le socket auquel nous faisons référence fait référence au socket de domaine Internet. (Sauf indication contraire ci-dessous, socket fait référence à socket de domaine Internet.)
Pour parvenir à une communication inter-processus entre différents hôtes, le premier problème à résoudre est de savoir comment identifier de manière unique un processus. Nous savons que chaque processus sur l'hôte a un pid unique, et le pid peut résoudre le problème de l'identification des processus de communication entre processus sur le même hôte. Mais si les deux processus ne sont pas sur le même hôte, le pid peut être répété, il n'est donc pas applicable dans ce scénario. Existe-t-il un autre moyen ? Nous savons que l'hôte peut être verrouillé de manière unique via l'adresse IP de l'hôte et que le programme peut être localisé via le port. Pour la communication inter-processus, nous devons également savoir quel protocole est utilisé pour la communication. De cette manière, la combinaison « IP+port+protocole » peut identifier de manière unique un processus sur un hôte du réseau. C'est également le paramètre principal pour générer le socket.
Une fois que chaque processus dispose d'un identifiant unique, l'étape suivante est la communication. La communication est une question d'une seule claque. Là où il y a un programme émetteur, il y a un programme récepteur, et Socket peut être considéré comme un point final dans la connexion de communication entre les deux extrémités. L'expéditeur écrit une information dans le Socket expéditeur, et l'expéditeur Socket Envoyez cette information à l'extrémité réceptrice Socket, et finalement cette information est envoyée à l'extrémité réceptrice. Quant à la façon dont les informations passent du Socket d'envoi au Socket de réception, c'est quelque chose dont le système d'exploitation et la pile réseau devraient s'inquiéter. Nous n'avons pas besoin de connaître les détails. Comme le montre la figure ci-dessous :
Afin de maintenir la connexion entre les deux extrémités, il ne suffit pas que notre Socket ait son propre identifiant unique. Il a également besoin de l'identifiant unique de l'autre partie, donc de l'expéditeur. et le récepteur Les Sockets mentionnés ci-dessus ne sont en réalité que la moitié, un Socket complet doit être composé d'un tableau à 5 dimensions composé de [protocole, adresse locale, port local, adresse distante, port distant]. Par exemple, le Socket de l'extrémité d'envoi est [tcp, IP de l'extrémité d'envoi, port d'extrémité d'envoi, IP de l'extrémité de réception, port d'extrémité de réception], puis le Socket de l'extrémité de réception est [tcp, IP de l'extrémité de réception, port d'extrémité de réception, envoi IP de fin, port de fin d'envoi].
Utilisons une analogie pour approfondir notre compréhension. Par exemple, dans le scénario où je vous envoie un WeChat pour vous contacter, nous sommes le processus, le client WeChat est le Socket et l'identifiant WeChat est notre identifiant unique. pour la façon dont Tencent m'a envoyé Nous n'avons pas besoin de nous soucier des détails des messages WeChat envoyés à votre WeChat. Afin de maintenir la connexion entre nous deux, notre Socket n'a que le client WeChat. Nous devons également ajouter des amis, afin que nous puissions nous retrouver via la liste d'amis. Vous êtes dans la liste d'amis de mon client WeChat. Socket complet. Et moi, dans la liste d'amis de votre client WeChat, je trouve votre Socket complet. J'espère que je ne t'ai pas assommé. . .
Socket peut être divisé en 3 types selon différents protocoles de communication : stream socket (SOCK_STREAM), datagram socket (SOCK_DGRAM) et raw socket.
Socket de streaming (SOCK_STREAM) : Le socket le plus courant, utilisant le protocole TCP, fournit un flux de communication fiable et orienté connexion. Assurez-vous que la transmission des données est correcte et séquentielle. Utilisé dans les connexions à distance Telnet, les services WWW, etc.
Datagram Socket (SOCK_DGRAM) : utilise le protocole UDP pour fournir des services sans connexion. Les données sont transmises via des messages indépendants, ce qui est en panne et la fiabilité n'est pas garantie. Les applications utilisant UDP doivent disposer de leurs propres protocoles pour confirmer les données.
Socket brut : permet un accès direct aux protocoles de bas niveau tels que IP ou ICMP, principalement utilisés pour tester de nouvelles implémentations de protocoles réseau, etc. Les sockets brutes sont principalement utilisées pour le développement de certains protocoles et peuvent effectuer des opérations de relativement bas niveau. Il est puissant, mais il n'est pas aussi pratique à utiliser que les deux sockets présentés ci-dessus, et les programmes ordinaires n'impliquent pas les sockets d'origine.
Le processus de fonctionnement du socket est illustré dans la figure ci-dessous (en prenant le socket de streaming comme exemple, le processus du socket de datagramme est différent, vous pouvez vous référer à : Qu'est-ce qu'un socket (Socket)) : Le serveur démarre en premier, et passe Appelez socket() pour établir un socket, puis appelez bind() pour associer le socket à l'adresse du réseau local, puis appelez Listen() pour préparer le socket à l'écoute et spécifiez la longueur de sa file d'attente de requêtes, puis appelez accept. () pour recevoir la connexion. Après avoir établi le socket, le client peut appeler connect() pour établir une connexion avec le serveur. Une fois la connexion établie, les données peuvent être envoyées et reçues entre le client et le serveur en appelant read() et write(). Enfin, une fois la transmission des données terminée, les deux parties appellent close() pour fermer le socket.
Le processus ci-dessus peut être résumé du point de vue de la connexion TCP, comme le montre la figure. Vous pouvez voir que la poignée de main à trois voies de TCP représente le processus d'établissement d'une connexion Socket. Une fois la connexion établie, les données peuvent être. transmis les uns aux autres par lecture et écriture, et les quatre dernières vagues sont agitées, déconnectez et supprimez le Socket.
2. Socket de domaine Unix
Le socket de domaine Unix est également appelé socket IPC (communication inter-processus), qui est utilisé pour implémenter la communication inter-processus sur le même hôte. Socket a été initialement conçu pour la communication réseau, mais plus tard, un mécanisme IPC a été développé sur la base du framework socket, qui est un socket de domaine UNIX. Bien que les sockets réseau puissent également être utilisées pour la communication inter-processus sur le même hôte (via l'adresse de bouclage 127.0.0.1), les sockets de domaine UNIX sont plus efficaces pour IPC : elles n'ont pas besoin de passer par la pile de protocoles réseau, d'empaqueter et de décompresser, et les calculs de somme de contrôle, conserver les numéros de séquence et les réponses, etc., copier simplement les données de la couche d'application d'un processus à un autre. En effet, le mécanisme IPC est une communication intrinsèquement fiable, alors que les protocoles réseau sont conçus pour une communication peu fiable.
Le socket de domaine UNIX est en duplex intégral et possède une sémantique d'interface API riche. Par rapport à d'autres mécanismes IPC, il est devenu le mécanisme IPC le plus largement utilisé. Par exemple, la connexion entre le serveur X Window et le programme GUI est. via la communication par socket de domaine UNIX. Le socket de domaine Unix est un composant de la norme POSIX, alors ne vous laissez pas tromper par son nom, les systèmes Linux le prennent également en charge.
Les étudiants qui connaissent Docker doivent savoir que le démon Docker surveille un fichier docker.sock. Le chemin par défaut de ce fichier docker.sock est /var/run/docker.sock. Ceci sera présenté en détail lors des séances pratiques ultérieures.
Socket Practice
Pour bien apprendre la programmation, le meilleur moyen est de s'entraîner. Ensuite, utilisons réellement la communication Socket et observons les fichiers Socket
1. Pratique des sockets de domaine Internet
Maintenant, nous allons utiliser socket pour écrire un serveur Comme j'ai peu d'expérience en langage C, j'ai choisi d'utiliser GoLang pour m'entraîner ici. . La fonction du serveur est très simple, c'est-à-dire qu'il écoute le port 1208. Lorsqu'il reçoit le ping d'entrée, il renvoie pong. Lorsqu'il reçoit l'écho xxx, il renvoie xxx. Lorsqu'il reçoit le quit, il ferme le. connexion. Article de référence de code pour socket-server.go : Utilisation de Go pour la programmation Socket Commencez avec Luochen. Comme suit :
package main import ( "fmt" "net" "strings" ) func connHandler(c net.Conn) { if c == nil { return } buf := make([]byte, 4096) for { cnt, err := c.Read(buf) if err != nil || cnt == 0 { c.Close() break } inStr := strings.TrimSpace(string(buf[0:cnt])) inputs := strings.Split(inStr, " ") switch inputs[0] { case "ping": c.Write([]byte("pong\n")) case "echo": echoStr := strings.Join(inputs[1:], " ") + "\n" c.Write([]byte(echoStr)) case "quit": c.Close() break default: fmt.Printf("Unsupported command: %s\n", inputs[0]) } } fmt.Printf("Connection from %v closed. \n", c.RemoteAddr()) } func main() { server, err := net.Listen("tcp", ":1208") if err != nil { fmt.Printf("Fail to start server, %s\n", err) } fmt.Println("Server Started ...") for { conn, err := server.Accept() if err != nil { fmt.Printf("Fail to connect, %s\n", err) break } go connHandler(conn) } }
Dans un système de type Unix où tout est un fichier, le socket produit par le processus est représenté par le fichier socket, et le processus réalise la transmission des messages en lisant et en écrivant du contenu dans le fichier socket. Sur les systèmes Linux, le fichier socket se trouve généralement dans le chemin du fichier /proc/pid/fd/. Démarrez notre serveur de socket et jetons un coup d'œil au fichier de socket correspondant. Démarrez d'abord le serveur :
# go run socket-server.go Server Started ...
Ensuite, ouvrez une fenêtre. Nous vérifions d'abord le pid du processus serveur. Vous pouvez utiliser la commande lsof ou netstat :
# lsof -i :1208 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME socket-se 20007 root 3u IPv6 470314 0t0 TCP *:1208 (LISTEN) # netstat -tupan | grep 1208 tcp6 0 0 :::1208 :::* LISTEN 20007/socket-server
Vous pouvez voir que le pid de notre serveur est 20007. Ensuite, vérifions le. socket que le serveur écoute. :
# ls -l /proc/20007/fd total 0 lrwx------ 1 root root 64 Sep 11 07:15 0 -> /dev/pts/0 lrwx------ 1 root root 64 Sep 11 07:15 1 -> /dev/pts/0 lrwx------ 1 root root 64 Sep 11 07:15 2 -> /dev/pts/0 lrwx------ 1 root root 64 Sep 11 07:15 3 -> 'socket:[470314]' lrwx------ 1 root root 64 Sep 11 07:15 4 -> 'anon_inode:[eventpoll]'
Vous pouvez voir que /proc/20007/fd/3 est un fichier de lien pointant vers socket:[470314], qui est le socket côté serveur. Le démarrage du socket-server est passé par trois processus : socket() --> bind() --> Ce socket LISTEN est créé pour écouter les demandes de connexion au port 1208.
Nous savons que la communication socket nécessite une paire de sockets : côté serveur et côté client. Ouvrons maintenant une autre fenêtre et utilisons telnet pour démarrer un client sur la même machine que le serveur socket. Jetons un coup d'œil au socket côté client :
# telnet localhost 1208 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'.
Continuez à vérifier le descripteur de fichier ouvert par le port du serveur ;
# lsof -i :1208 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME socket-se 20007 root 3u IPv6 470314 0t0 TCP *:1208 (LISTEN) socket-se 20007 root 5u IPv6 473748 0t0 TCP localhost:1208->localhost:51090 (ESTABLISHED) telnet 20375 ubuntu 3u IPv4 473747 0t0 TCP localhost:51090->localhost:1208 (ESTABLISHED)Nous avons constaté que par rapport à Il y a 2 autres résultats précédents. Ces 3 résultats sont :
.
在/proc/pid/fd/
文件路径下可以看到server和client新建的socket,这里不做赘述。从第3条结果我们可以看出,前2条socket,LISTEN socket和新建的ESTABLISHED socket都属于server进程,对于每条链接server进程都会创建一个新的socket去链接client,这条socket的源IP和源端口为server的IP和端口,目的IP和目的端口是client的IP和端口。相应的client也创建一条新的socket,该socket的源IP和源端口与目的IP和目的端口恰好与server创建的socket相反,client的端口为一个主机随机分配的高位端口。
从上面的结果我们可以回答一个问题 “服务端socket.accept后,会产生新端口吗”? 答案是不会。server的监听端口不会变,server为client创建的新的socket的端口也不会变,在本例中都是1208。这难到不会出现端口冲突吗?当然不会,我们知道socket是通过5维数组[协议,本地IP,本地端口,远程IP,远程端口] 来唯一确定的。socket: *:1208 (LISTEN)和socket: localhost:1208->localhost:51090 (ESTABLISHED)是不同的socket 。那这个LISTEN socket有什么用呢?我的理解是当收到请求连接的数据包,比如TCP的SYN请求,那么这个连接会被LISTEN socket接收,进行accept处理。如果是已经建立过连接后的客户端数据包,则将数据放入接收缓冲区。这样,当服务器端需要读取指定客户端的数据时,则可以利用ESTABLISHED套接字通过recv或者read函数到缓冲区里面去取指定的数据,这样就可以保证响应会发送到正确的客户端。
上面提到客户端主机会为发起连接的进程分配一个随机端口去创建一个socket,而server的进程则会为每个连接创建一个新的socket。因此对于客户端而言,由于端口最多只有65535个,其中还有1024个是不准用户程序用的,那么最多只能有64512个并发连接。对于服务端而言,并发连接的总量受到一个进程能够打开的文件句柄数的限制,因为socket也是文件的一种,每个socket都有一个文件描述符(FD,file descriptor),进程每创建一个socket都会打开一个文件句柄。该上限可以通过ulimt -n查看,通过增加ulimit可以增加server的并发连接上限。本例的server机器的ulimit为:
# ulimit -n 1024
上面讲了半天服务端与客户端的socket创建,现在我们来看看服务端与客户端的socket通信。还记得我们的server可以响应3个命令吗,分别是ping,echo和quit,我们来试试:
# telnet localhost 1208 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. ping pong echo Hello,socket Hello,socket quit Connection closed by foreign host.
我们可以看到client与server通过socket的通信。
到此为止,我们来总结下从telnet发起连接,到客户端发出ping,服务端响应pong,到最后客户端quit,连接断开的整个过程:
telnet发起向localhost:1208发起连接请求;
server通过socket: TCP *:1208 (LISTEN)收到请求数据包,进行accept处理;
server返回socket信息给客户端,客户端收到server socket信息,为客户端进程分配一个随机端口51090,然后创建socket: TCP localhost:51090->localhost:1208 来连接服务端;
服务端进程创建一个新的socket: TCP localhost:1208->localhost:51090来连接客户端;
客户端发出ping,ping数据包send到socket: TCP localhost:51090->localhost:1208 ;
服务端通过socket: TCP localhost:1208->localhost:51090收到ping数据包,返回pong,pong数据包又通过原路返回到客户端 ,完成一次通信。
客户端进程发起quit请求,通过上述相同的socket路径到达服务端后,服务端切断连接,服务端删除socket: TCP localhost:1208->localhost:51090释放文件句柄;客户端删除 socket: TCP localhost:51090->localhost:1208,释放端口 51090。
在上述过程中,socket到socket之间还要经过操作系统,网络栈等过程,这里就不做细致描述。
2. Unix domain socket实践
我们知道docker使用的是client-server架构,用户通过docker client输入命令,client将命令转达给docker daemon去执行。docker daemon会监听一个unix domain socket来与其他进程通信,默认路径为/var/run/docker.sock。我们来看看这个文件:
# ls -l /var/run/docker.sock srw-rw---- 1 root docker 0 Aug 31 01:19 /var/run/docker.sock
可以看到它的Linux文件类型是“s”,也就是socket。通过这个socket,我们可以直接调用docker daemon的API进行操作,接下来我们通过docker.sock调用API来运行一个nginx容器,相当于在docker client上执行:
# docker run nginx
与在docker client上一行命令搞定不同的是,通过API的形式运行容器需要2步:创建容器和启动容器。
1. 创建nginx容器,我们使用curl命令调用docker API,通过--unix-socket /var/run/docker.sock指定Unix domain socket。首先调用/containers/create,并传入参数指定镜像为nginx,如下:
# curl -XPOST --unix-socket /var/run/docker.sock -d '{"Image":"nginx"}' -H 'Content-Type: application/json' http://localhost/containers/create {"Id":"67bfc390d58f7ba9ac808d3fc948a5d4e29395e94288a7588ec3523af6806e1a","Warnings":[]}
2. 启动容器,通过上一步创建容器返回的容器id,我们来启动这个nginx:
# curl -XPOST --unix-socket /var/run/docker.sock http://localhost/containers/67bfc390d58f7ba9ac808d3fc948a5d4e29395e94288a7588ec3523af6806e1a/start
# docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 67bfc390d58f nginx "/docker-entrypoint.…" About a minute ago Up 7 seconds 80/tcp romantic_heisenberg
至此,通过Unix domain socket我们实现了客户端进程curl与服务端进程docker daemon间的通信,并成功地调用了docker API运行了一个nginx container。
值得注意的是,在连接服务端的Unix domain socket的时候,我们直接指定的是服务端的socket文件。而在使用Internet domain socket的时候,我们指定的是服务端的IP地址和端口号。
总结
Socket是Linux跨进程通信方式的一种。它不仅仅可以做到同一台主机内跨进程通信,它还可以做到不同主机间的跨进程通信。根据通信域的不同可以划分成2种:Unix domain socket 和 Internet domain socket。
Internet domain socket根据通信协议划分成3种:流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM)及原始套接字
一个完整的Socket的组成应该是由[协议,本地地址,本地端口,远程地址,远程端口]组成的一个5维数组。
相关推荐:《Linux视频教程》
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!