異常處理與網絡編程(八)

1.1 異常處理

1.1.1 什麼是異常?

異常是錯誤發生的信號,一旦程序出錯,就會產生一個異常,應用程序未處理該異常,異常便會拋出,程序隨之終止python

 

1.1.2 常見異常類型

1.1.2.1  語法錯誤應該在程序運行前修正

if 1 >2

    print('xxxxx')

 

這種錯誤,根本過不了python解釋器的語法檢測,必須在程序執行前就改正linux

1.1.2.2  邏輯錯誤

x  #NameError

 

l=[]

l[10000] #IndexError

 

class Foo:

    pass

Foo.x #AttributeError:

 

k={'x':1}

k['y'] #KeyError

 

1/0 #ZeroDivisionError 沒法完成計算

 

for i in 3: #TypeError: int類型不可迭代

    pass

 

age=input('>>: ') #ValueError

age=int(age)

 

1.1.3 如何處理異常

爲了保證程序的健壯性與容錯性,即在遇到錯誤時程序不會崩潰,咱們須要對異常進行處理,若是錯誤發生的條件是可預知的,咱們須要用if進行處理:在錯誤發生以前進行預防算法

若是錯誤發生的條件是不可預知的,則須要用到try...except:在錯誤發生以後進行處理shell

print('====>start<=====')

 

try:

    l=[]

    print(l[1111])

    print('====>1')

    print('====>2')

    print('====>3')

except IndexError:

    pass

 

print('====>end<=======')

 

1.1.3.1  異常類只能用來處理指定的異常狀況,若是非指定異常則沒法處理。

print('====>start<=====')

try:

    l=[]

    print(l[1111])

    print('====>1')

    print('====>2')

    print('====>3')

except IndexError as e: # 未捕獲到異常,程序直接報錯

    print('===>',e)

 

print('====>end<=======')

 

1.1.3.2  多分支

print('====>start<=====')

try:

    l=[]

    # print(l[1111])

    print('====>1')

    d={}

    d['k']

    print('====>2')

    print('====>3')

except IndexError as e:

    print('===>',e)

except KeyError as e:

    print('----',e)

 

print('====>end<=======')

  

1.1.3.3  萬能異常Exception

print('====>start<=====')

try:

    l=[]

    # print(l[1111])

    print('====>1')

    d={}

    d['k']

    print('====>2')

    print('====>3')

except IndexError:

    pass

except KeyError:

    pass

except Exception as e:

    print('萬能異常--->',e)

 

print('====>end<=======')

 

1.1.3.4  多分支異常與萬能異常

若是你想要的效果是,不管出現什麼異常,咱們統一丟棄,或者使用同一段代碼邏輯去處理他們,那麼騷年,大膽的去作吧,只有一個Exception就足夠了。json

若是你想要的效果是,對於不一樣的異常咱們須要定製不一樣的處理邏輯,那就須要用到多分支了。瀏覽器

 

1.1.3.5  也能夠在多分支後來一個Exception

print('====>start<=====')

try:

    l=[]

    print(l[1111])

    # print('====>1')

    d={}

    # d['k']

    # print('====>2')

    # print('====>3')

except IndexError:

    pass

except KeyError:

    pass

except Exception as e:

    print('萬能異常--->',e)

else:

    print('沒有異常發生的時候觸發')

finally:

    print('有沒有異常都觸發')

 

 

print('====>end<=======')

 

1.1.4 自定義異常

'''

try:

    conn=connect('1.1.1.1',3306)

    conn.execute('select * from db1.t1')

finally:

    conn.close()

'''

stus=['egon','alex','wxxx']

ip_list=[

    # '1.1.1.1:8080',

    # '1.1.1.2:8081',

    # '1.1.1.3:8082',

]

 

if len(ip_list) == 0:

    raise TypeError

assert len(ip_list) > 0

 

print('從ip_list取出ip地址,驗證可用性')

 

class MyException(BaseException):

    def __init__(self,msg):

        super(MyException,self).__init__()

        self.msg=msg

 

    def __str__(self):

        return '<%s>' %self.msg

 

raise MyException('類型錯誤') #異常的值:print(obj)

 

1.2 基於TCP協議實現簡單的套接字協議

1.2.1 客戶端/服務器架構

1.2.1.1  硬件C/S架構(打印機)

1.2.1.2  軟件C/S架構

  互聯網中到處是C/S架構緩存

  如網站是服務端,你的瀏覽器是客戶端(B/S架構也是C/S架構的一種)服務器

  騰訊做爲服務端爲你提供視頻,你得下個騰訊視頻客戶端才能看它的視頻)cookie

1.2.1.3  C/S架構與socket的關係:

咱們學習socket就是爲了完成C/S架構的開發網絡

1.2.2 osi七層

   一個完整的計算機系統是由硬件、操做系統、應用軟件三者組成,具有了這三個條件,一臺計算機系統就能夠本身跟本身玩了(打個單機遊戲,玩個掃雷啥的),若是你要跟別人一塊兒玩,那你就須要上網了,什麼是互聯網?

  互聯網的核心就是由一堆協議組成,協議就是標準,好比全世界人通訊的標準是英語,若是把計算機比做人,互聯網協議就是計算機界的英語。全部的計算機都學會了互聯網協議,那全部的計算機都就能夠按照統一的標準去收發信息從而完成通訊了。

  人們按照分工不一樣把互聯網協議從邏輯上劃分了層級,

  互聯網協議按照功能不一樣分爲osi七層或tcp/ip五層或tcp/ip四層

 

 

圖1-1  

每層運行常見物理設備

 

 

圖1-2  

 

1.2.3 tcp/ip五層模型

咱們將應用層,表示層,會話層並做應用層,從tcp/ip五層協議的角度來闡述每層的由來與功能,搞清楚了每層的主要協議,就理解了整個互聯網通訊的原理。

1.2.3.1  物理層

物理層由來:

上面提到,孤立的計算機之間要想一塊兒玩,就必須接入internet,言外之意就是計算機之間必須完成組網

物理層功能:

主要是基於電器特性發送高低電壓(電信號),高電壓對應數字1,低電壓對應數字0

1.2.3.2  數據鏈路層

數據鏈路層由來:

單純的電信號0和1沒有任何意義,必須規定電信號多少位一組,每組什麼意思

數據鏈路層的功能:

定義了電信號的分組方式

  早期的時候各個公司都有本身的分組方式,後來造成了統一的標準,即以太網協議ethernet,ethernet規定:

一組電信號構成一個數據包,叫作‘幀’,每一數據幀分紅:報頭head和數據data兩部分。

head包含:(固定18個字節),發送者/源地址,6個字節,數據類型,6個字節。

data包含:(最短46字節,最長1500字節),數據包的具體內容

head長度+data長度=最短64字節,最長1518字節,超過最大限制就分片發送

mac地址:

head中包含的源和目標地址由來:ethernet規定接入internet的設備都必須具有網卡,發送端和接收端的地址即是指網卡的地址,即mac地址,mac地址:每塊網卡出廠時都被燒製上一個世界惟一的mac地址,長度爲48位2進制,一般由12位16進制數表示(前六位是廠商編號,後六位是流水線號)

廣播:

有了mac地址,同一網絡內的兩臺主機就能夠通訊了(一臺主機經過arp協議獲取另一臺主機的mac地址),ethernet採用最原始的方式,廣播的方式進行通訊,即計算機通訊基本靠吼

1.2.3.3  網絡層

網絡層由來:

有了ethernet、mac地址、廣播的發送方式,世界上的計算機就能夠彼此通訊了,問題是世界範圍的互聯網是由

一個個彼此隔離的小的局域網組成的,那麼若是全部的通訊都採用以太網的廣播方式,那麼一臺機器發送的包全世界都會收到,這就不只僅是效率低的問題了,這會是一種災難

必須找出一種方法來區分哪些計算機屬於同一廣播域,哪些不是,若是是就採用廣播的方式發送,若是不是,就採用路由的方式(向不一樣廣播域/子網分發數據包),mac地址是沒法區分的,它只跟廠商有關

網絡層功能:

引入一套新的地址用來區分不一樣的廣播域/子網,這套地址即網絡地址

IP協議:

規定網絡地址的協議叫ip協議,它定義的地址稱之爲ip地址,普遍採用的v4版本即ipv4,它規定網絡地址由32位2進製表示,範圍0.0.0.0-255.255.255.255,一個ip地址一般寫成四段十進制數,例:172.16.10.1

ip地址分紅兩部分

  網絡部分:標識子網

  主機部分:標識主機

注意:單純的ip地址段只是標識了ip地址的種類,從網絡部分或主機部分都沒法辨識一個ip所處的子網,例:172.16.10.1與172.16.10.2並不能肯定兩者處於同一子網

子網掩碼:

所謂」子網掩碼」,就是表示子網絡特徵的一個參數。它在形式上等同於IP地址,也是一個32位二進制數字,它的網絡部分所有爲1,主機部分所有爲0。好比,IP地址172.16.10.1,若是已知網絡部分是前24位,主機部分是後8位,那麼子網絡掩碼就是11111111.11111111.11111111.00000000,寫成十進制就是255.255.255.0。

知道」子網掩碼」,咱們就能判斷,任意兩個IP地址是否處在同一個子網絡。方法是將兩個IP地址與子網掩碼分別進行AND運算(兩個數位都爲1,運算結果爲1,不然爲0),而後比較結果是否相同,若是是的話,就代表它們在同一個子網絡中,不然就不是。

好比,已知IP地址172.16.10.1和172.16.10.2的子網掩碼都是255.255.255.0,請問它們是否在同一個子網絡?二者與子網掩碼分別進行AND運算,

172.16.10.1:10101100.00010000.00001010.000000001

255255.255.255.0:11111111.11111111.11111111.00000000

AND運算得網絡地址結果:10101100.00010000.00001010.000000001->172.16.10.0

 

172.16.10.2:10101100.00010000.00001010.000000010

255255.255.255.0:11111111.11111111.11111111.00000000

AND運算得網絡地址結果:10101100.00010000.00001010.000000001->172.16.10.0

結果都是172.16.10.0,所以它們在同一個子網絡。

總結一下,IP協議的做用主要有兩個,一個是爲每一臺計算機分配IP地址,另外一個是肯定哪些地址在同一個子網絡。

ip數據包:

ip數據包也分爲head和data部分,無須爲ip包定義單獨的欄位,直接放入以太網包的data部分:

head:長度爲20到60字節

data:最長爲65,515字節。

而以太網數據包的」數據」部分,最長只有1500字節。所以,若是IP數據包超過了1500字節,它就須要分割成幾個以太網數據包,分開發送了。

ARP協議

arp協議由來:計算機通訊基本靠吼,即廣播的方式,全部上層的包到最後都要封裝上以太網頭,而後經過以太網協議發送,在談及以太網協議時候,我門瞭解到

通訊是基於mac的廣播方式實現,計算機在發包時,獲取自身的mac是容易的,如何獲取目標主機的mac,就須要經過arp協議

arp協議功能:廣播的方式發送數據包,獲取目標主機的mac地址

協議工做方式:每臺主機ip都是已知的,首先經過ip地址和子網掩碼區分出本身所處的子網,目標主機ip同一子網,目標主機mac,目標主機ip,若是不是同一網絡,那麼目標ip經過arp獲取的是網關的mac,這個包會以廣播的方式在發送端所處的自網內傳輸,全部主機接收後拆開包,發現目標ip爲本身的,就響應,返回本身的mac

1.2.3.4  傳輸層

傳輸層的由來:

網絡層的ip幫咱們區分子網,以太網層的mac幫咱們找到主機,而後你們使用的都是應用程序,你的電腦上可能同時開啓qq,暴風影音,等多個應用程序,那麼咱們經過ip和mac找到了一臺特定的主機,如何標識這臺主機上的應用程序,答案就是端口,端口即應用程序與網卡關聯的編號。

傳輸層功能:創建端口到端口的通訊

補充:端口範圍0-65535,0-1023爲系統佔用端口

tcp協議:

可靠傳輸,TCP數據包沒有長度限制,理論上能夠無限長,可是爲了保證網絡的效率,一般TCP數據包的長度不會超過IP數據包的長度,以確保單個TCP數據包沒必要再分割。

udp協議:

不可靠傳輸,」報頭」部分一共只有8個字節,總長度不超過65,535字節,正好放進一個IP數據包。

tcp報文

 

 

圖1-3  

tcp三次握手和四次揮手

 

 

圖1-4  

 

1.2.3.5  應用層:

應用層由來:用戶使用的都是應用程序,均工做於應用層,互聯網是開發的,你們均可以開發本身的應用程序,數據多種多樣,必須規定好數據的組織形式

應用層功能:規定應用程序的數據格式。

TCP協議能夠爲各類各樣的程序傳遞數據,好比Email、WWW、FTP等等。那麼,必須有不一樣協議規定電子郵件、網頁、FTP數據的格式,這些應用程序協議就構成了」應用層」。

 

 

圖1-5  

1.2.4 socket

咱們知道兩個進程若是須要進行通信最基本的一個前提能可以惟一的標示一個進程,在本地進程通信中咱們可使用PID來惟一標示一個進程,但PID只在本地惟一,網絡中的兩個進程PID衝突概率很大,這時候咱們須要另闢它徑了,咱們知道IP層的ip地址能夠惟一標示主機,而TCP層協議和端口號能夠惟一標示主機的一個進程,這樣咱們能夠利用ip地址+協議+端口號惟一標示網絡中的一個進程。

可以惟一標示網絡中的進程後,它們就能夠利用socket進行通訊了,什麼是socket呢?咱們常常把socket翻譯爲套接字,socket是在應用層和傳輸層之間的一個抽象層,它把TCP/IP層複雜的操做抽象爲幾個簡單的接口供應用層調用已實現進程在網絡中通訊。

 

圖1-6  

socket起源於UNIX,在Unix一切皆文件哲學的思想下,socket是一種"打開—讀/寫—關閉"模式的實現,服務器和客戶端各自維護一個"文件",在創建鏈接打開後,能夠向本身文件寫入內容供對方讀取或者讀取對方內容,通信結束時關閉文件。

1.2.4.1  服務端套接字函數

s.bind()    綁定(主機,端口號)到套接字

s.listen()  開始TCP監聽

s.accept()  被動接受TCP客戶的鏈接,(阻塞式)等待鏈接的到來

 

1.2.4.2  客戶端套接字函數

s.connect()     主動初始化TCP服務器鏈接

s.connect_ex()  connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常

 

1.2.4.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()           關閉套接字

用打電話的流程快速描述socket通訊:

1.2.4.4  基於TCP的套接字

tcp服務端:

ss = socket() #建立服務器套接字

ss.bind()      #把地址綁定到套接字

ss.listen()      #監聽連接

inf_loop:      #服務器無限循環

    cs = ss.accept() #接受客戶端連接

    comm_loop:         #通信循環

        cs.recv()/cs.send() #對話(接收與發送)

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

ss.close()        #關閉服務器套接字(可選)

 

tcp客戶端:

cs = socket()    # 建立客戶套接字

cs.connect()    # 嘗試鏈接服務器

comm_loop:        # 通信循環

     cs.send()/cs.recv()    # 對話(發送/接收)

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

 

socket通訊流程與打電話流程相似,咱們就以打電話爲例來實現一個low版的套接字通訊

1.2.5 客戶端:

import socket

 

#一、買手機

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp協議

 

#二、撥電話

phone.connect(('127.0.0.1',8081)) #0-65535

 

#三、發收消息

phone.send('hello'.encode('utf-8'))

 

data=phone.recv(1024)

print(data)

 

#四、掛電話

phone.close()

 

1.2.6 服務端:

import socket

 

#一、買手機

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp協議

 

#二、綁定手機

# phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

phone.bind(('127.0.0.1',8081)) #0-65535

 

#三、開機

phone.listen(5)

 

#四、等待電話鏈接

print('starting...')

conn,client_addr=phone.accept() #(conn,client_addr)

print(conn,client_addr)

 

#五、收\發消息

data=conn.recv(1024) #1024bytes?

 

conn.send(data.upper())

 

#六、掛電話鏈接

conn.close()

 

#七、關機

phone.close()

 

 

1.3 加上連接循環與通訊循環

1.3.1 服務端:

import socket

 

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp協議

# print(phone)

# phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

phone.bind(('127.0.0.1',8083)) #0-65535

phone.listen(5)

 

print('starting...')

while True: #連接循環

    conn,client_addr=phone.accept() #(conn,client_addr)

    # print(conn,client_addr)

    print(client_addr)

 

    while True: #通訊循環

        try:

            data=conn.recv(1024) #1024bytes?

            if not data:break #針對的是linux系統

            print('客戶端消息',data)

            conn.send(data.upper())

            # print('====has send')

        except ConnectionResetError:

            break

    conn.close()

 

phone.close()

 

1.3.2 客戶端:

1.3.2.1  客戶端1:

import socket

 

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp協議

phone.connect(('127.0.0.1',8083)) #0-65535

 

while True:

    msg=input('>>: ') #msg=' '

    if not msg:continue

 

    phone.send(msg.encode('utf-8'))

    # print('has send===>')

    data=phone.recv(1024)

    # print('has recv===>')

    print(data)

 

phone.close()

 

1.3.2.2  客戶端2:

import socket

 

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp協議

phone.connect(('127.0.0.1',8083)) #0-65535

 

while True:

    msg=input('>>: ').strip()

    if not msg:continue

 

    phone.send(msg.encode('utf-8'))

    # print('has send===>')

    data=phone.recv(1024)

    # print('has recv===>')

    print(data)

 

phone.close()

在重啓服務端時可能會遇到

 

 

圖1-7  

  這個是因爲你的服務端仍然存在四次揮手的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狀態的鏈接,經過調整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

而後執行 /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 時間

 

1.4 實現ssh遠程執行命令

1.4.1 服務端:

from socket import *

import subprocess

 

server=socket(AF_INET,SOCK_STREAM)

server.bind(('127.0.0.1',8090))

server.listen(5)

 

while True:

    conn,client_addr=server.accept()

    print(client_addr)

 

    while True:

        try:

            cmd=conn.recv(1024)

            if not cmd:break

 

            #ls -l;sadfasdf;pwd;echo 123

            obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,

                             stdout=subprocess.PIPE,

                             stderr=subprocess.PIPE

                             )

            stdout=obj.stdout.read()

            stderr=obj.stderr.read()

 

            cmd_res=stdout+stderr

            print(len(cmd_res))

            conn.send(cmd_res)

        except ConnectionResetError:

            break

    conn.close()

 

server.close()

 

1.4.2 客戶端:

from socket import *

 

client=socket(AF_INET,SOCK_STREAM)

client.connect(('127.0.0.1',8090))

 

while True:

    cmd=input('>>: ').strip()

    if not cmd:continue

    client.send(cmd.encode('utf-8'))

    data=client.recv(1024)

    print(data.decode('gbk'))

 

client.close()

 

1.5 粘包現象

只有TCP有粘包現象,UDP永遠不會粘包,所謂粘包問題主要仍是由於接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所形成的。此外,發送方引發的粘包是由TCP協議自己形成的,TCP爲提升傳輸效率,發送方每每要收集到足夠多的數據後才發送一個TCP段。若連續幾回須要send的數據都不多,一般TCP會根據優化算法把這些數據合成一個TCP段後一次發送出去,這樣接收方就收到了粘包數據。

TCP(transport control protocol,傳輸控制協議)是面向鏈接的,面向流的,提供高可靠性服務。收發兩端(客戶端和服務器端)都要有一一成對的socket,所以,發送端爲了將多個發往接收端的包,更有效的發到對方,使用了優化方法(Nagle算法),將屢次間隔較小且數據量小的數據,合併成一個大的數據塊,而後進行封包。這樣,接收端,就難於分辨出來了,必須提供科學的拆包機制。 即面向流的通訊是無消息保護邊界的。

tcp是基於數據流的,因而收發的消息不能爲空,這就須要在客戶端和服務端都添加空消息的處理機制,防止程序卡住,而udp是基於數據報的,即使是你輸入的是空內容(直接回車),那也不是空消息,udp協議會幫你封裝上消息頭。

1.5.1 服務端:

from socket import *

import time

 

server=socket(AF_INET,SOCK_STREAM)

server.bind(('127.0.0.1',8091))

server.listen(5)

 

conn,addr=server.accept()

 

#b'hello'

res1=conn.recv(5) #b'h'

print('res1: ',res1)

 

# b'elloworld'

time.sleep(6)

res2=conn.recv(5)

print('res2: ',res2)

 

conn.close()

server.close()

 

1.5.2 客戶端:

from socket import *

import time

 

client=socket(AF_INET,SOCK_STREAM)

client.connect(('127.0.0.1',8091))

 

client.send('hello'.encode('utf-8')) #b'hello'

time.sleep(5)

client.send('world'.encode('utf-8')) #b'world'

 

client.close()

 

1.6 解決粘包現象版本1

問題的根源在於,接收端不知道發送端將要傳送的字節流的長度,因此解決粘包的方法就是圍繞,如何讓發送端在發送數據前,把本身將要發送的字節流總大小讓接收端知曉,而後接收端來一個死循環接收完全部數據

low版本的解決方法

1.6.1 struct模塊的使用

import struct

 

headers=struct.pack('i',132333)

# print(headers,len(headers))

 

res=struct.unpack('i',headers)

print(res[0])

1.6.2 服務端:

from socket import *

import subprocess

import struct

 

server=socket(AF_INET,SOCK_STREAM)

server.bind(('127.0.0.1',8093))

server.listen(5)

 

while True:

    conn,client_addr=server.accept()

    print(client_addr)

 

    while True:

        try:

            cmd=conn.recv(8096)

            if not cmd:break

 

            #ls -l;sadfasdf;pwd;echo 123

            obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,

                             stdout=subprocess.PIPE,

                             stderr=subprocess.PIPE

                             )

            stdout=obj.stdout.read()

            stderr=obj.stderr.read()

 

            #一、製做固定長度的報頭

            total_size = len(stdout) + len(stderr)

            headers=struct.pack('i',total_size)

 

            #二、先發送命令長度

            conn.send(headers)

 

            #三、發送命令的執行結果

            conn.send(stdout)

            conn.send(stderr)

        except ConnectionResetError:

            break

    conn.close()

 

server.close()

 

1.6.3 客戶端:

from socket import *

import struct

 

client=socket(AF_INET,SOCK_STREAM)

client.connect(('127.0.0.1',8093))

 

while True:

    cmd=input('>>: ').strip()

    if not cmd:continue

    client.send(cmd.encode('utf-8'))

 

    #一、先接收命令長度

    headers=client.recv(4)

    total_size = struct.unpack('i', headers)[0]

 

    #二、再收命令的結果

    recv_size=0

    data=b''

    while recv_size < total_size:

        recv_data=client.recv(1024)

        data+=recv_data

        recv_size+=len(recv_data)

 

    print(data.decode('gbk'))

 

client.close()

 

1.7 解決粘包現象版本2

爲字節流加上自定義固定長度報頭,報頭中包含字節流長度,而後一次send到對端,對端在接收時,先從緩存中取出定長的報頭,而後再取真實數據

咱們能夠把報頭作成字典,字典裏包含將要發送的真實數據的詳細信息,而後json序列化,而後用struck將序列化後的數據長度打包成4個字節(4個本身足夠用了)發送時:先發報頭長度,再編碼報頭內容而後發送,最後發真實內容。接收時:先手報頭長度,用struct取出來,根據取出的長度收取報頭內容,而後解碼,反序列化,從反序列化的結果中取出待取數據的詳細信息,而後去取真實的數據內容

1.7.1 struct模塊的使用

import struct

 

 

# headers=struct.pack('q',13233322222222222)

# print(headers,len(headers))

 

# res=struct.unpack('i',headers)

# print(res[0])

 

import json

 

headers={

    'filepath' : 'a.txt',

    'md5' : '123sxd123x123',

    'total_size' : 11111111111111111111111111111111111

}

 

headers_json=json.dumps(headers)

headers_bytes=headers_json.encode('utf-8')

 

# print(len(headers_bytes))

res=struct.pack('i',len(headers_bytes))

print(res,len(res))

 

 

1.7.2 服務端:

from socket import *

import subprocess

import struct

import json

 

server=socket(AF_INET,SOCK_STREAM)

server.bind(('127.0.0.1',8093))

server.listen(5)

 

while True:

    conn,client_addr=server.accept()

    print(client_addr)

 

    while True:

        try:

            cmd=conn.recv(8096)

            if not cmd:break

 

            #ls -l;sadfasdf;pwd;echo 123

            obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,

                             stdout=subprocess.PIPE,

                             stderr=subprocess.PIPE

                             )

            stdout=obj.stdout.read()

            stderr=obj.stderr.read()

 

            #一、製做報頭

            headers = {

                'filepath': 'a.txt',

                'md5': '123sxd123x123',

                'total_size': len(stdout) + len(stderr)

            }

 

            headers_json = json.dumps(headers)

            headers_bytes = headers_json.encode('utf-8')

 

            #二、先發報頭的長度

            conn.send(struct.pack('i',len(headers_bytes)))

 

            #三、發送報頭

            conn.send(headers_bytes)

 

            #四、發送命令的執行結果

            conn.send(stdout)

            conn.send(stderr)

        except ConnectionResetError:

            break

    conn.close()

 

server.close()

 

1.7.3 客戶端:

from socket import *

import struct

import json

 

client=socket(AF_INET,SOCK_STREAM)

client.connect(('127.0.0.1',8093))

 

while True:

    cmd=input('>>: ').strip()

    if not cmd:continue

    client.send(cmd.encode('utf-8'))

 

    #一、先接收報頭的長度

    headers_size=struct.unpack('i',client.recv(4))[0]

 

    #二、再收報頭

    headers_bytes=client.recv(headers_size)

    headers_json=headers_bytes.decode('utf-8')

    headers_dic=json.loads(headers_json)

    print('========>',headers_dic)

    total_size=headers_dic['total_size']

 

    #三、再收命令的結果

    recv_size=0

    data=b''

    while recv_size < total_size:

        recv_data=client.recv(1024)

        data+=recv_data

        recv_size+=len(recv_data)

 

    print(data.decode('gbk'))

 

client.close()

 

1.8 FTP上傳下載文件

文件目錄:share

上傳目錄:DOWNLOAD

1.8.1 客戶端:

import socket

import struct

import json

import os

 

DOWNLOAD_DIR=r'F:\Python週末20期\day8\08 上傳下載文件\DOWNLOAD'

 

class FtpClient:

    def __init__(self,host,port):

        self.host=host

        self.port=port

        self.client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

        self.client.connect((self.host,self.port))

 

    def interactive(self):

        while True:

            data=input('>>: ').strip() #get a.txt

            if not data:continue

            params=data.split() #parmas=['get','a.txt']

            cmd=params[0] #cmd='get'

            if hasattr(self,cmd):

                func=getattr(self,cmd)

                func(params) #func(['get','a.txt'])

 

    def get(self,params):

        params_json=json.dumps(params)

        self.client.send(params_json.encode('utf-8'))

 

        # 一、先接收報頭的長度

        headers_size = struct.unpack('i', self.client.recv(4))[0]

 

        # 二、再收報頭

        headers_bytes = self.client.recv(headers_size)

        headers_json = headers_bytes.decode('utf-8')

        headers_dic = json.loads(headers_json)

        print('========>', headers_dic)

        filename = headers_dic['filename']

        filesize = headers_dic['filesize']

        filepath = os.path.join(DOWNLOAD_DIR, filename)

 

        # 三、再收真實的數據

        with open(filepath, 'wb') as f:

            recv_size = 0

            while recv_size < filesize:

                line = self.client.recv(1024)

                recv_size += len(line)

                f.write(line)

            print('===>下載成功')

 

if __name__ == '__main__':

    client=FtpClient('127.0.0.1',8081)

    client.interactive()

 

 

1.8.2 服務端:

import socket

import os

import json

import struct

 

SHARE_DIR=r'F:\Python週末20期\day8\08 上傳下載文件\SHARE'

 

class FtpServer:

    def __init__(self,host,port):

        self.host=host

        self.port=port

        self.server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

        self.server.bind((self.host,self.port))

        self.server.listen(5)

 

    def serve_forever(self):

        print('server starting...')

        while True:

            self.conn,self.client_addr=self.server.accept()

            print(self.client_addr)

 

            while True:

                try:

                    data=self.conn.recv(1024)  #params_json.encode('utf-8')

                    if not data:break

                    params=json.loads(data.decode('utf-8')) #params=['get','a.txt']

                    cmd=params[0] #

                    if hasattr(self,cmd):

                        func=getattr(self,cmd)

                        func(params)

                    else:

                        print('\033[45mcmd not exists\033[0m')

                except ConnectionResetError:

                    break

            self.conn.close()

        self.server.close()

 

    def get(self,params): #params=['get','a.txt']

        filename=params[1] #filename='a.txt'

        filepath=os.path.join(SHARE_DIR,filename) #

        if os.path.exists(filepath):

            #一、製做報頭

            headers = {

                'filename': filename,

                'md5': '123sxd123x123',

                'filesize': os.path.getsize(filepath)

            }

 

            headers_json = json.dumps(headers)

            headers_bytes = headers_json.encode('utf-8')

 

            #二、先發報頭的長度

            self.conn.send(struct.pack('i',len(headers_bytes)))

 

            #三、發送報頭

            self.conn.send(headers_bytes)

 

            #四、發送真實的數據

            with open(filepath,'rb') as f:

                for line in f:

                    self.conn.send(line)

 

    def put(self):

        pass

 

if __name__ == '__main__':

    server=FtpServer('127.0.0.1',8081)

    server.serve_forever()
相關文章
相關標籤/搜索