Scoket層
- Scoket層在應用層和傳輸層之間
一、什麼是socket
- Socket是應用層與TCP/IP協議族通訊的中間軟體抽象層,它是一組介面
- 在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket介面後面
- 對使用者來說,一組簡單的介面就是全部,讓Socket去組織資料,以符合指定的協議。
二、套接字發展史及分類
- 套接字起源於 20 世紀 70 年代加利福尼亞大學伯克利分校版本的 Unix,即人們所說的 BSD Unix
- 因此,有時人們也把套接字稱為“伯克利套接字”或“BSD 套接字”
- 一開始,套接字被設計用在同 一臺主機上多個應用程式之間的通訊
- 這也被稱程序間通訊,或 IPC。套接字有兩種(或者稱為有兩個種族),分別是
- 基於檔案型的
- 基於網路型的。
[1]基於檔案型別的套接字家族
- 套接字家族的名字:
- AF_UNIX
- unix一切皆檔案
- 基於檔案的套接字呼叫的就是底層的檔案系統來取資料
- 兩個套接字程序執行在同一機器
- 可以透過訪問同一個檔案系統間接完成通訊
[2]基於網路型別的套接字家族**
- 套接字家族的名字:
- AF_INET
- (還有AF_INET6被用於ipv6,還有一些其他的地址家族,不過,他們要麼是隻用於某個平臺,要麼就是已經被廢棄,或者是很少被使用,或者是根本沒有實現
- 所有地址家族中,AF_INET是使用最廣泛的一個
- python支援很多種地址家族,但是由於我們只關心網路程式設計,所以大部分時候我麼只使用AF_INET)
三、套接字工作流程
[1]服務端流程
- 先從伺服器端說起。
- 伺服器端先初始化Socket
- 然後與埠繫結(bind),對埠進行監聽(listen)
- 呼叫accept阻塞,等待客戶端連線。
- 在這時如果有個客戶端初始化一個Socket
- 然後連線伺服器(connect)
- 如果連線成功,這時客戶端與伺服器端的連線就建立了。
- 客戶端傳送資料請求,伺服器端接收請求並處理請求
- 然後把回應資料傳送給客戶端,客戶端讀取資料
- 最後關閉連線,一次互動結束
[2]服務端套接字函式
- s.bind() 繫結(主機,埠號)到套接字
- s.listen() 開始TCP監聽
- s.accept() 被動接受TCP客戶的連線,(阻塞式)等待連線的到來
[3]客戶端套接字函式
- s.connect() 主動初始化TCP伺服器連線
- s.connect_ex() connect()函式的擴充套件版本,出錯時返回出錯碼,而不是丟擲異常
[4]公共用途的函式
- 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() 關閉套接字
[5]面向鎖的套接字方法
- s.setblocking() 設定套接字的阻塞與非阻塞模式
- s.settimeout() 設定阻塞套接字操作的超時時間
- s.gettimeout() 得到阻塞套接字操作的超時時間
[6]面向檔案的套接字函式
- s.fileno() 套接字的檔案描述符
- s.makefile() 建立一個與該套接字相關的檔案
四、基於TCP的套接字
[1]方法簡介
- tcp是基於連線的
- 必須先啟動服務端
- 然後再啟動客戶端去連結服務端
(1)TCP服務端
server = socket() #建立伺服器套接字
server.bind() #把地址繫結到套接字
server.listen() #監聽連結
inf_loop: #伺服器無限迴圈
conn = server.accept() #接受客戶端連結
comm_loop: #通訊迴圈
conn.recv()/conn.send() #對話(接收與傳送)
conn.close() #關閉客戶端套接字
server.close() #關閉伺服器套接字(可選)
(2)TCP客戶端
client = socket() # 建立客戶套接字
client.connect() # 嘗試連線伺服器
comm_loop: # 通訊迴圈
client.send()/client.recv() # 對話(傳送/接收)
client.close() # 關閉客戶套接字
[2]案例演示
- 服務端
import socket
IP = '127.0.0.1'
PORT = 8080
server = socket.socket()
server.bind((IP, PORT))
server.listen(5)
while True:
conn, addr = server.accept()
msg_from_client = conn.recv(1024)
msg_from_client = msg_from_client.decode('utf-8')
print(f'這是來自客戶端的資訊:{msg_from_client}')
while True:
msg_send_to_client = input('請輸入要傳送到客戶端的資訊:').strip()
if not len(msg_send_to_client):
continue
msg_send_to_client = msg_send_to_client.encode('utf-8')
conn.send(msg_send_to_client)
break
if msg_send_to_client.decode('utf-8') == 'q':
break
conn.close()
server.close()
- 客戶端
import socket
while True:
client = socket.socket()
IP = '127.0.0.1'
PORT = 8080
client.connect((IP, PORT))
while True:
msg_send_to_server = input(f'請輸入傳送發到伺服器端的資訊:').strip()
if not len(msg_send_to_server):
continue
msg_send_to_server = msg_send_to_server.encode('utf-8')
client.send(msg_send_to_server)
break
if msg_send_to_server.decode('utf-8') == 'q':
break
msg_from_server = client.recv(1024)
msg_from_server = msg_from_server.decode('utf-8')
if msg_from_server == 'q':
break
print(f'這是來自伺服器端的資訊:{msg_from_server}')
client.close()
- bug
- 當多個客戶端連線服務端時,如果服務端輸入‘q’想要斷開連線,只有正在與服務端互動的客戶端會收到‘q’並結束程序,其他的客戶端只能報錯,強制結束程序
五、基於UDP的套接字
- udp是無連結的,先啟動哪一端都不會報錯
[1]方法簡介
(1)UDP服務端
server = socket() #建立一個伺服器的套接字
server.bind() #繫結伺服器套接字
inf_loop: #伺服器無限迴圈
conn = server.recvfrom()/conn.sendto() # 對話(接收與傳送)
server.close() # 關閉伺服器套接字
(2)UDP客戶端
client = socket() # 建立客戶套接字
comm_loop: # 通訊迴圈
client.sendto()/client.recvfrom() # 對話(傳送/接收)
client.close() # 關閉客戶套接字
[2]案例演示
- 服務端
import socket
IP = '127.0.0.1'
PORT = 8080
server = socket.socket(type=socket.SOCK_DGRAM)
server.bind((IP, PORT))
msg_from_client, addr = server.recvfrom(1024)
msg_from_client = msg_from_client.decode('utf-8')
print(f'這是來自客戶端的資訊:{msg_from_client}')
print(addr)
while True:
msg_send_to_client = input('請輸入要傳送到客戶端的資訊:').strip()
if not len(msg_send_to_client):
continue
msg_send_to_client = msg_send_to_client.encode('utf-8')
server.sendto(msg_send_to_client, addr)
break
- 客戶端
import socket
IP = '127.0.0.1'
PORT = 8080
client = socket.socket(type=socket.SOCK_DGRAM)
msg_send_to_server = input('請輸入傳送到服務端的資訊:')
msg_send_to_server = msg_send_to_server.encode('utf-8')
client.sendto(msg_send_to_server, (IP, PORT))
msg_from_server, addr = client.recvfrom(1024)
msg_from_server = msg_from_server.decode('utf-8')
print(msg_from_server)
print(addr)
六、補充(轉載自)
【1】問題引入
- 有的同學在重啟服務端時可能會遇到
- 這個是由於你的服務端仍然存在四次揮手的time_wait狀態在佔用地址
【2】解決方法
(1)方法一
#加入一條socket配置,重用ip和埠
phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
phone.bind(('127.0.0.1',8080))
(2)方法二
- 發現系統存在大量TIME_WAIT狀態的連線,透過調整linux核心引數解決
vi /etc/sysctl.conf
- 編輯檔案,加入以下內容
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
-
引數說明
net.ipv4.tcp_syncookies = 1 # 表示開啟SYN Cookies。當出現SYN等待佇列溢位時,啟用cookies來處理,可防範少量SYN攻擊,預設為0,表示關閉; net.ipv4.tcp_tw_reuse = 1 # 表示開啟重用。允許將TIME-WAIT sockets重新用於新的TCP連線,預設為0,表示關閉; net.ipv4.tcp_tw_recycle = 1 # 表示開啟TCP連線中TIME-WAIT sockets的快速回收,預設為0,表示關閉。 net.ipv4.tcp_fin_timeout # 修改系統預設的 TIMEOUT 時間
-
然後執行 /sbin/sysctl -p 讓引數生效。
/sbin/sysctl -p