中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

linux socket怎么使用

發布時間:2022-02-24 16:27:24 來源:億速云 閱讀:194 作者:iii 欄目:建站服務器

本篇內容介紹了“linux socket怎么使用”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

socket又稱套接字,是Linux跨進程通信(IPC)方式的一種,它不僅僅可以做到同一臺主機內跨進程通信,還可以做到不同主機間的跨進程通信。

本教程操作環境:linux5.9.8系統、Dell G3電腦。

socket 的原意是“插座”,在計算機通信領域,socket 被翻譯為“套接字”,它是計算機之間進行通信的一種約定或一種方式。通過 socket 這種約定,一臺計算機可以接收其他計算機的數據,也可以向其他計算機發送數據。

linux中的socket

Socket是Linux跨進程通信(IPC,Inter Process Communication,詳情參考:Linux進程間通信方式總結)方式的一種。相比于其他IPC方式,Socket更牛的地方在于,它不僅僅可以做到同一臺主機內跨進程通信,它還可以做到不同主機間的跨進程通信。根據通信域的不同可以劃分成2種:Unix domain socket 和 Internet domain socket。

1. Internet domain socket

Internet domain socket用于實現不同主機上的進程間通信,大部分情況下我們所說的socket都是指internet domain socket。(下文不特殊指代的情況下,socket就是指internet domain socket。)

要做到不同主機跨進程通信,第一個要解決的問題就是怎么唯一標識一個進程。我們知道主機上每個進程都有一個唯一的pid,通過pid可以解決同一臺主機上的跨進程通信進程的識別問題。但是如果2個進程不在一臺主機上的話,pid是有可能重復的,所以在這個場景下不適用,那有什么其他的方式嗎?我們知道通過主機IP可以唯一鎖定主機,而通過端口可以定位到程序,而進程間通信我們還需要知道通信用的什么協議。這樣一來“IP+端口+協議”的組合就可以唯一標識網絡中一臺主機上的一個進程。這也是生成socket的主要參數。

每個進程都有唯一標識之后,接下來就是通信了。通信這事一個巴掌拍不響,有發送端程序就有接收端程序,而Socket可以看成在兩端進行通訊連接中的一個端點,發送端將一段信息寫入發送端Socket中,發送端Socket將這段信息發送給接收端Socket,最后這段信息傳送到接收端。至于信息怎么從發送端Socket到接收端Socket就是操作系統和網絡棧該操心的事情,我們可以不用了解細節。如下圖所示:

linux socket怎么使用

為了維護兩端的連接,我們的Socket光有自己的唯一標識還不夠,還需要對方的唯一標識,所以一個上面說的發送端和接收端Socket其實都只有一半,一個完整的Socket的組成應該是由[協議,本地地址,本地端口,遠程地址,遠程端口] 組成的一個5維數組。比如發送端的Socket就是 [tcp,發送端IP,發送端port,接收端IP,接收端port],那么接收端的Socket就是 [tcp,接收端IP,接收端port,發送端IP,發送端port]。

打個比方加深下理解,就比如我給你發微信聯系你這個場景,我倆就是進程,微信客戶端就是Socket,微信號就是我倆的唯一標識,至于騰訊是怎么把我發的微信消息傳到你的微信上的細節,我們都不需要關心。為了維持我倆的聯系,我們的Socket光有微信客戶端還不行,我倆還得加好友,這樣通過好友列表就能互相找到,我的微信客戶端的好友列表中的你就是我的完整Socket,而你的微信客戶端的好友列表中的我就是你的完整Socket。希望沒有把你們弄暈。。。

Socket根據通信協議的不同還可以分為3種:流式套接字(SOCK_STREAM),數據報套接字(SOCK_DGRAM)及原始套接字。

  • 流式套接字(SOCK_STREAM):最常見的套接字,使用TCP協議,提供可靠的、面向連接的通信流。保證數據傳輸是正確的,并且是順序的。應用于Telnet遠程連接、WWW服務等。

  • 數據報套接字(SOCK_DGRAM):使用UDP協議,提供無連接的服務,數據通過相互獨立的報文進行傳輸,是無序的,并且不保證可靠性。使用UDP的應用程序要有自己的對數據進行確認的協議。

  • 原始套接字:允許對低層協議如IP或ICMP直接訪問,主要用于新的網絡協議實現的測試等。原始套接字主要用于一些協議的開發,可以進行比較底層的操作。它功能強大,但是沒有上面介紹的兩種套接字使用方便,一般的程序也涉及不到原始套接字。

套接字工作過程如下圖所示(以流式套接字為例,數據報套接字流程有所不同,可以參考:什么是套接字(Socket)):服務器首先啟動,通過調用socket()建立一個套接字,然后調用bind()將該套接字和本地網絡地址聯系在一起,再調用listen()使套接字做好偵聽的準備,并規定它的請求隊列的長度,之后就調用accept()來接收連接。客戶端在建立套接字后就可調用connect()和服務器建立連接。連接一旦建立,客戶機和服務器之間就可以通過調用read()和write()來發送和接收數據。最后,待數據傳送結束后,雙方調用close()關閉套接字。

linux socket怎么使用

從TCP連接視角看待上述過程可以總結如圖,可以看到TCP的三次握手代表著Socket連接建立的過程,建立完連接后就可以通過read,wirte相互傳輸數據,最后四次揮手斷開連接刪除Socket。

linux socket怎么使用

2. Unix domain socket

Unix domain socket 又叫 IPC(inter-process communication 進程間通信) socket,用于實現同一主機上的進程間通信。socket 原本是為網絡通訊設計的,但后來在 socket 的框架上發展出一種 IPC 機制,就是 UNIX domain socket。雖然網絡 socket 也可用于同一臺主機的進程間通訊(通過 loopback 地址 127.0.0.1),但是 UNIX domain socket 用于 IPC 更有效率:不需要經過網絡協議棧,不需要打包拆包、計算校驗和、維護序號和應答等,只是將應用層數據從一個進程拷貝到另一個進程。這是因為,IPC 機制本質上是可靠的通訊,而網絡協議是為不可靠的通訊設計的。

UNIX domain socket 是全雙工的,API 接口語義豐富,相比其它 IPC 機制有明顯的優越性,目前已成為使用最廣泛的 IPC 機制,比如 X Window 服務器和 GUI 程序之間就是通過 UNIX domain socket 通訊的。Unix domain socket 是 POSIX 標準中的一個組件,所以不要被名字迷惑,linux 系統也是支持它的。

了解Docker的同學應該知道Docker daemon監聽一個docker.sock文件,這個docker.sock文件的默認路徑是/var/run/docker.sock,這個Socket就是一個Unix domain socket。在后面的實踐環節會詳細介紹。

Socket實踐

要學好編程,最好的方式就是實踐。接下來我們來實際用下Socket通信,并且觀察Socket文件

1. Internet domain socket實踐

現在我們就用socket寫一個server,由于本人C語言經驗較少,所以這里我選擇用GoLang實踐。server的功能很簡單,就是監聽1208端口,當收到輸入ping時就返回pong,收到echo xxx就返回xxx,收到quit就關閉連接。socket-server.go的代碼參考文章:使用 Go 進行 Socket 編程 | 始于珞塵。如下:

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-like系統中,進程生產的socket通過socket文件來表示,進程通過向socket文件讀寫內容實現消息的傳遞。在Linux系統中,通常socket文件在/proc/pid/fd/文件路徑下。啟動我們的socket-server,我們來窺探一下對應的socket文件。先啟動server:

# go run socket-server.go 
Server Started ...

再開一個窗口,我們先查看server進程的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

可以看到我們的server pid為20007,接下來我們來查看下server監聽的socket:

# 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],這個便是server端的socket。socket-server啟動經歷了socket() --> bind() --> listen()3個過程,創建了這個LISTEN socket用來監聽對1208端口的連接請求。

我們知道socket通信需要一對socket:server端和client端。現在我們再開一個窗口,在socket-server的同一臺機器上用telnet啟動一個client ,來看看client端的socket:

# telnet localhost 1208
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

繼續查看server端口打開的文件描述符;

# 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)是server到監聽socket文件名,所屬進程pid是20007

  • localhost:1208->localhost:51090 (ESTABLISHED)是server端為client端建立的新的socket,負責和client通信,所屬進程pid是20007

  • localhost:51090->localhost:1208 (ESTABLISHED)是client端為server端建立的新的socket,負責和server通信,所屬進程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地址和端口號。

“linux socket怎么使用”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

平武县| 密云县| 聂拉木县| 柘城县| 郸城县| 高陵县| 凭祥市| 南康市| 长治县| 千阳县| 大港区| 古田县| 皋兰县| 芦溪县| 玉溪市| 托克托县| 湄潭县| 木兰县| 阳新县| 且末县| 新沂市| 大渡口区| 视频| 洞头县| 苏州市| 常州市| 宝清县| 辛集市| 深水埗区| 额尔古纳市| 右玉县| 宜兰市| 日土县| 云南省| 萍乡市| 白山市| 沽源县| 拉孜县| 上饶县| 平泉县| 那曲县|