25. Socket與粘包問題

hbutmeng發表於2024-08-27

1. Socket概念

Socket允許應用程式透過它傳送或接收資料,對其進行像對檔案一樣的開啟、讀寫和關閉等操作,從而允許應用程式將I/O插入到網路中,並與網路中的其他應用程式進行通訊。Socket是應用層與傳輸層之間的介面,提供了一種標準的通訊方式,使得不同的程式能夠在網路上進行資料交換。

Socket是應用層與TCP/IP協議族通訊的中間軟體抽象層,它是一組介面。
在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket介面後面
對使用者來說,一組簡單的介面就是全部,讓Socket去組織資料,以符合指定的協議。
所以,我們無需深入理解tcp/udp協議,socket已經為我們封裝好了,我們只需要遵循socket的規定去程式設計,寫出的程式自然就是遵循tcp/udp標準的。
也有人將socket說成ip+port
ip是用來標識網際網路中的一臺主機的位置
而port是用來標識這臺機器上的一個應用程式
ip地址是配置到網路卡上的
而port是應用程式開啟的
ip與port的繫結就標識了網際網路中獨一無二的一個應用程式
而程式的pid是同一臺機器上不同程序或者執行緒的標識

本機中的不同程式之間互動 藉助 SOCKET

2. 套接字

2.1 套接字起源

套接字起源於 20 世紀 70 年代加利福尼亞大學伯克利分校版本的 Unix,即人們所說的 BSD Unix。
因此,有時人們也把套接字稱為“伯克利套接字”或“BSD 套接字” 一開始,套接字被設計用在同 一臺主機上多個應用程式之間的通訊,這也被稱程序間通訊或 IPC。

套接字有兩種:基於檔案型的、基於網路型的

2.2 基於檔案型的套接字

名稱:AF_UNIX

unix一切皆檔案,基於檔案的套接字呼叫的就是底層的檔案系統來取資料,兩個套接字程序執行在同一機器,可以透過訪問同一個檔案系統間接完成通訊

2.3 基於網路型的套接字

名稱: AF_INET

(還有AF_INET6被用於ipv6,還有一些其他的地址族,AF_INET是使用最廣泛的一個)
python支援很多種地址族,網路程式設計大部分只使用AF_INET

2.4 套接字工作流程

以打電話模型來說明

(1)服務端---接電話

1.擁有一臺手機

2.插上手機卡

3.開機等待別人打電話進來

4.接聽來電

5.聽到對方說話資訊

6.給對方回話

7.結束通話電話

8.關機

(2)客戶端---打電話

1.擁有一臺手機

2.插上手機卡

3.獲取對方手機號,撥打電話

4.向對方說話

5.聽到對方回話

6.結束通話電話

7.關機

2.5 TCP套接字模型一

打電話模型

# (1)服務端---接電話
import socket

# 1.擁有一臺手機    family:使用的是基於網路的套接字族  type:流式套接字  proto是一個預設引數為-1,當預設-1時該值為0
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0)
# 2.插上手機卡
addr = '127.0.0.1'
port = 9000
server.bind((addr, port))  # 繫結(主機,埠號)到套接字
# 3.開機等別人打電話進來,括號內為空預設5個
server.listen(5)
# 4.別人打電話進來,接聽電話
client_socket, client_addr = server.accept()
print(client_socket)
# <socket.socket fd=404, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9000), raddr=('127.0.0.1', 8307)>
print(client_addr)
# ('127.0.0.1', 8307)
# 5.接收對方的資訊
info_from_client = client_socket.recv(1024)  # 一次接收多少位元組的資料
print(f'對方資訊內容為:{info_from_client.decode("utf-8")}')  # 傳輸的資料格式為二進位制,解碼後再展示
# 對方資訊內容為:你好,這裡是客戶端
# 6.給對方回資訊
info_to_client = f'你好,這裡是服務端'
client_socket.send(info_to_client.encode("utf-8"))  # 編碼後再傳輸
# 7.結束通話電話
client_socket.close()
# 8.關機
server.close()
# (2)客戶端---打電話
import socket

# 1.擁有一臺手機
client = socket.socket()  # 括號裡面不填,預設為基於網路套接字模型、tcp協議
# 2.插上手機卡
addr = '127.0.0.1'
port = 9001
# 3.獲取對方手機號並撥打
client.connect(('127.0.0.1', 9000))
# 4.向對方說話
info_to_server = f'你好,這裡是客戶端'
client.send(info_to_server.encode())  # 編碼後才能傳輸
# 5.聽到對方回話
info_from_server = client.recv(1024)  # 一次接收1024位元組的資料
print(f'對方回覆內容為:{info_from_server.decode()}')  # 解碼
# 對方回覆內容為:你好,這裡是服務端
# 6.結束通話電話  7.關機
client.close()

模板

# (1)服務端
import socket

server = socket.socket()  # 建立服務端物件
server.bind(('127.0.0.1', 9000))  # 繫結ip與埠
server.listen(5)  # 監聽客戶端的連線
conn, addr = server.accept()  # 接收到客戶端的連線
recv_info = conn.recv(1024)  # 接收客戶端的二進位制資料
send_info = ''  # 向客戶端傳送資料
conn.send(send_info.encode('utf-8'))
conn.close()  # 斷開客戶端連線
server.close()  # 關閉服務端
# (2)客戶端
import socket

client = socket.socket()  # 建立客戶端物件
client.connect(('127.0.0.1', 9000))  # 連線服務端ip和埠,在這個例子中不寫客戶端ip和埠也行,只要有對方的就能進行通訊
send_info = ''  # 傳送資料
client.send(send_info.encode())
recv_info = client.recv(1024)  # 接收服務端返回的資訊
client.close()  # 關閉客戶端

2.6 套接字函式

(1)服務端套接字函式
s.bind() 繫結(主機,埠號)到套接字
s.listen() 開始TCP監聽
s.accept() 被動接受TCP客戶的連線,(阻塞式)等待連線的到來
(2)客戶端套接字函式
s.connect() 主動初始化TCP伺服器連線
s.connect_ex() connect()函式的擴充套件版本,出錯時返回出錯碼,而不是丟擲異常
(3)公共用途的套接字函式
s.recv() 接收TCP資料
s.send() 傳送TCP資料(send在待傳送資料量大於己端快取區剩餘空間時,資料丟失,不會發完)
s.sendall() 傳送完整的TCP資料(本質就是迴圈呼叫send,sendall在待傳送資料量大於己端快取區剩餘空間時,資料不丟失,迴圈呼叫send直到發完)
s.recvfrom() 接收UDP資料
s.sendto() 傳送UDP資料
s.getpeername() 連線到當前套接字的遠端的地址
s.getsockname() 當前套接字的地址
s.getsockopt() 返回指定套接字的引數
s.setsockopt() 設定指定套接字的引數
s.close() 關閉套接字

2.7 TCP套接字模型二(通訊迴圈)

# (1)服務端
import socket

server = socket.socket()
server.bind(('127.0.0.1', 9000))
server.listen(5)
while True:
    conn, addr = server.accept()  # 用來獲取不同的客戶端連線物件
    while True:  # 持續的和當前連線好的客戶端進行互動
        recv_info = conn.recv(1024)  # 接收客戶端的二進位制資料
        print(f'對方資訊內容為:{recv_info.decode()}')
        send_info = recv_info.decode().upper()  # 收到客戶端的小寫字母后向客戶端返回大寫字母
        conn.send(send_info.encode())
# (2)客戶端
import socket

client = socket.socket()
client.connect(('127.0.0.1', 9000))  # 連線服務端ip和埠,在這個例子中不寫客戶端ip和埠也行,只要有對方的就能進行通訊
while True:
    send_info = input('請輸入給服務端的資料:')
    client.send(send_info.encode())
    recv_info = client.recv(1024)
    print(f'對方回覆資訊內容為:{recv_info.decode()}')

相關文章