ソケット (ソケットとも呼ばれます) は、Linux のプロセス間通信 (IPC) メソッドの一種で、同じホスト内でクロスプロセス通信を実現するだけでなく、異なるホスト間でクロスプロセス通信を実現することもできます。ホストのコミュニケーション。
#このチュートリアルの動作環境: linux5.9.8 システム、Dell G3 コンピューター。
socket の本来の意味は「ソケット」で、コンピュータ通信の分野では「ソケット」と訳され、コンピュータ間の通信のための規約や方法のことです。ソケット規約を通じて、コンピューターは他のコンピューターからデータを受信したり、他のコンピューターにデータを送信したりできます。
Linux のソケット
ソケットは、Linux のクロスプロセス通信 (IPC、プロセス間通信) です。詳細については、「概要」を参照してください。 Linuxのプロセス間通信方式)方式。他の IPC 方式と比較した場合、Socket の利点は、同じホスト内でクロスプロセス通信を実現できるだけでなく、異なるホスト間でもクロスプロセス通信を実現できることです。さまざまな通信ドメインに応じて、Unix ドメイン ソケットとインターネット ドメイン ソケットの 2 つのタイプに分けることができます。
1. インターネット ドメイン ソケット
インターネット ドメイン ソケットは、異なるホスト上でプロセス間通信を実装するために使用されます。インターネット、ドメインソケット。 (以下で特に指定しない限り、ソケットはインターネット ドメイン ソケットを指します。)
異なるホスト間でプロセス間通信を実現するには、解決すべき最初の問題は、プロセスを一意に識別する方法です。ホスト上の各プロセスには一意の pid があることがわかっており、pid を使用すると、同じホスト上のプロセス間通信プロセスを識別する問題を解決できます。ただし、2 つのプロセスが同じホスト上にない場合、pid が繰り返される可能性があるため、このシナリオには適用できません。他の方法はありますか?ホストはホスト IP を通じて一意にロックできること、プログラムはポートを通じて特定できることがわかっていますが、プロセス間通信の場合は、通信にどのようなプロトコルが使用されているかも知る必要があります。このように、「IPポートプロトコル」の組み合わせにより、ネットワーク上のホスト上のプロセスを一意に識別することができます。これはソケットを生成するための主要なパラメータでもあります。
各プロセスが一意の識別子を持ったら、次のステップは通信です。通信はスラップの問題であり、送信側プログラムと受信側プログラムがあり、ソケットはその両端間の通信接続のエンドポイントとみなすことができます。送信側は送信側ソケットに情報を書き込み、送信側ソケットはこの情報を受信側のSocketに送信し、最終的にこの情報が受信側に送信されます。情報が送信側ソケットから受信側ソケットにどのように送信されるかについては、オペレーティング システムとネットワーク スタックが考慮すべきことであり、詳細を知る必要はありません。以下の図に示すように:
両端での接続を維持するには、ソケットが独自の一意の識別子を持つだけでは十分ではなく、相手の一意の識別子なので、上記のいずれか 実際には送信側と受信側のソケットは半分しかない 完全なソケットは [プロトコル、ローカルアドレス、ローカルポート、リモートアドレス、リモートポート]。例えば、送信側のソケットが[tcp, 送信側IP, 送信側ポート, 受信側IP, 受信側ポート]の場合、受信側のSocketは[tcp, 受信側IP, 受信側ポート, 送信側]となります。終了 IP、送信終了ポート]。
理解を深めるために例えを使用しましょう。たとえば、連絡するために WeChat を送信するシナリオでは、私たちはプロセス、WeChat クライアントはソケット、WeChat ID です。 Tencent に関しては、私が送信した WeChat メッセージをあなたの WeChat に送信する方法の詳細を気にする必要はありません。私たち 2 人の接続を維持するために、私たちのソケットには WeChat クライアントのみがあります。また、友達を追加する必要があります。そうすれば、友達リストを通じてお互いを見つけることができます。私の WeChat クライアントの友達リストにいるあなたは私の人です。完全なソケットです。そして、あなたの WeChat クライアントの友達リストにある私があなたの完全なソケットです。あなたをノックアウトしなかったことを願っています。 。 。
ソケットは、通信プロトコルの違いに応じて、ストリーム ソケット (SOCK_STREAM)、データグラム ソケット (SOCK_DGRAM)、および raw ソケットの 3 つのタイプに分類できます。
ストリーミング ソケット (SOCK_STREAM): TCP プロトコルを使用する最も一般的なソケットは、信頼性の高い接続指向の通信ストリームを提供します。データ送信が正しく、連続的に行われていることを確認してください。 Telnet リモート接続、WWW サービスなどで使用されます。
データグラム ソケット (SOCK_DGRAM): UDP プロトコルを使用してコネクションレス サービスを提供します。データは独立したメッセージを通じて送信されますが、順序が乱れており、信頼性は保証されません。 UDP を使用するアプリケーションには、データを確認するための独自のプロトコルが必要です。
Raw ソケット: 主に新しいネットワーク プロトコルの実装をテストするために使用される、IP や ICMP などの低層プロトコルへの直接アクセスを可能にします。 Raw ソケットは主に一部のプロトコルの開発に使用され、比較的低レベルの操作を実行できます。強力ではありますが、上で紹介した 2 つのソケットほど使い勝手が悪く、通常のプログラムには独自のソケットが必要ありません。
ソケットの動作プロセスを次の図に示します (ストリーミング ソケットを例として取り上げます。データグラム ソケット プロセスは異なります。参照: ソケット (ソケット) とは) : サーバーは最初に起動し、socket() を呼び出してソケットを確立し、次に、bind() を呼び出してソケットをローカル ネットワーク アドレスに関連付け、次に listen() を呼び出してソケットをリッスンできる状態にします。リクエストキューを作成し、accept() を呼び出して接続を受信します。ソケットを確立した後、クライアントは connect() を呼び出してサーバーとの接続を確立できます。接続が確立されると、read() および write() を呼び出すことによって、クライアントとサーバーの間でデータを送受信できるようになります。最後に、データ送信が完了した後、両者は close() を呼び出してソケットを閉じます。
上記のプロセスを TCP 接続の観点から要約すると、図のようになります。TCP の 3 ウェイ ハンドシェイクがソケット接続を確立するプロセスを表していることがわかります。接続が確立された後、 を読み取り、wirt で相互にデータを送信し、最後の 4 回を wave で切断して Socket を削除します。
2. Unix ドメイン ソケット
Unix ドメイン ソケットは IPC (プロセス間通信) ソケットとも呼ばれ、目的を達成するために使用されます。同一ホスト上のプロセス間通信。ソケットはもともとネットワーク通信用に設計されましたが、後に UNIX ドメイン ソケットであるソケット フレームワークに基づいて IPC メカニズムが開発されました。ネットワーク ソケットは同じホスト上のプロセス間通信 (ループバック アドレス 127.0.0.1 経由) にも使用できますが、UNIX ドメイン ソケットは IPC にとってより効率的です。つまり、ネットワーク プロトコル スタック、パッケージ化とアンパックを通過する必要がなく、チェックサム計算、シーケンス番号と応答などの維持、アプリケーション層データをあるプロセスから別のプロセスにコピーするだけです。これは、ネットワーク プロトコルが信頼性の低い通信向けに設計されているのに対し、IPC メカニズムは本質的に信頼性の高い通信であるためです。
UNIX ドメイン ソケットは全二重であり、豊富な API インターフェイス セマンティクスを備えており、他の IPC メカニズムに比べて明らかな利点があります。X Window サーバーと GUI プログラムの間などで、最も広く使用されている IPC メカニズムとなっています。 UNIX ドメインソケットを介して通信します。 Unix ドメイン ソケットは POSIX 標準のコンポーネントであるため、名前に混同しないでください。Linux システムもサポートしています。
Docker を知っている学生は、Docker デーモンが docker.sock ファイルを監視していることを知っておく必要があります。docker.sock ファイルのデフォルトのパスは /var/run/docker.sock です。このソケットは Unix ドメイン ソケットです。これについては、後の実践セッションで詳しく紹介します。
ソケットの練習
プログラミングを上手に学ぶための最良の方法は練習することです。次に、実際に Socket 通信を使用して、Socket ファイルを観察してみましょう
1. インターネット ドメイン ソケットの実践
今度は、C 言語なので、Socket を使用してサーバーを作成します私は言語経験が少ないので、ここでは GoLang を使って練習することにしました。サーバーの機能は非常に単純で、1208 ポートをリッスンし、入力 ping を受信すると pong を返し、echo xxx を受信すると xxx を返し、quit を受信するとサーバーを閉じます。繋がり。 socket-server.go のコード リファレンス記事: ソケット プログラミングに Go を使用する | Luochen から始める。以下のように:
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) } }
すべてがファイルである Unix のようなシステムでは、プロセスによって生成されたソケットはソケット ファイルによって表され、プロセスはコンテンツをソケット ファイルに読み書きすることでメッセージの送信を実現します。ソケットファイル。 Linux システムでは、ソケット ファイルは通常、/proc/pid/fd/ ファイル パスの下にあります。ソケットサーバーを起動し、対応するソケットファイルを覗いてみましょう。最初にサーバーを起動します:
# go run socket-server.go Server Started ...
次に、ウィンドウを開きます。最初にサーバー プロセスの pid を確認します。lsof または 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
サーバーの pid が確認できます。次に、サーバーによって監視されているソケットを確認してみましょう:
# 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]'
/proc/20007/fd/3 が、socket:[470314] を指すリンク ファイルであることがわかります。これは、サーバ側。ソケットサーバーの起動には、socket() -->bind() -->listen() という 3 つのプロセスが実行されています。この LISTEN ソケットは、ポート 1208 への接続リクエストをリッスンするために作成されます。
ソケット通信には、サーバー側とクライアント側のソケットのペアが必要であることがわかっています。次に、別のウィンドウを開き、Telnet を使用してソケット サーバーと同じマシン上でクライアントを起動しましょう。クライアント側のソケットを見てみましょう:
# telnet localhost 1208 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'.
引き続き、サーバー ポート;
# 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)
前の結果と比較すると、さらに 2 つの結果があることがわかりました。これら 3 つの結果は次のとおりです:
*:1208 (LISTEN) は、サーバーのリスニングソケットファイルとそれが属するプロセスの名前。pid は 20007
localhost:1208->localhost:51090 (ESTABLISHED) はサーバーによって確立された新しいソケットです。クライアント用です。クライアントとの通信を担当します。プロセス PID は 20007
localhost:51090->localhost:1208 (ESTABLISHED) は、クライアントによって確立された新しいソケットですサーバー用です。サーバーとの通信を担当します。プロセス PID は 20375
です。在/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视频教程》
以上がLinuxソケットとは何ですかの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。