1. 程式人生 > >我的Python成長之路--Day31-基於TCP和UDP的套接字程式設計

我的Python成長之路--Day31-基於TCP和UDP的套接字程式設計

在介紹這兩種套接字程式設計之前,我們需要掌握iso七層協議中傳輸層中的和TCP有關的知識點,

三次握手和四次揮手:

首先傳輸層的由來: 網路層的ip幫我們區分子網,乙太網層的mac幫我們找到主機,然後大家使用的都是應用程式,你的電腦上可能同時開啟QQ、暴風影音等多個應用程式,那麼我們通過ip和mac找到了一臺特定的主機,如何表示這臺主機上的應用程式,答案就是埠,埠就是應用程式與網絡卡關聯的編號.

傳輸層的功能: 建立埠到埠的通訊                        埠的範圍是0-65535,其中0-1023位系統佔用埠 TCP協議: 也成為可靠傳輸/好人協議/流式協議,TCP資料報沒有長度限制,理論上可以無限長,但是為了保證網路的效率,通常TCP資料報的長度不會超過IP資料報的長度,以確保單個TCP資料包不必再分割 瞭解:之所以稱TCP協議為可靠協議或者好人協議,是因為無論是誰發過來的連線請求,它都會進行相應並與其連線

TCP資料報
乙太網頭 ip頭 tcp頭 資料

UDP協議: 不可靠傳輸,"報頭"部分一共只有8個位元組,總長度不超過65535位元組,正好放進一個ip資料包

UDP資料報
乙太網頭 ip頭 udp頭 資料

大概瞭解了這兩種協議之後,下面我們來學習TCP的三次握手和四次揮手,首先來看一張比較專業的三次握手和四次揮手的流程圖: 下面我們來分析一下這張圖:

首先分析三次握手部分: 在客戶端和服務端進行三次握手建立連線的時候,首先由客戶端發出一條連線請求(對應socket中的connet請求)給伺服器端,內容是(syn和一個序列seq=x(序列本來時一堆數字,為了方便在這裡將他們賦值給x))

,出去監聽狀態(對應socket中的listen()操作)的伺服器收到這條資訊之後,會給客戶端一個相應內容(ACK=x+1),+1表示伺服器端收到了客戶端的連線請求,因為TCP建立的連線是雙向的,這時候伺服器端會向客戶端傳送一個連線請求,同理客戶端收到伺服器端的連線請求之後,也會給伺服器端返回一個數據,內容為(ACK=y+1),到這裡基於TCP的雙向連線就建立成功了,但是我們發現這裡不是進行了四次握手麼,怎麼說是三次呢,其實在伺服器端收到客戶端的連線請求和服務端給客戶端傳送連線請求這兩次操作並沒有衝突,所以將他們合併成一步完成,到這裡就是完整的TCP的三次握手的內容了.

接下來分析一下四次揮手

: 三次握手是進行建立連線的,那麼四次揮手就是斷開連線的過程,和三次握手的過程相似,無論是哪一方先發起斷開連線的請求(一般都是有伺服器端先發起斷開連線的請求),這四次過程都要走完,並且中間的兩個過程不能合併為一步傳送,因為在斷開連線的時候,不能確定資料是否傳送完成,在資料沒有傳送完成的時候如果斷開連線就會造成丟包的現象,導致資料不完整.

下面我們來介紹基於TCP和UDP的兩種套接字的具體內容:

1.基於TCP的套接字

TCP是基於連結的,必須先啟動服務端,然後再啟動客戶端去連結服務端使用TCP的時候,原理上還是要完成三次握手成功建立連線之後才能進行下一步的資料收發和其他操作,在上一篇部落格中我們已經詳細介紹了使用TCP的時候,每一個步驟代表的具體意思和要注意的事項,在這裡就不在過多贅述,我們在這裡主要介紹一下,在能夠實現通訊和收發資料的基礎上增加新的功能,上篇部落格中的服務端和客戶端連線成功之後,都是隻能進行一次資料收發之後就會關閉,我們在這裡進行優化,在原來的基礎上加上通訊迴圈和連線迴圈.

加上通訊迴圈: 使得客戶端和服務端可以無限次的進行資料的傳送和接收,直到服務端收到客戶端的通訊完畢的訊號或者標誌之後,與當前客戶端斷開連線

加上連線迴圈: 在加過通訊迴圈之後,我們能實現的是客戶端連線上服務端之後可以進行多次的資料收發,但是當連線斷開之後,伺服器就會停止,不能繼續和下一個客戶端進行連線,所以我們要加上連線迴圈來進行優化,使得伺服器在與一個客戶端完成資料互動斷開之後,還可以繼續等待連線下一個客戶端.TCP服務端

import socket

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 建立伺服器套接字
phone.bind(('127.0.0.1', 8081))     # 繫結地址到套接字
phone.listen(5)                     # 監聽連線

print('start...')
while True:            # 實現伺服器連線迴圈
    conn,client_addr=phone.accept() # 接受客戶端連線
    print('客戶端 ',client_addr)

    while True:       # 實現通訊迴圈  
        try:
            msg=conn.recv(1024)     # 對話(接受與傳送)
            print('客戶端的訊息: ',msg)
            conn.send(msg+b'SB')
        except ConnectionResetError:
            break
    conn.close()                     # 關閉客戶端套接字
phone.close()  # 關閉伺服器套接字(這個是可選的)

TCP客戶端:

import socket

phone=socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 建立客戶端套接字
phone.connect(('127.0.0.1', 8081)) # 配置地址為服務端的ip和埠  嘗試建立連線

while True:        # 實現通訊迴圈 
    msg=input('>>>: ').strip()      # 輸入要傳送的訊息
    phone.send(msg.encode('utf-8')) # 發訊息b'hello'
    data=phone.recv(1024)           # 收訊息

    print(data.decode('utf-8'))     # 對收到的訊息進行解碼

phone.close()  # 關閉客戶套接字

使用的過程中容易出現的問題: 1.在重啟伺服器端的時候會遇到 這個是由於你的伺服器任然存在四次揮手的time_wait狀態在佔用地址(如果不懂,請深入研究1.tcp三次握手,四次揮手,2,syn洪水攻擊,3.伺服器高併發情況下會有大量的time_wait狀態的優化方法)

這種問題的解決辦法: 方法一: 加入一條socket配置,重用ip和埠

phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
phone.bind(('127.0.0.1',8080))

方法二:發現系統存在大量的time_wait狀態的連線,通過調整核心引數解決

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
 
然後執行 /sbin/sysctl -p 讓引數生效。
 
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 時間

2.基於UDP的套接字

UDP是無連線的,不像TCP一樣有啟動順序,它先啟動那一端都不會報錯

UDP服務端:

1 ss = socket()   #建立一個伺服器的套接字
2 ss.bind()       #繫結伺服器套接字
3 inf_loop:       #伺服器無限迴圈
4     cs = ss.recvfrom()/ss.sendto() # 對話(接收與傳送)
5 ss.close()                         # 關閉伺服器套接字

UDP客戶端:

cs = socket()   # 建立客戶套接字
comm_loop:      # 通訊迴圈
    cs.sendto()/cs.recvfrom()   # 對話(傳送/接收)
cs.close()                      # 關閉客戶套接字

下面實現一個簡單的UDP套接字例項:UDP服務端配置:

#_*_coding:utf-8_*_

import socket
ip_port=('127.0.0.1',9000)
BUFSIZE=1024
udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

udp_server_client.bind(ip_port)

while True:
    msg,addr=udp_server_client.recvfrom(BUFSIZE)
    print(msg,addr)

    udp_server_client.sendto(msg.upper(),addr)

udp服務端

UDP客戶端配置:  

#_*_coding:utf-8_*_

import socket
ip_port=('127.0.0.1',9000)
BUFSIZE=1024
udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

while True:
    msg=input('>>: ').strip()
    if not msg:continue

    udp_server_client.sendto(msg.encode('utf-8'),ip_port)

    back_msg,addr=udp_server_client.recvfrom(BUFSIZE)
    print(back_msg.decode('utf-8'),addr)

udp客戶端