TCP通信原理與Socket編程

TCP通信原理與Socket編程

進程之間如何實現網絡通信

進程通信的概念最初來源於單機系統,由於每個進程都在自己的地址範圍內運行,爲了保證兩個相互通信的進程之間既互不干擾又能協調一致地工作,操作系統爲進程通信提供了相應的措施,如:

Unix BSD有管道(pipe)、命名管道(named pipe)、軟中斷信號(signal),Unix system V有消息(message)、共享存儲區(shared memory)和信號量(semaphore)等。

但是這些僅限於本機進程之間的通信,進程之間的網絡通信要解決不同主機進程之間相互通信的問題,爲此,首先要解決的是網絡中進程標識的問題。同一主機上,不同進程可以用進程號(process ID)唯一標識,但在網絡環境下,各主機獨立分配的進程號並不能唯一標識該進程。例如,主機A賦予某進程ID爲2363,主機B中也可能存在2363號進程,因此單靠進程ID也就失去了標識的意義。其次,操作系統支持的網絡協議衆多,不同協議的工作方式不同,地址格式也不同,因此,進程之間的網絡通信還要解決多重協議的識別問題。

其實TCP/IP協議族已經幫我們解決了這個問題,網絡層的IP地址可以唯一標識網絡中的主機,而傳輸層的協議+端口可以唯一標識主機中的應用程序(進程),這樣利用三元組(協議,IP地址,端口)就可以標識網絡的進程了。使用TCP/IP協議的應用程序通常採用Unix BSD的Socket來實現進程之間的網絡通信。

Socket通信過程

Socket起源於Unix,通常翻譯爲“套接字”,用於描述IP地址和端口,是一個通信鏈的句柄。Socket是在應用層和傳輸層(TCP/IP協議族)之間的一個軟件抽象層,它把TCP/IP層複雜的操作抽象成一組簡單的接口,供應用層調用,從而實現進程在網絡中通信。

socket抽象層在網絡中的位置:

在這裏插入圖片描述
TCP通信的標準流程一般是,服務器端先初始化Socket,然後與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端連接。在這時如果有個客戶端初始化一個Socket,然後主動連接服務器(connect),如果連接成功,這時客戶端與服務器端的連接就建立了。客戶端發送數據請求,服務器端接收請求並處理請求,然後把迴應數據發送給客戶端,客戶端讀取數據,最後關閉連接,一次交互結束。

Socket通信過程:

在這裏插入圖片描述

Socket服務端

開發步驟
  1. 創建服務端端套接字對象
  2. 綁定端口號
  3. 設置監聽
  4. 等待接受客戶端的連接請求
  5. 接收數據
  6. 發送數據
  7. 關閉套接字
import socket

if __name__ == '__main__':
    # 1.創建tcp服務端套接字對象
    # socket()函數的參數說明:
    # AF_INET:協議族參數,AF_INET決定了socket的地址類型爲ipv4地址(32位的)與端口號(16位的)的組合
    # SOCK_STREAM: 套接字類型參數,SOCK_STREAM決定了socket接受數據格式爲stream(流)
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 設置端口號複用,讓服務端程序退出後,端口號立即釋放
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) 
    # 2.給程序綁定端口號
    tcp_server_socket.bind(("", 8989))
    # 3.設置監聽
    # 128表示最大等待建立連接的個數
    # listen後的這個套接字只負責接收客戶端連接請求,不能收發消息,收發消息使用返回的這個新套接字來完成
    tcp_server_socket.listen(128)
    # 4.等待客戶端建立連接的請求, 只有客戶端和服務端建立連接成功代碼纔會解阻塞,代碼才能繼續往下執行
    # 專門和客戶端通信的套接字: service_client_socket
    # 客戶端的ip地址和端口號: ip_port
    service_client_socket, ip_port = tcp_server_socket.accept()
    # 代碼執行到此說明連接建立成功
    print("客戶端的ip地址和端口號:", ip_port)
    # 5.接收客戶端發送的數據, 這次接收數據的最大字節數是1024
    recv_data = service_client_socket.recv(1024)
    # 獲取數據的長度
    recv_data_length = len(recv_data)
    print("接收數據的長度爲:", recv_data_length)
    # 對二進制數據進行解碼
    recv_content = recv_data.decode("gbk")
    print("接收客戶端的數據爲:", recv_content)
    # 準備發送的數據
    send_data = "ok".encode("gbk")
    # 6.發送數據給客戶端
    service_client_socket.send(send_data)
    # 關閉服務與客戶端的套接字, 終止和客戶端通信的服務
    service_client_socket.close()
    # 7.關閉服務端的套接字, 終止和客戶端提供建立連接請求的服務
    tcp_server_socket.close()
執行結果
客戶端的ip地址和端口號: ('172.16.47.209', 52472)
接收數據的長度爲: 5
接收客戶端的數據爲: hello

Socket客戶端

開發步驟
  1. 創建客戶端套接字對象
  2. 和服務端套接字建立連接
  3. 發送數據
  4. 接收數據
  5. 關閉客戶端套接字
import socket

if __name__ == '__main__':
    # 1.創建tcp客戶端套接字對象
    tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 2.和服務端應用程序建立連接
    tcp_client_socket.connect(("192.168.131.62", 8080))
    # 代碼執行到此,說明連接建立成功
    # 準備發送的數據
    send_data = "hello".encode("gbk")
    # 3.發送數據
    tcp_client_socket.send(send_data)
    # 4.接收數據, 這次接收的數據最大字節數是1024
    recv_data = tcp_client_socket.recv(1024)
    # 返回的直接是服務端程序發送的二進制數據
    print(recv_data)
    # 對數據進行解碼
    recv_content = recv_data.decode("gbk")
    print("接收服務端的數據爲:", recv_content)
    # 5.關閉套接字
    tcp_client_socket.close()
執行結果
b'ok'
接收服務端的數據爲: ok

send和recv原理

當創建一個socket對象的時候會有一個發送緩衝區和一個接收緩衝區,這個發送和接收緩衝區指的就是內存分配的一片空間。

不管是send還是recv都不是直接發送數據到對方和接收到對方的數據,發送數據會寫入到發送緩衝區,接收數據是從接收緩衝區來讀取,發送數據和接收數據最終是由操作系統控制網卡來完成。

send和recv原理剖析圖:

在這裏插入圖片描述

TCP三次握手和四次揮手的Socket過程

在這裏插入圖片描述

TCP三次握手
  • 服務端調用socket()、bind()、listen()完成初始化後,調用accept()阻塞等待;
  • 客戶端Socket對象調用connect()向服務端發送了一個SYN報文並阻塞;
  • 服務端完成了第一次握手,即發送SYN+ACK報文應答;
  • 客戶端收到服務端發送的應答報文,從connect()返回,再發送一個ACK報文給服務器,完成第二次握手,該應答報文可攜帶客戶端到服務端的數據;
  • 服務端Socket對象接收客戶端第三次握手ACK確認,此時服務端從accept()返回,建立連接。
TCP四次揮手
  • 某個應用進程調用close()主動關閉,發送一個FIN;
  • 另一端接收到FIN後被動執行關閉,併發送ACK確認;
  • 之後被動執行關閉的應用進程調用close()關閉Socket,並也發送了一個FIN;
  • 接收到這個FIN的一端向另一端ACK確認。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章