소켓이라고도 하는 소켓은 Linux IPC(프로세스 간 통신) 방식의 일종으로, 동일한 호스트 내에서 프로세스 간 통신을 수행할 수 있을 뿐만 아니라 서로 다른 호스트 간에 프로세스 간 통신도 수행할 수 있습니다.
이 튜토리얼의 운영 환경: linux5.9.8 시스템, Dell G3 컴퓨터.
소켓의 원래 의미는 "소켓"입니다. 컴퓨터 통신 분야에서 소켓은 "소켓"으로 번역됩니다. 컴퓨터 간의 통신 방법입니다. 소켓 규칙을 통해 컴퓨터는 다른 컴퓨터로부터 데이터를 받을 수 있고 다른 컴퓨터로 데이터를 보낼 수도 있습니다.
Socket in Linux
Socket은 Linux 크로스 프로세스 통신(IPC, Inter Process Communication) 방법입니다. 자세한 내용은 Linux Inter-Process Communication Methods 요약을 참조하세요. 다른 IPC 방법과 비교할 때 소켓의 장점은 동일한 호스트 내에서 프로세스 간 통신을 달성할 수 있을 뿐만 아니라 서로 다른 호스트 간에 프로세스 간 통신을 달성할 수 있다는 것입니다. 다양한 통신 도메인에 따라 Unix 도메인 소켓과 인터넷 도메인 소켓의 두 가지 유형으로 나눌 수 있습니다.
1. 인터넷 도메인 소켓
인터넷 도메인 소켓은 서로 다른 호스트에서 프로세스 간 통신을 구현하는 데 사용됩니다. 대부분의 경우 우리가 참조하는 소켓은 인터넷 도메인 소켓을 나타냅니다. (아래에서 별도로 지정하지 않는 한 소켓은 인터넷 도메인 소켓을 나타냅니다.)
다른 호스트 간의 프로세스 간 통신을 달성하기 위해 해결해야 할 첫 번째 문제는 프로세스를 고유하게 식별하는 방법입니다. 우리는 호스트의 각 프로세스가 고유한 pid를 가지고 있으며 pid가 동일한 호스트의 프로세스 간 통신 프로세스를 식별하는 문제를 해결할 수 있다는 것을 알고 있습니다. 하지만 두 프로세스가 동일한 호스트에 있지 않으면 pid가 반복될 수 있으므로 이 시나리오에는 적용할 수 없습니다. 다른 방법이 있습니까? 호스트 IP를 통해 호스트를 고유하게 잠글 수 있고, 포트를 통해 프로그램을 찾을 수 있다는 것을 알고 있습니다. 프로세스 간 통신을 위해서는 통신에 어떤 프로토콜이 사용되는지도 알아야 합니다. 이런 방식으로 "IP+포트+프로토콜"의 조합은 네트워크의 호스트에 있는 프로세스를 고유하게 식별할 수 있습니다. 이는 소켓 생성을 위한 주요 매개변수이기도 합니다.
각 프로세스에 고유 식별자가 부여된 후 다음 단계는 통신입니다. 통신은 발신자 프로그램이 있는 곳에는 수신자 프로그램이 있으며, 송신자는 송신자 소켓에 정보 조각을 씁니다. 그리고 송신자 소켓 이 정보를 수신측 소켓으로 보내고, 마지막으로 이 정보가 수신측으로 전송됩니다. 정보가 보내는 소켓에서 받는 소켓으로 어떻게 이동하는지에 대해서는 운영 체제와 네트워크 스택이 걱정해야 하는 부분입니다. 아래 그림과 같이:
두 끝 사이의 연결을 유지하려면 소켓이 고유한 식별자를 갖는 것만으로는 충분하지 않고 상대방의 고유 식별자도 필요하므로 위에서 언급한 송신자 및 수신자 소켓은 실제로 절반에 불과하며, 완전한 소켓은 [프로토콜, 로컬 주소, 로컬 포트, 원격 주소, 원격 포트]로 구성된 5차원 배열로 구성되어야 합니다. 예를 들어 송신단의 Socket은 [tcp, 송신단 IP, 송신단 포트, 수신단 IP, 수신단 포트]이고, 수신단의 Socket은 [tcp, 수신단 IP, 수신단 포트, 송신단]이다. 끝 IP, 보내는 끝 포트].
이해를 돕기 위해 비유를 들어보겠습니다. 예를 들어 제가 귀하에게 연락하기 위해 WeChat을 보내는 시나리오에서 우리는 프로세스이고 WeChat 클라이언트는 소켓이며 WeChat ID는 우리의 고유 식별자입니다. Tencent가 나에게 보낸 방법에 대해 우리는 귀하의 WeChat으로 전송되는 WeChat 메시지의 세부 사항에 신경 쓸 필요가 없습니다. 우리 둘 사이의 연결을 유지하기 위해 우리 소켓에는 WeChat 클라이언트만 있습니다. 친구 목록을 통해 서로를 찾을 수 있도록 친구도 추가해야 합니다. 완전한 소켓. 그리고 귀하의 WeChat 클라이언트 친구 목록에 있는 제가 귀하의 완전한 소켓입니다. 내가 당신을 쓰러뜨리지 않았으면 좋겠어요. . .
소켓은 다양한 통신 프로토콜에 따라 스트림 소켓(SOCK_STREAM), 데이터그램 소켓(SOCK_DGRAM), 원시 소켓의 3가지 유형으로 나눌 수 있습니다.
스트리밍 소켓(SOCK_STREAM): TCP 프로토콜을 사용하는 가장 일반적인 소켓은 안정적인 연결 지향 통신 흐름을 제공합니다. 데이터 전송이 정확하고 순차적인지 확인하십시오. Telnet 원격 연결, WWW 서비스 등에 사용됩니다.
데이터그램 소켓(SOCK_DGRAM): UDP 프로토콜을 사용하여 비연결형 서비스를 제공합니다. 데이터는 순서가 맞지 않아 신뢰성이 보장되지 않는 독립적인 메시지를 통해 전송됩니다. UDP를 사용하는 애플리케이션에는 데이터 확인을 위한 자체 프로토콜이 있어야 합니다.
Raw 소켓: 주로 새로운 네트워크 프로토콜 구현 등을 테스트하는 데 사용되는 IP 또는 ICMP와 같은 하위 수준 프로토콜에 직접 액세스할 수 있습니다. 원시 소켓은 주로 일부 프로토콜 개발에 사용되며 상대적으로 낮은 수준의 작업을 수행할 수 있습니다. 강력하지만 위에서 소개한 두 개의 소켓만큼 사용하기 편리하지 않으며 일반 프로그램에는 원래 소켓이 필요하지 않습니다.
소켓의 작업 프로세스는 아래 그림에 나와 있습니다. (스트리밍 소켓을 예로 들면, 데이터그램 소켓 프로세스는 다릅니다. 소켓(소켓)이란 무엇인지 참조할 수 있습니다.): 서버가 먼저 시작됩니다. Call 소켓()을 전달하여 소켓을 설정한 다음, 바인딩()을 호출하여 소켓을 로컬 네트워크 주소와 연결하고, Listen()을 호출하여 수신할 소켓을 준비하고 요청 대기열 길이를 지정한 다음 accept를 호출합니다. () 연결을 수신합니다. 소켓을 설정한 후 클라이언트는 connect()를 호출하여 서버와의 연결을 설정할 수 있습니다. 연결이 설정되면 read() 및 write()를 호출하여 클라이언트와 서버 간에 데이터를 보내고 받을 수 있습니다. 마지막으로 데이터 전송이 완료된 후 양측은 close()를 호출하여 소켓을 닫습니다.
위 프로세스는 그림과 같이 TCP 연결 관점에서 요약할 수 있습니다. TCP의 3방향 핸드셰이크는 연결이 설정된 후 데이터가 연결되는 과정을 나타냅니다. 읽기와 쓰기를 통해 서로 전송되며 마지막 4개의 웨이브는 소켓을 분리하고 삭제합니다.
2. Unix 도메인 소켓
Unix 도메인 소켓은 동일한 호스트에서 프로세스 간 통신을 구현하는 데 사용되는 IPC(Inter-Process Communication) 소켓이라고도 합니다. 소켓은 원래 네트워크 통신을 위해 설계되었지만 나중에 UNIX 도메인 소켓인 소켓 프레임워크를 기반으로 IPC 메커니즘이 개발되었습니다. 네트워크 소켓은 동일한 호스트(루프백 주소 127.0.0.1을 통해)에서 프로세스 간 통신에 사용될 수도 있지만 UNIX 도메인 소켓은 IPC에 더 효율적입니다. 네트워크 프로토콜 스택, 패키징 및 언패킹을 거칠 필요가 없습니다. 체크섬 계산, 시퀀스 번호 및 응답 유지 등은 한 프로세스에서 다른 프로세스로 애플리케이션 계층 데이터를 복사하기만 하면 됩니다. 이는 IPC 메커니즘이 본질적으로 신뢰할 수 있는 통신인 반면 네트워크 프로토콜은 신뢰할 수 없는 통신을 위해 설계되었기 때문입니다.
UNIX 도메인 소켓은 전이중이며 풍부한 API 인터페이스 의미를 갖고 있습니다. 다른 IPC 메커니즘과 비교할 때 이는 가장 널리 사용되는 IPC 메커니즘이 되었습니다. 예를 들어 X Window 서버와 GUI 프로그램 간의 연결은 다음과 같습니다. UNIX 도메인 소켓 통신을 통해. Unix 도메인 소켓은 POSIX 표준의 구성 요소이므로 이름으로 혼동하지 마십시오. Linux 시스템도 이를 지원합니다.
Docker를 아는 학생은 Docker 데몬이 docker.sock 파일을 모니터링한다는 것을 알아야 합니다. 이 docker.sock 파일의 기본 경로는 /var/run/docker.sock입니다. 이에 대해서는 이후 실습 세션에서 자세히 소개하겠습니다.
소켓 연습
프로그래밍을 잘 배우기 위해서는 연습하는 것이 가장 좋은 방법입니다. 다음으로 실제로 소켓 통신을 사용하고 소켓 파일을 살펴보겠습니다
1. 인터넷 도메인 소켓 연습
이제 소켓을 사용하여 서버를 작성해 보겠습니다. C 언어에 대한 경험이 거의 없기 때문에 여기서는 GoLang을 사용하여 연습해 보겠습니다. . 서버의 기능은 매우 간단합니다. 즉, 입력 ping을 받으면 pong을 반환하고, quit를 받으면 xxx를 반환합니다. 연결. 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) } }
모든 것이 파일인 유닉스 계열 시스템에서는 프로세스가 생성한 소켓을 소켓 파일로 표현하고, 프로세스는 소켓 파일의 내용을 읽고 쓰는 방식으로 메시지 전송을 실현합니다. 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가 20007임을 확인할 수 있습니다. :
# 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이 서버측 소켓인 소켓:[470314]을 가리키는 링크 파일임을 알 수 있습니다. 소켓 서버의 시작은 소켓() --> 바인딩() --> 청취()의 세 가지 프로세스를 거쳤습니다. 이 LISTEN 소켓은 포트 1208에 대한 연결 요청을 수신하기 위해 생성됩니다.
소켓 통신에는 서버 측과 클라이언트 측의 한 쌍의 소켓이 필요하다는 것을 알고 있습니다. 이제 다른 창을 열고 텔넷을 사용하여 소켓 서버와 동일한 시스템에서 클라이언트를 시작하겠습니다. 클라이언트 측 소켓을 살펴보겠습니다.
# 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개의 결과는 다음과 같습니다.
입니다.
在/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视频教程》
위 내용은 리눅스 소켓이 뭐야?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!