一、說明
背景一:大學的時候學網絡編程,經常看到說socket有AF_UNIX、AF_INET和AF_INET6三個協議族,AF_UNIX一般不用、AF_INET是IPv4的、AF_INET6是IPv6的。基於這種說教一直以來寫網絡編程,上來協議族就寫AF_INET,AF_UNIX是什么怎么用一直沒深究。
背景二:畢業后多接觸Linux,也經常用netstat看端口監聽情況,在較長一段時間內也不懂“netstat -ltnp”、"netstat -unp",上來就是“netstat -anp”。這樣導致的問題就是在最后總是有“Active UNIX domain sockets”一項,而且經常占很長的一個版面,要往前拉很久才能看到前面的tcp和udp。
背景三:今天早上看《Linux內核源代碼情景分析》的進程間通信章節,發現AF_UNIX和UNIX domain sockets竟是一個東西,同時解決了兩個困惑,真想擊節稱嘆。
二、使用代碼實現
2.1 服務端示例代碼
import socket class SocketServer: def __init__(self): # 常規tcp監聽寫法 # server_address = ('127.0.0.1', 9999) # socket_family = socket.AF_INET # socket_type = socket.SOCK_STREAM # unix domain sockets 監聽寫法 server_address = '/tmp/uds_socket' socket_family = socket.AF_UNIX socket_type = socket.SOCK_STREAM # 其他代碼完全一樣 self.sock = socket.socket(socket_family, socket_type) self.sock.bind(server_address) self.sock.listen(1) print(f"listening on '{server_address}'.") pass def wait_and_deal_client_connect(self): while True: connection, client_address = self.sock.accept() data = connection.recv(1024) print(f"recv data from client '{client_address}': {data.decode()}") connection.sendall("hello client".encode()) def __del__(self): self.sock.close() if __name__ == "__main__": socket_server_obj = SocketServer() socket_server_obj.wait_and_deal_client_connect()
2.2 客戶端示例代碼
import socket class SocketClient: def __init__(self): pass def connect_to_server(self): # 常規tcp連接寫法 # server_address = ('127.0.0.1', 9999) # socket_family = socket.AF_INET # socket_type = socket.SOCK_STREAM # unix domain sockets 連接寫法 server_address = '/tmp/uds_socket' socket_family = socket.AF_UNIX socket_type = socket.SOCK_STREAM # 其他代碼完全一樣 sock = socket.socket(socket_family, socket_type) sock.connect(server_address) sock.sendall("hello server".encode()) data = sock.recv(1024) print(f"recv data from server '{server_address}': {data.decode()}") sock.close() if __name__ == "__main__": socket_client_obj = SocketClient() socket_client_obj.connect_to_server()
三、運行結果
3.1 運行步驟
第一步:啟動服務端
第二步:運行客戶端
第三步:再回頭看服務端輸出
可以看到客戶端與服務端成功通過UNIX domain sockets進行通信。
3.2 其他的佐證
上邊的截圖確實能說明服務端與客戶端能夠進行通信,為了更有說服力地說明使用的就是UNIX domain sockets本身,我們可以提供其他一些佐證。
使用netstat查看是監聽存在:
查看指定的/tmp/uds_socket存在,且是一個大小為0的文件:
四、關於進程通信的其他一些問題
4.1 關於UNIX domain sockets的說明
在Unix的發展史中,AT&T和BSD是兩大主力。在進程間通信方面兩者着重點各有不同。
AT&T保留傳統的Unix IPC寫法,着重對其實現細節加以打磨,形成了SysV IPC通信機制;而BSD則直接將機器內部進程間通信視為不同機器進程間通信的一個特例,將兩者在寫法上都統一為socket的形式。
由於socket在單機實現上與SysV IPC並沒有很大差別,而又與跨機器進程間通信的寫法相統一,所以socket成為了更常用的通信形式。
另外這里要注意,socket在單機實現上與SysV IPC並沒有很大差別,這就意味着socket單機實現上與socket跨機器實現上是有較大差別的,最直接的一點是由於是在單台機器上報文的傳遞並不需要復雜的TCP等協議去保證其順序性,所以從性能等方面講AF_UNIX會優於AF_INET。
4.2 為什么現在的框架監聽一堆端口
在上家公司測試基於spring boot開發的一套系統時,發現spring boot監聽了一堆的端口,當時問領導說進程間通信使用管道不就好了嗎為什么要監聽一堆端口呢,他說並不是現在大多數也都通過socket的方式進行通信。
現在想來他的說法也不夠清晰准確。進程間通信,舊的方法是管道、新的方法是UNIX domain sockets(AF_UNIX),spring boot等使用AF_INET這種成本更高的的形式一是為了方便地使用http二是為了方便分布式。
參考: