1. 程式人生 > >Python3 從零單排23_socket套接字

Python3 從零單排23_socket套接字

  套接字這部分不得不強調下tcp協議,這裡只講五層協議。

    應用層 應用程式告訴作業系統,要給某ip port 傳送bytes資料
    傳輸層 建立埠到埠的通訊
    網路層 引入一套新的地址用來區分不同的廣播域/子網,這套地址即網路地址
    資料鏈路層 單純的電訊號0和1無意義,定義了電訊號的分組方式,將網路層傳過來的資料分組,一組電訊號構成一個數據包,叫做‘幀’每一資料幀分成:報頭head和資料data兩部分
    物理層 將鏈路層傳過來的資料用高低壓的電流傳輸

  tcp協議通訊,為何建立連結需要三次握手,而斷開連結卻需要四次揮手?
  A請求和B建立連結
    1.A傳送請求建立連結資訊給B,syn=1&seq=X
    2.B收到syn=1&seq=X,傳送確認建立連結資訊和請求建立連結資訊,syn=1&ack=X+1&seq=Y
    3.A接到syn=1&ack=X+1&seq=Y後,傳送確認建立連結資訊給B ack=Y+1&seq=Z
  A請求和B斷開連結


    1.A傳送請求斷開連結資訊給B,fin=1&seq=X
    2.B收到fin=1&seq=X,傳送確認斷開連結資訊ack=1&seq=X+1
    3.B傳送確認斷開連結資訊fin=1&seq=Y
    4.A接到fin=1&seq=Y後,傳送確認斷開連結資訊給B ack=Y+1&seq=Z
  建立連結和斷開連結的過程是一樣的,只是被建立連結方把第二步的確認建立連結和第三步傳送建立連結資訊合併了
  之所以斷開連結有四次,是因為B不確定什麼時候斷開連結,也許A在傳送斷開請求的時候,B還有資料正在傳送給A,此時可單方面斷開A到B,還不能斷開B到A。

  什麼是socket?
    socket:一組介面,作用於應用程式於傳輸層之間,等於是將傳輸層做了一次封裝,使用者只要和socket簡單互動,複雜的事情交給socket與傳輸層互動
    通訊流程:服務端:生成socket物件->繫結ip port->監聽客戶端連結->建立連結->收發資料
         客戶端:生成socket物件->連結ip port->建立連結->收發資料

  什麼是粘包?
    粘包:將多次傳送的資料粘到一起,致使接收方無法區分資料包
    原因:1.傳送方,有傳送演算法機制,會將短期內且資料包不大,會將多個包打成一個包進行傳送
       2.接收方,當一次接收位元組小於包的大小時,未接收完的資料會存留在系統記憶體中,下次有資料包過來時會粘在一起
    解決方法

:每次傳送資料包之前,告訴接收方此次傳送的資料包的大小,接收方根據包大小接收指定長度的資料

 

  接下來,我們來看下在python裡怎麼用socket模組

  流式協議(tcp):資料像水流一樣,源源不斷傳輸,傳送端和接收端都可以實時接收一定位元組大小的資料

  服務端程式碼

 1 import socket,subprocess,struct,json
 2 
 3 
 4 '''
 5 地址簇:
 6 socket.AF_INET:典型的TCP/IP四層模型的通訊過程,傳送方、接收方依賴IP:Port來標識,即將本地的socket繫結到對應的IP埠上;
 7 傳送資料時,指定對方的IP埠,經過Internet,可以根據此IP埠最終找到接收方;接收資料時,可以從資料包中獲取到傳送方的IP埠。
 8 socket.AF_UNIX:典型的本地IPC,類似於管道,依賴路徑名標識傳送方和接收方。即傳送資料時,指定接收方繫結的路徑名,
 9 作業系統根據該路徑名可以直接找到對應的接收方,並將原始資料直接拷貝到接收方的核心緩衝區中,並上報給接收方程序進行處理。
10 同樣的接收方可以從收到的資料包中獲取到傳送方的路徑名,並通過此路徑名向其傳送資料.(可實現程序間的通訊)
11 型別:
12 socket.SOCK_STREAM:流式socket , for TCP
13 socket.SOCK_DGRAM: 資料報式socket , for UDP
14 socket.SOCK_RAW:   原始套接字,普通的套接字無法處理ICMP、IGMP等網路報文,而SOCK_RAW可以;
15 SOCK_RAW也可以處理特殊的IPv4報文;此外,利用原始套接字,可以通過IP_HDRINCL套接字選項由使用者構造IP頭。
16 '''
17 ip_port= ("127.0.0.1",8081)
18 max_connect = 1  # 最對只接收5個連線,超過時直接拒絕
19 
20 # 例項化socket套接字物件,指定地址簇為socket.AF_INET;型別為socket.SOCK_STREAM 流式socket , for TCP
21 server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
22 # 埠複用,當程式重啟時埠還是被佔用的,加上這個可忽略埠衝突報錯
23 server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
24 server_socket.bind(ip_port)  # 繫結本機ip+port
25 server_socket.listen(max_connect)  # 開始TCP監聽,5代表在允許有一個連線排隊,更多的新連線連進來時就會被拒絕
26 print("starting....")
27 while True: # 迴圈生成連線物件,當前連線斷開馬上進入下一次等待,知道有連線
28     try:  # 當客戶端暴力斷開連線時,windows下會報錯,所以用try防止報錯導致程式奔潰
29         conn,client_addr = server_socket.accept()  # #阻塞直到有連線為止,有了一個新連線進來後,就會為這個請求生成一個連線物件
30         while True: # 迴圈和客戶端互動,收、發信息
31             rec = conn.recv(1024)  # 最多接收1024個位元組的資料
32             if not rec:break  # 當客戶端暴力斷開連線時,linux下會進入死迴圈接收資料為空,所以當接收資料為空的時候跳出迴圈
33 
34             # 執行客戶端傳送過來的shell命令,執行命令,並返回執行結果
35             res = subprocess.Popen(rec.decode("utf-8"), stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=True)
36             err = res.stderr.read()
37             out = res.stdout.read()
38             datas = err if err else out
39 
40             header = {"file_size":len(datas)} # 資料頭資訊,記錄這次需要傳送的檔案大小
41             header_bytes = bytes(json.dumps(header),encoding='utf-8') # 將資料頭資訊處理成bytes,便於傳送
42             header_len_bytes = struct.pack("i",len(header_bytes)) # 規定傳送4個位元組記錄資料頭的大小
43             # 傳送資料(send在待發送資料量大於己端快取區剩餘空間時,資料丟失,不會發完) 注:資料必須是bytes型別
44             conn.send(header_len_bytes)  # 傳送長度為4個位元組經過struct打包的資料頭長度資訊
45             conn.send(header_bytes)     # 傳送資料頭資訊,客戶端根據上面接收到的資料頭長度,接收指定長度的資料頭資訊
46             conn.send(datas)    #傳送正式資料,客戶端根據上面接收到資料頭資訊得到本次傳送的資料長度,接收指定長度資料
47 
48         conn.close()  # 關閉連線套接字
49     except Exception as e:
50         print(e)
51 
52 server_socket.close()  # 關閉套接字

 

  客戶端程式碼

 1 import socket,struct,json
 2 
 3 
 4 server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
 5 server_socket.connect(("127.0.0.1",8081))  # 主動初始化TCP伺服器連線
 6 while True:
 7     msg = input(">>>>:").strip()
 8     if not msg: continue
 9     msg_bytes = msg.encode()
10     server_socket.send(msg_bytes) # 傳送的資料必須是bytes型別
11 
12     header_len_bytes = server_socket.recv(4) # 接收4個位元組的資料頭資訊
13     header_len = struct.unpack("i",header_len_bytes)[0] # struct.unpack解壓資料,得到資料頭資訊長度
14     header_str = server_socket.recv(header_len).decode("utf-8") # 根據上面的長度接收資料頭資訊
15     header = json.loads(header_str,encoding="utf-8")
16     file_size = header["file_size"] # 根據資料頭資訊得到本次要接收的資料大小
17     recv_size = 0
18     res = b''
19     while recv_size < file_size:  # 當接收到的資料小於本次資料長度時就一直接收
20         res += server_socket.recv(10) # 將每次接收到的資料拼接
21         recv_size = len(res) # 實時記錄當前接收到的資料長度
22     res = res.decode("GBK") # 這裡是windows,搜尋一GBK格式列印返回資料
23     print(res)
24 
25 server_socket.close()

 

  

  基於UDP協議的socket程式設計 

    資料包協議(upd):一次傳送就是一個數據包,不存在源源不斷的傳送,傳送端和接收端都只能一個個包的接收,不能接收指定長度大小的資料 

  服務端

# udp協議不需要建立連結,直接傳送和接收,接收方的地址放在傳送函式的第二個位置引數上
# 缺點  不可靠,沒有確保資料一定送達,它只做了一件事,傳送資料,對方有沒有收到,都不在傳送。
# 優點  1.因為不需要建連結和確認資料收到,所以傳送速度更快  2.不存在粘包現象
# 應用  QQ的訊息傳送就是用的udp,一般用於資料查詢


import socket


ip = "127.0.0.1"
port = 8083
ip_port = (ip, port)
soctet_server = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
soctet_server.bind(ip_port)
print("starting....")
msg, addr = soctet_server.recvfrom(1024)
soctet_server.sendto(msg.upper(), addr)
print(msg.decode("utf-8"))

  客戶端

 1 import socket
 2 
 3 
 4 ip = "127.0.0.1"
 5 port = 8083
 6 ip_port = (ip, port)
 7 socket_client = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
 8 socket_client.sendto(b"hello", ip_port)
 9 msg, addr = socket_client.recvfrom(1024)
10 print(msg.decode("utf-8"), addr)