Scoket層

桃源氏發表於2024-03-20

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】問題引入

  • 有的同學在重啟服務端時可能會遇到

img

  • 這個是由於你的服務端仍然存在四次揮手的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

相關文章