?
第1章 異常處理
1.1 什么是異常?
1.1.1 描述
#1 什么是異常?
# 異常是錯誤發生的信號,一旦程序出錯,就會產生一個異常,應用程序未處理該異常,
# 異常便會拋出,程序隨之終止
?
異常就是程序運行時發生錯誤的信號(在程序出現錯誤時,則會產生一個異常,若程序沒有處理它,則會拋出該異常,程序的運行也隨之終止),在python中,錯誤觸發的異常如下
?
1.1.2 錯誤分類
而錯誤分成兩種


1.語法錯誤(這種錯誤,根本過不了python解釋器的語法檢測,必須在程序執行前就改正)#語法錯誤示范一if#語法錯誤示范二def test:pass#語法錯誤示范三class Foopass#語法錯誤示范四print(haha


2.邏輯錯誤#TypeError:int類型不可迭代for i in 3:pass#ValueError num=input(">>: ") #輸入hello int(num)#NameError aaa#IndexError l=['egon','aa']l[3]#KeyError dic={'name':'egon'}dic['age']#AttributeErrorclass Foo:passFoo.x#ZeroDivisionError:無法完成計算 res1=1/0res2=1+'str'
1.2 異常的種類
在python中不同的異常可以用不同的類型(python中統一了類與類型,類型即類)去標識,一個異常標識一種錯誤
1.2.1 常用異常


#######################常用異常#################### AttributeError 試圖訪問一個對象沒有的樹形,比如foo.x,但是foo沒有屬性xIOError 輸入/輸出異常;基本上是無法打開文件ImportError 無法引入模塊或包;基本上是路徑問題或名稱錯誤IndentationError 語法錯誤(的子類) ;代碼沒有正確對齊IndexError 下標索引超出序列邊界,比如當x只有三個元素,卻試圖訪問x[5]KeyError 試圖訪問字典里不存在的鍵KeyboardInterrupt Ctrl+C被按下NameError 使用一個還未被賦予對象的變量SyntaxError Python代碼非法,代碼不能編譯(個人認為這是語法錯誤,寫錯了)TypeError 傳入對象類型與要求的不符合UnboundLocalError 試圖訪問一個還未被設置的局部變量,基本上是由于另有一個同名的全局變量,導致你以為正在訪問它ValueError 傳入一個調用者不期望的值,即使值的類型是正確的
1.2.2 更多異常


ArithmeticErrorAssertionErrorAttributeErrorBaseExceptionBufferErrorBytesWarningDeprecationWarningEnvironmentErrorEOFErrorExceptionFloatingPointErrorFutureWarningGeneratorExitImportErrorImportWarningIndentationErrorIndexErrorIOErrorKeyboardInterruptKeyErrorLookupErrorMemoryErrorNameErrorNotImplementedErrorOSErrorOverflowErrorPendingDeprecationWarningReferenceErrorRuntimeErrorRuntimeWarningStandardErrorStopIterationSyntaxErrorSyntaxWarningSystemErrorSystemExitTabErrorTypeErrorUnboundLocalErrorUnicodeDecodeErrorUnicodeEncodeErrorUnicodeErrorUnicodeTranslateErrorUnicodeWarningUserWarningValueErrorWarningZeroDivisionError
1.2.3 常見異常類型


#2、常見異常類型#I:語法錯誤應該在程序運行前修正# if 1 >2# print('xxxxx')#II:邏輯錯誤# x# l=[]# l[10000] #IndexError# class Foo:# pass# Foo.x #AttributeError:# k={'x':1}# k['y'] #KeyError# 1/0 #ZeroDivisionError# for i in 3: #TypeError:# pass# age=input('>>: ') #ValueError# age=int(age)
1.3 異常處理
1.3.1 異常處理描述
為了保證程序的健壯性與容錯性,即在遇到錯誤時程序不會崩潰,我們需要對異常進行處理,
如果錯誤發生的條件是可預知的,我們需要用if進行處理:在錯誤發生之前進行預防


AGE=10while True:age=input('>>: ').strip()if age.isdigit(): #只有在age為字符串形式的整數時,下列代碼才不會出錯,該條件是可預知的 age=int(age)if age == AGE:print('you got it')break
如果錯誤發生的條件是不可預知的,則需要用到try...except:在錯誤發生之后進行處理


#基本語法為try:被檢測的代碼塊except 異常類型:try中一旦檢測到異常,就執行這個位置的邏輯#舉例try:f=open('a.txt')g=(line.strip() for line in f)print(next(g))print(next(g))print(next(g))print(next(g))print(next(g))except StopIteration:f.close()
1.3.2 異常分類使用


#1 異常類只能用來處理指定的異常情況,如果非指定異常則無法處理。 s1 = 'hello'try:int(s1)except IndexError as e: # 未捕獲到異常,程序直接報錯print e#2 多分支 s1 = 'hello'try:int(s1)except IndexError as e:print(e)except KeyError as e:print(e)except ValueError as e:print(e)#3 萬能異常Exception s1 = 'hello'try:int(s1)except Exception as e:print(e)#4 多分支異常與萬能異常#4.1 如果你想要的效果是,無論出現什么異常,我們統一丟棄,或者使用同一段代碼邏輯去處理他們,那么騷年,大膽的去做吧,只有一個Exception就足夠了。#4.2 如果你想要的效果是,對于不同的異常我們需要定制不同的處理邏輯,那就需要用到多分支了。#5 也可以在多分支后來一個Exception s1 = 'hello'try:int(s1)except IndexError as e:print(e)except KeyError as e:print(e)except ValueError as e:print(e)except Exception as e:print(e)#6 異常的其他機構 s1 = 'hello'try:int(s1)except IndexError as e:print(e)except KeyError as e:print(e)except ValueError as e:print(e)#except Exception as e:# print(e)else:print('try內代碼塊沒有異常則執行我')finally:print('無論異常與否,都會執行該模塊,通常是進行清理工作')#7 主動觸發異常try:raise TypeError('類型錯誤')except Exception as e:print(e)#8 自定義異常class EgonException(BaseException):def __init__(self,msg):self.msg=msgdef __str__(self):return self.msgtry:raise EgonException('類型錯誤')except EgonException as e:print(e)#9 斷言:assert 條件assert 1 == 1 assert 1 == 2#10 總結try..except1:把錯誤處理和真正的工作分開來2:代碼更易組織,更清晰,復雜的工作任務更容易實現;3:毫無疑問,更安全了,不至于由于一些小的疏忽而使程序意外崩潰了;
1.3.3 如何處理異常實例


#3、如何處理異常# print('====>start<=====')# # try:# l=[]# print(l[1111])# print('====>1')# print('====>2')# print('====>3')# except IndexError:# pass# # print('====>end<=======')# print('====>start<=====')# try:# l=[]# print(l[1111])# print('====>1')# print('====>2')# print('====>3')# except IndexError as e:# print('===>',e)# # print('====>end<=======')# 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<=======')# 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<=======')# 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<=======')'''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) age=input('>>: ')if age.isdigit():age=int(age)if age > 50:print('====>too big')
1.4 什么時候用異常處理
有的同學會這么想,學完了異常處理后,好強大,我要為我的每一段程序都加上try...except,干毛線去思考它會不會有邏輯錯誤啊,這樣就很好啊,多省腦細胞===》2B青年歡樂多
首先try...except是你附加給你的程序的一種異常處理的邏輯,與你的主要的工作是沒有關系的,這種東西加的多了,會導致你的代碼可讀性變差
然后異常處理本就不是你2b邏輯的擦屁股紙,只有在錯誤發生的條件無法預知的情況下,才應該加上try...except,其他的邏輯錯誤應該盡量修正。
?
第2章 網絡編程
2.1 楔子
你現在已經學會了寫python代碼,假如你寫了兩個python文件a.py和b.py,分別去運行,你就會發現,這兩個python的文件分別運行的很好。但是如果這兩個程序之間想要傳遞一個數據,你要怎么做呢?
這個問題以你現在的知識就可以解決了,我們可以創建一個文件,把a.py想要傳遞的內容寫到文件中,然后b.py從這個文件中讀取內容就可以了。
?
但是當你的a.py和b.py分別在不同電腦上的時候,你要怎么辦呢?
類似的機制有計算機網盤,qq等等。我們可以在我們的電腦上和別人聊天,可以在自己的電腦上向網盤中上傳、下載內容。這些都是兩個程序在通信。
?
2.2 軟件開發的架構
我們了解的涉及到兩個程序之間通訊的應用大致可以分為兩種:
第一種是應用類:qq、微信、網盤、優酷這一類是屬于需要安裝的桌面應用
第二種是web類:比如百度、知乎、博客園等使用瀏覽器訪問就可以直接使用的應用
這些應用的本質其實都是兩個程序之間的通訊。而這兩個分類又對應了兩個軟件開發的架構~
2.2.1 C/S架構
C/S即:Client與Server ,中文意思:客戶端與服務器端架構,這種架構也是從用戶層面(也可以是物理層面)來劃分的。
這里的客戶端一般泛指客戶端應用程序EXE,程序需要先安裝后,才能運行在用戶的電腦上,對用戶的電腦操作系統環境依賴較大。
?
2.2.2 B/S架構
B/S即:Browser與Server,中文意思:瀏覽器端與服務器端架構,這種架構是從用戶層面來劃分的。
Browser瀏覽器,其實也是一種Client客戶端,只是這個客戶端不需要大家去安裝什么應用程序,只需在瀏覽器上通過HTTP請求服務器端相關的資源(網頁資源),客戶端Browser瀏覽器就能進行增刪改查。
?
?
2.3 網絡基礎
2.3.1 詳細請見外鏈地址
http://www.cnblogs.com/Eva-J/articles/8066842.html
或者
http://www.cnblogs.com/linhaifeng/articles/5937962.html
?
2.3.2 一個程序如何在網絡上找到另一個程序?
首先,程序必須要啟動,其次,必須有這臺機器的地址,我們都知道我們人的地址大概就是國家\省\市\區\街道\樓\門牌號這樣字。那么每一臺聯網的機器在網絡上也有自己的地址,它的地址是怎么表示的呢?
就是使用一串數字來表示的,例如:100.4.5.6
#########################什么是ip地址?############################ IP地址是指互聯網協議地址(英語:Internet Protocol Address,又譯為網際協議地址),是IP Address的縮寫。IP地址是IP協議提供的一種統一的地址格式,它為互聯網上的每一個網絡和每一臺主機分配一個邏輯地址,以此來屏蔽物理地址的差異。 IP地址是一個32位的二進制數,通常被分割為4個“8位二進制數”(也就是4個字節)。IP地址通常用“點分十進制”表示成(a.b.c.d)的形式,其中,a,b,c,d都是0~255之間的十進制整數。例:點分十進IP地址(100.4.5.6),實際上是32位二進制數(01100100.00000100.00000101.00000110)。
#####################什么是端口#########################"端口"是英文port的意譯,可以認為是設備與外界通訊交流的出口。因此ip地址精確到具體的一臺電腦,而端口精確到具體的程序。
2.4 osi七層模型
2.4.1 引子
須知一個完整的計算機系統是由硬件、操作系統、應用軟件三者組成,具備了這三個條件,一臺計算機系統就可以自己跟自己玩了(打個單機游戲,玩個掃雷啥的)
如果你要跟別人一起玩,那你就需要上網了,什么是互聯網?
互聯網的核心就是由一堆協議組成,協議就是標準,比如全世界人通信的標準是英語,如果把計算機比作人,互聯網協議就是計算機界的英語。所有的計算機都學會了互聯網協議,那所有的計算機都就可以按照統一的標準去收發信息從而完成通信了。
?
2.4.2 osi七層模型
人們按照分工不同把互聯網協議從邏輯上劃分了層級:
?
2.4.3 為何學習socket一定要先學習互聯網協議
為何學習socket一定要先學習互聯網協議:
1.首先:本節課程的目標就是教會你如何基于socket編程,來開發一款自己的C/S架構軟件
2.其次:C/S架構的軟件(軟件屬于應用層)是基于網絡進行通信的
3.然后:網絡的核心即一堆協議,協議即標準,你想開發一款基于網絡通信的軟件,就必須遵循這些標準。
4.最后:就讓我們從這些標準開始研究,開啟我們的socket編程之旅
?
2.5 socket概念
2.5.1 socket層
?
2.5.2 理解socket
Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。
###################站在你的角度上看socket######################## 其實站在你的角度上看,socket就是一個模塊。我們通過調用模塊中已經實現的方法建立兩個進程之間的連接和通信。也有人將socket說成ip+port,因為ip是用來標識互聯網中的一臺主機的位置,而port是用來標識這臺機器上的一個應用程序。所以我們只要確立了ip和port就能找到一個應用程序,并且使用socket模塊來與之通信。
2.6 套接字(socket)的發展史
套接字起源于 20 世紀 70 年代加利福尼亞大學伯克利分校版本的 Unix,即人們所說的 BSD Unix。 因此,有時人們也把套接字稱為“伯克利套接字”或“BSD 套接字”。一開始,套接字被設計用在同 一臺主機上多個應用程序之間的通訊。這也被稱進程間通訊,或 IPC。套接字有兩種(或者稱為有兩個種族),分別是基于文件型的和基于網絡型的。
2.6.1 基于文件類型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程運行在同一機器,可以通過訪問同一個文件系統間接完成通信
?
2.6.2 基于網絡類型的套接字家族
套接字家族的名字:AF_INET
(還有AF_INET6被用于ipv6,還有一些其他的地址家族,不過,他們要么是只用于某個平臺,要么就是已經被廢棄,或者是很少被使用,或者是根本沒有實現,所有地址家族中,AF_INET是使用最廣泛的一個,python支持很多種地址家族,但是由于我們只關心網絡編程,所以大部分時候我么只使用AF_INET)
?
2.7 套接字工作流程
? 一個生活中的場景。你要打電話給一個朋友,先撥號,朋友聽到電話鈴聲后提起電話,這時你和你的朋友就建立起了連接,就可以講話了。等交流結束,掛斷電話結束此次交談。 生活中的場景就解釋了這工作原理。
?
先從服務器端說起。服務器端先初始化Socket,然后與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端連接。在這時如果有個客戶端初始化一個Socket,然后連接服務器(connect),如果連接成功,這時客戶端與服務器端的連接就建立了。客戶端發送數據請求,服務器端接收請求并處理請求,然后把回應數據發送給客戶端,客戶端讀取數據,最后關閉連接,一次交互結束
?
2.7.1 socket()模塊函數用法


import socketsocket.socket(socket_family,socket_type,protocal=0)socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默認值為 0。獲取tcp/ip套接字tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)獲取udp/ip套接字udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)由于 socket 模塊中有太多的屬性。我們在這里破例使用了'from module import *'語句。使用 'from socket import *',我們就把 socket 模塊里的所有屬性都帶到我們的命名空間里了,這樣能 大幅減短我們的代碼。例如tcpSock = socket(AF_INET, SOCK_STREAM)
2.7.2 服務端套接字函數
s.bind()??? 綁定(主機,端口號)到套接字
s.listen()? 開始TCP監聽
s.accept()? 被動接受TCP客戶的連接,(阻塞式)等待連接的到來
?
2.7.3 客戶端套接字函數
s.connect()???? 主動初始化TCP服務器連接
s.connect_ex()? connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常
?
2.7.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()?????????? 關閉套接字
?
2.7.5 面向鎖的套接字方法
s.setblocking()???? 設置套接字的阻塞與非阻塞模式
s.settimeout()????? 設置阻塞套接字操作的超時時間
s.gettimeout()????? 得到阻塞套接字操作的超時時間
?
2.7.6 面向文件的套接字的函數
s.fileno()????????? 套接字的文件描述符
s.makefile()??????? 創建一個與該套接字相關的文件
?
2.7.7 socket實驗推演流程


1:用打電話的流程快速描述socket通信2:服務端和客戶端加上基于一次鏈接的循環通信3:客戶端發送空,卡主,證明是從哪個位置卡的服務端:from socket import *phone=socket(AF_INET,SOCK_STREAM)phone.bind(('127.0.0.1',8081))phone.listen(5)conn,addr=phone.accept()while True:data=conn.recv(1024)print('server===>')print(data)conn.send(data.upper())conn.close()phone.close()客戶端:from socket import *phone=socket(AF_INET,SOCK_STREAM)phone.connect(('127.0.0.1',8081))while True:msg=input('>>: ').strip()phone.send(msg.encode('utf-8'))print('client====>')data=phone.recv(1024)print(data)說明卡的原因:緩沖區為空recv就卡住,引出原理圖4.演示客戶端斷開鏈接,服務端的情況,提供解決方法5.演示服務端不能重復接受鏈接,而服務器都是正常運行不斷來接受客戶鏈接的6:簡單演示udp服務端from socket import *phone=socket(AF_INET,SOCK_DGRAM)phone.bind(('127.0.0.1',8082))while True:msg,addr=phone.recvfrom(1024)phone.sendto(msg.upper(),addr)客戶端from socket import *phone=socket(AF_INET,SOCK_DGRAM)while True:msg=input('>>: ')phone.sendto(msg.encode('utf-8'),('127.0.0.1',8082))msg,addr=phone.recvfrom(1024)print(msg)udp客戶端可以并發演示udp客戶端可以輸入為空演示,說出recvfrom與recv的區別,暫且不提tcp流和udp報的概念,留到粘包去說
2.8 基于TCP的套接字
tcp是基于鏈接的,必須先啟動服務端,然后再啟動客戶端去鏈接服務端
2.8.1 實例一server端與client端


#################server端#################import socketsk = socket.socket()sk.bind(('127.0.0.1',8898)) #把地址綁定到套接字 sk.listen() #監聽鏈接 conn,addr = sk.accept() #接受客戶端鏈接 ret = conn.recv(1024) #接收客戶端信息print(ret) #打印客戶端信息 conn.send(b'hi') #向客戶端發送信息 conn.close() #關閉客戶端套接字 sk.close() #關閉服務器套接字(可選)


#####################client端############################import socketsk = socket.socket() # 創建客戶套接字 sk.connect(('127.0.0.1',8898)) # 嘗試連接服務器 sk.send(b'hello!')ret = sk.recv(1024) # 對話(發送/接收)print(ret)sk.close() # 關閉客戶套接字
2.8.2 問題:有的同學在重啟服務端時可能會遇到
?


##################解決方法####################加入一條socket配置,重用ip和端口import socketfrom socket import SOL_SOCKET,SO_REUSEADDRsk = socket.socket()sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加 sk.bind(('127.0.0.1',8898)) #把地址綁定到套接字 sk.listen() #監聽鏈接 conn,addr = sk.accept() #接受客戶端鏈接 ret = conn.recv(1024) #接收客戶端信息print(ret) #打印客戶端信息 conn.send(b'hi') #向客戶端發送信息 conn.close() #關閉客戶端套接字 sk.close() #關閉服務器套接字(可選)
2.8.3 實例二server端與client端


##################server端######################import socket#1、買手機 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp協議#2、綁定手機# phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) phone.bind(('127.0.0.1',8081)) #0-65535#3、開機 phone.listen(5)#4、等待電話連接print('starting...')conn,client_addr=phone.accept() #(conn,client_addr)print(conn,client_addr)#5、收\發消息 data=conn.recv(1024) #1024bytes? conn.send(data.upper())#6、掛電話連接 conn.close()#7、關機 phone.close()


################client端####################import socket#1、買手機 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp協議#2、撥電話 phone.connect(('127.0.0.1',8081)) #0-65535#3、發收消息 phone.send('hello'.encode('utf-8'))data=phone.recv(1024)print(data)#4、掛電話 phone.close()
2.8.4 實例二加上鏈接循環與通信循環進階版
模擬一個服務端三個客戶端連接通信


#######################服務端進階版###########################import socketphone=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:breakconn.close()phone.close()


#############client端一進階版#####################import socketphone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp協議 phone.connect(('127.0.0.1',8083)) #0-65535while True:msg=input('>>: ') #msg=' 'if not msg:continuephone.send(msg.encode('utf-8'))# print('has send===>') data=phone.recv(1024)# print('has recv===>')print(data)phone.close()


#############client端二進階版#####################import socketphone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp協議 phone.connect(('127.0.0.1',8083)) #0-65535while True:msg=input('>>: ').strip()if not msg:continuephone.send(msg.encode('utf-8'))# print('has send===>') data=phone.recv(1024)# print('has recv===>')print(data)phone.close()


#############client端三進階版#####################import socketphone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp協議 phone.connect(('127.0.0.1',8083)) #0-65535while True:msg=input('>>: ').strip()if not msg:continuephone.send(msg.encode('utf-8'))# print('has send===>') data=phone.recv(1024)# print('has recv===>')print(data)phone.close()
2.8.5 實現ssh遠程執行命令


#################服務端##################from socket import *import subprocessserver=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+stderrprint(len(cmd_res))conn.send(cmd_res)except ConnectionResetError:breakconn.close()server.close()


#####################client端#################from socket import *client=socket(AF_INET,SOCK_STREAM)client.connect(('127.0.0.1',8090))while True:cmd=input('>>: ').strip()if not cmd:continueclient.send(cmd.encode('utf-8'))data=client.recv(1024)print(data.decode('gbk'))client.close()
2.9 基于UDP協議的socket
udp是無鏈接的,先啟動哪一端都不會報錯
2.9.1 簡單使用


###################server端#####################import socketudp_sk = socket.socket(type=socket.SOCK_DGRAM) #創建一個服務器的套接字 udp_sk.bind(('127.0.0.1',9000)) #綁定服務器套接字 msg,addr = udp_sk.recvfrom(1024)print(msg)udp_sk.sendto(b'hi',addr) # 對話(接收與發送) udp_sk.close() # 關閉服務器套接字


#######################client端#######################import socketip_port=('127.0.0.1',9000)udp_sk=socket.socket(type=socket.SOCK_DGRAM)udp_sk.sendto(b'hello',ip_port)back_msg,addr=udp_sk.recvfrom(1024)print(back_msg.decode('utf-8'),addr)
2.9.2 qq聊天


#################server端##############################_*_coding:utf-8_*_import socketip_port=('127.0.0.1',8081)udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)udp_server_sock.bind(ip_port)while True:qq_msg,addr=udp_server_sock.recvfrom(1024)print('來自[%s:%s]的一條消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))back_msg=input('回復消息: ').strip()udp_server_sock.sendto(back_msg.encode('utf-8'),addr)


##################################client端######################_*_coding:utf-8_*_import socketBUFSIZE=1024udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)qq_name_dic={'金老板':('127.0.0.1',8081),'哪吒':('127.0.0.1',8081),'egg':('127.0.0.1',8081),'yuan':('127.0.0.1',8081),}while True:qq_name=input('請選擇聊天對象: ').strip()while True:msg=input('請輸入消息,回車發送,輸入q結束和他的聊天: ').strip()if msg == 'q':breakif not msg or not qq_name or qq_name not in qq_name_dic:continueudp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])back_msg,addr=udp_client_socket.recvfrom(BUFSIZE)print('來自[%s:%s]的一條消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))udp_client_socket.close()
2.9.3 時間服務器


#################server端############################## _*_coding:utf-8_*_from socket import *from time import strftimeip_port = ('127.0.0.1', 9000)bufsize = 1024tcp_server = socket(AF_INET, SOCK_DGRAM)tcp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)tcp_server.bind(ip_port)while True:msg, addr = tcp_server.recvfrom(bufsize)print('===>', msg)if not msg:time_fmt = '%Y-%m-%d %X'else:time_fmt = msg.decode('utf-8')back_msg = strftime(time_fmt)tcp_server.sendto(back_msg.encode('utf-8'), addr)tcp_server.close()


#################client端##############################_*_coding:utf-8_*_from socket import *ip_port=('127.0.0.1',9000)bufsize=1024tcp_client=socket(AF_INET,SOCK_DGRAM)while True:msg=input('請輸入時間格式(例%Y %m %d)>>: ').strip()tcp_client.sendto(msg.encode('utf-8'),ip_port)data=tcp_client.recv(bufsize)
2.10 socket參數的詳解
socket.socket(family=AF_INET,type=SOCK_STREAM,proto=0,fileno=None)
創建socket對象的參數說明:
family | 地址系列應為AF_INET(默認值),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。 |
type | 套接字類型應為SOCK_STREAM(默認值),SOCK_DGRAM,SOCK_RAW或其他SOCK_常量之一。 |
proto | 協議號通常為零,可以省略,或者在地址族為AF_CAN的情況下,協議應為CAN_RAW或CAN_BCM之一。 |
fileno | 如果指定了fileno,則其他參數將被忽略,導致帶有指定文件描述符的套接字返回。 |
?
2.11 粘包
2.11.1 黏包現象
讓我們基于tcp先制作一個遠程執行命令的程序(命令ls -l ; lllllll ; pwd)
###########################注意######################### res=subprocess.Popen(cmd.decode('utf-8'),shell=True,stderr=subprocess.PIPE,stdout=subprocess.PIPE)的結果的編碼是以當前所在的系統為準的,如果是windows,那么res.stdout.read()讀出的就是GBK編碼的,在接收端需要用GBK解碼 且只能從管道里讀一次結果
同時執行多條命令之后,得到的結果很可能只有一部分,在執行其他命令的時候又接收到之前執行的另外一部分結果,這種顯現就是黏包。
2.11.2 基于tcp協議實現的黏包


########################tcp - server####################_*_coding:utf-8_*_from socket import *import subprocessip_port=('127.0.0.1',8888)BUFSIZE=1024tcp_socket_server=socket(AF_INET,SOCK_STREAM)tcp_socket_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)tcp_socket_server.bind(ip_port)tcp_socket_server.listen(5)while True:conn,addr=tcp_socket_server.accept()print('客戶端',addr)while True:cmd=conn.recv(BUFSIZE)if len(cmd) == 0:breakres=subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stdin=subprocess.PIPE,stderr=subprocess.PIPE)stderr=res.stderr.read()stdout=res.stdout.read()conn.send(stderr)conn.send(stdout)


######################tcp - client########################_*_coding:utf-8_*_import socketBUFSIZE=1024ip_port=('127.0.0.1',8888)s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)res=s.connect_ex(ip_port)while True:msg=input('>>: ').strip()if len(msg) == 0:continueif msg == 'quit':breaks.send(msg.encode('utf-8'))act_res=s.recv(BUFSIZE)print(act_res.decode('utf-8'),end='')
2.11.3 基于tcp協議黏包現象實例二


#################server端###################from socket import *import timeserver=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()


######################client端##############################from socket import *import timeclient=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()
2.11.4 基于udp協議實現的黏包


#################udp - server#######################_*_coding:utf-8_*_from socket import *import subprocessip_port=('127.0.0.1',9000)bufsize=1024udp_server=socket(AF_INET,SOCK_DGRAM)udp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)udp_server.bind(ip_port)while True:#收消息 cmd,addr=udp_server.recvfrom(bufsize)print('用戶命令----->',cmd)#邏輯處理 res=subprocess.Popen(cmd.decode('utf-8'),shell=True,stderr=subprocess.PIPE,stdin=subprocess.PIPE,stdout=subprocess.PIPE)stderr=res.stderr.read()stdout=res.stdout.read()#發消息 udp_server.sendto(stderr,addr)udp_server.sendto(stdout,addr)udp_server.close()


######################udp - client#############################from socket import *ip_port=('127.0.0.1',9000)bufsize=1024udp_client=socket(AF_INET,SOCK_DGRAM)while True:msg=input('>>: ').strip()udp_client.sendto(msg.encode('utf-8'),ip_port)err,addr=udp_client.recvfrom(bufsize)out,addr=udp_client.recvfrom(bufsize)if err:print('error : %s'%err.decode('utf-8'),end='')if out:print(out.decode('utf-8'), end='')
注意:只有TCP有粘包現象,UDP永遠不會粘包
2.12 黏包成因
2.12.1 TCP協議中的數據傳遞
##################tcp協議的拆包機制################### 當發送端緩沖區的長度大于網卡的MTU時,tcp會將這次發送的數據拆成幾個數據包發送出去。MTU是Maximum Transmission Unit的縮寫。意思是網絡上傳送的最大數據包。MTU的單位是字節。 大部分網絡設備的MTU都是1500。如果本機的MTU比網關的MTU大,大的數據包就會被拆開來傳送,這樣會產生很多數據包碎片,增加丟包率,降低網絡速度。
#########################面向流的通信特點和Nagle算法############################## TCP(transport control protocol,傳輸控制協議)是面向連接的,面向流的,提供高可靠性服務。收發兩端(客戶端和服務器端)都要有一一成對的socket,因此,發送端為了將多個發往接收端的包,更有效的發到對方,使用了優化方法(Nagle算法),將多次間隔較小且數據量小的數據,合并成一個大的數據塊,然后進行封包。這樣,接收端,就難于分辨出來了,必須提供科學的拆包機制。 即面向流的通信是無消息保護邊界的。對于空消息:tcp是基于數據流的,于是收發的消息不能為空,這就需要在客戶端和服務端都添加空消息的處理機制,防止程序卡住,而udp是基于數據報的,即便是你輸入的是空內容(直接回車),也可以被發送,udp協議會幫你封裝上消息頭發送過去。可靠黏包的tcp協議:tcp的協議數據不會丟,沒有收完包,下次接收,會繼續上次繼續接收,己端總是在收到ack時才會清除緩沖區內容。數據是可靠的,但是會粘包。
##################基于tcp協議特點的黏包現象成因#############
?
##############socket數據傳輸過程中的用戶態與內核態說明################ 例如基于tcp的套接字客戶端往服務端上傳文件,發送時文件內容是按照一段一段的字節流發送的,在接收方看了,根本不知道該文件的字節流從何處開始,在何處結束此外,發送方引起的粘包是由TCP協議本身造成的,TCP為提高傳輸效率,發送方往往要收集到足夠多的數據后才發送一個TCP段。若連續幾次需要send的數據都很少,通常TCP會根據優化算法把這些數據合成一個TCP段后一次發送出去,這樣接收方就收到了粘包數據。
2.12.2 UDP不會發生黏包
UDP(user datagram protocol,用戶數據報協議)是無連接的,面向消息的,提供高效率服務。不會使用塊的合并優化算法,, 由于UDP支持的是一對多的模式,所以接收端的skbuff(套接字緩沖區)采用了鏈式結構來記錄每一個到達的UDP包,在每個UDP包中就有了消息頭(消息來源地址,端口等信息),這樣,對于接收端來說,就容易進行區分處理了。 即面向消息的通信是有消息保護邊界的。對于空消息:tcp是基于數據流的,于是收發的消息不能為空,這就需要在客戶端和服務端都添加空消息的處理機制,防止程序卡住,而udp是基于數據報的,即便是你輸入的是空內容(直接回車),也可以被發送,udp協議會幫你封裝上消息頭發送過去。不可靠不黏包的udp協議:udp的recvfrom是阻塞的,一個recvfrom(x)必須對唯一一個sendinto(y),收完了x個字節的數據就算完成,若是y;x數據就丟失,這意味著udp根本不會粘包,但是會丟數據,不可靠。
補充說明:
#####################udp和tcp一次發送數據長度的限制##################### 用UDP協議發送時,用sendto函數最大能發送數據的長度為:65535- IP頭(20) – UDP頭(8)=65507字節。用sendto函數發送數據時,如果發送數據長度大于該值,則函數會返回錯誤。(丟棄這個包,不進行發送)用TCP協議發送時,由于TCP是數據流協議,因此不存在包大小的限制(暫不考慮緩沖區的大小),這是指在用send函數時,數據長度參數不受限制。而實際上,所指定的這段數據并不一定會一次性發送出去,如果這段數據比較長,會被分段發送,如果比較短,可能會等待和下一次數據一起發送。
2.12.3 會發生黏包的兩種情況
情況一 發送方的緩存機制
發送端需要等緩沖區滿才發送出去,造成粘包(發送數據時間間隔很短,數據了很小,會合到一起,產生粘包)


#############################服務端######################_*_coding:utf-8_*_from socket import *ip_port=('127.0.0.1',8080)tcp_socket_server=socket(AF_INET,SOCK_STREAM)tcp_socket_server.bind(ip_port)tcp_socket_server.listen(5)conn,addr=tcp_socket_server.accept()data1=conn.recv(10)data2=conn.recv(10)print('----->',data1.decode('utf-8'))print('----->',data2.decode('utf-8'))conn.close()


####################客戶端#####################_*_coding:utf-8_*_import socketBUFSIZE=1024ip_port=('127.0.0.1',8080)s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)res=s.connect_ex(ip_port)s.send('hello'.encode('utf-8'))s.send('feng'.encode('utf-8'))
情況二 接收方的緩存機制
?接收方不及時接收緩沖區的包,造成多個包接收(客戶端發送了一段數據,服務端只收了一小部分,服務端下次再收的時候還是從緩沖區拿上次遺留的數據,產生粘包)


#######################服務端##########################_*_coding:utf-8_*_from socket import *ip_port=('127.0.0.1',8080)tcp_socket_server=socket(AF_INET,SOCK_STREAM)tcp_socket_server.bind(ip_port)tcp_socket_server.listen(5)conn,addr=tcp_socket_server.accept()data1=conn.recv(2) #一次沒有收完整 data2=conn.recv(10)#下次收的時候,會先取舊的數據,然后取新的print('----->',data1.decode('utf-8'))print('----->',data2.decode('utf-8'))conn.close()


############################客戶端##########################_*_coding:utf-8_*_import socketBUFSIZE=1024ip_port=('127.0.0.1',8080)s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)res=s.connect_ex(ip_port)s.send('hello feng'.encode('utf-8'))
2.12.4 總結
黏包現象只發生在tcp協議中:
1.從表面上看,黏包問題主要是因為發送方和接收方的緩存機制、tcp協議面向流通信的特點。
2.實際上,主要還是因為接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所造成的
?
2.13 黏包的解決方案一
問題的根源在于,接收端不知道發送端將要傳送的字節流的長度,所以解決粘包的方法就是圍繞,如何讓發送端在發送數據前,把自己將要發送的字節流總大小讓接收端知曉,然后接收端來一個死循環接收完所有數據。
?


###################服務端#################### #_*_coding:utf-8_*_import socket,subprocessip_port=('127.0.0.1',8080)s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)s.bind(ip_port)s.listen(5)while True:conn,addr=s.accept()print('客戶端',addr)while True:msg=conn.recv(1024)if not msg:breakres=subprocess.Popen(msg.decode('utf-8'),shell=True,\stdin=subprocess.PIPE,\stderr=subprocess.PIPE,\stdout=subprocess.PIPE)err=res.stderr.read()if err:ret=errelse:ret=res.stdout.read()data_length=len(ret)conn.send(str(data_length).encode('utf-8'))data=conn.recv(1024).decode('utf-8')if data == 'recv_ready':conn.sendall(ret)conn.close()


##################客戶端###################_*_coding:utf-8_*_import socket,times=socket.socket(socket.AF_INET,socket.SOCK_STREAM)res=s.connect_ex(('127.0.0.1',8080))while True:msg=input('>>: ').strip()if len(msg) == 0:continueif msg == 'quit':breaks.send(msg.encode('utf-8'))length=int(s.recv(1024).decode('utf-8'))s.send('recv_ready'.encode('utf-8'))send_size=0recv_size=0data=b''while recv_size < length:data+=s.recv(1024)recv_size+=len(data)print(data.decode('utf-8'))
存在的問題:
程序的運行速度遠快于網絡傳輸速度,所以在發送一段字節前,先用send去發送該字節流長度,這種方式會放大網絡延遲帶來的性能損耗。
?
2.14 解決方案進階
剛剛的方法,問題在于我們我們在發送
?
我們可以借助一個模塊,這個模塊可以把要發送的數據長度轉換成固定長度的字節。這樣客戶端每次接收消息之前只要先接受這個固定長度字節的內容看一看接下來要接收的信息大小,那么最終接受的數據只要達到這個值就停止,就能剛好不多不少的接收完整的數據了。
?
2.14.1 struct模塊
該模塊可以把一個類型,如數字,轉成固定長度的bytes
>>> struct.pack('i',1111111111111)struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #這個是范圍
?
?
import json,struct#假設通過客戶端上傳1T:1073741824000的文件a.txt#為避免粘包,必須自定制報頭 header={'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T數據,文件路徑和md5值#為了該報頭能傳送,需要序列化并且轉為bytes head_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化并轉成bytes,用于傳輸#為了讓客戶端知道報頭的長度,用struck將報頭長度這個數字轉成固定長度:4個字節 head_len_bytes=struct.pack('i',len(head_bytes)) #這4個字節里只包含了一個數字,該數字是報頭的長度#客戶端開始發送 conn.send(head_len_bytes) #先發報頭的長度,4個bytes conn.send(head_bytes) #再發報頭的字節格式 conn.sendall(文件內容) #然后發真實內容的字節格式#服務端開始接收 head_len_bytes=s.recv(4) #先收報頭4個bytes,得到報頭長度的字節格式 x=struct.unpack('i',head_len_bytes)[0] #提取報頭的長度 head_bytes=s.recv(x) #按照報頭長度x,收取報頭的bytes格式 header=json.loads(json.dumps(header)) #提取報頭#最后根據報頭的內容提取真實的數據,比如 real_data_len=s.recv(header['file_size'])s.recv(real_data_len)


####################關于struct的詳細用法######################_*_coding:utf-8_*_#http://www.cnblogs.com/coser/archive/2011/12/17/2291160.html__author__ = 'Linhaifeng'import structimport binasciiimport ctypesvalues1 = (1, 'abc'.encode('utf-8'), 2.7)values2 = ('defg'.encode('utf-8'),101)s1 = struct.Struct('I3sf')s2 = struct.Struct('4sI')print(s1.size,s2.size)prebuffer=ctypes.create_string_buffer(s1.size+s2.size)print('Before : ',binascii.hexlify(prebuffer))# t=binascii.hexlify('asdfaf'.encode('utf-8'))# print(t) s1.pack_into(prebuffer,0,*values1)s2.pack_into(prebuffer,s1.size,*values2)print('After pack',binascii.hexlify(prebuffer))print(s1.unpack_from(prebuffer,0))print(s2.unpack_from(prebuffer,s1.size))s3=struct.Struct('ii')s3.pack_into(prebuffer,0,123,123)print('After pack',binascii.hexlify(prebuffer))print(s3.unpack_from(prebuffer,0))
2.14.2 使用struct解決黏包
借助struct模塊,我們知道長度數字可以被轉換成一個標準大小的4字節數字。因此可以利用這個特點來預先發送數據長度。
發送時 | 接收時 |
先發送struct轉換好的數據長度4字節 | 先接受4個字節使用struct轉換成數字來獲取要接收的數據長度 |
再發送數據 | 再按照長度接收數據 |
?


#################服務端(自定制報頭)##################import socket,struct,jsonimport subprocessphone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加 phone.bind(('127.0.0.1',8080))phone.listen(5)while True:conn,addr=phone.accept()while True:cmd=conn.recv(1024)if not cmd:breakprint('cmd: %s' %cmd)res=subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)err=res.stderr.read()print(err)if err:back_msg=errelse:back_msg=res.stdout.read()conn.send(struct.pack('i',len(back_msg))) #先發back_msg的長度 conn.sendall(back_msg) #在發真實的內容 conn.close()


#####################客戶端(自定制報頭)#######################_*_coding:utf-8_*_import socket,time,structs=socket.socket(socket.AF_INET,socket.SOCK_STREAM)res=s.connect_ex(('127.0.0.1',8080))while True:msg=input('>>: ').strip()if len(msg) == 0:continueif msg == 'quit':breaks.send(msg.encode('utf-8'))l=s.recv(4)x=struct.unpack('i',l)[0]print(type(x),x)# print(struct.unpack('I',l)) r_s=0data=b''while r_s < x:r_d=s.recv(1024)data+=r_dr_s+=len(r_d)# print(data.decode('utf-8'))print(data.decode('gbk')) #windows默認gbk編碼
2.14.3 使用struct與json序列化解決黏包
我們還可以把報頭做成字典,字典里包含將要發送的真實數據的詳細信息,然后json序列化,然后用struck將序列化后的數據長度打包成4個字節(4個自己足夠用了)
?
發送時 | 接收時 |
先發報頭長度 | 先收報頭長度,用struct取出來 |
再編碼報頭內容然后發送 | 根據取出的長度收取報頭內容,然后解碼,反序列化 |
最后發真實內容 | 從反序列化的結果中取出待取數據的詳細信息,然后去取真實的數據內容 |
?


#############服務端:定制稍微復雜一點的報頭#################import socket,struct,jsonimport subprocessphone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加 phone.bind(('127.0.0.1',8080))phone.listen(5)while True:conn,addr=phone.accept()while True:cmd=conn.recv(1024)if not cmd:breakprint('cmd: %s' %cmd)res=subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)err=res.stderr.read()print(err)if err:back_msg=errelse:back_msg=res.stdout.read()headers={'data_size':len(back_msg)}head_json=json.dumps(headers)head_json_bytes=bytes(head_json,encoding='utf-8')conn.send(struct.pack('i',len(head_json_bytes))) #先發報頭的長度 conn.send(head_json_bytes) #再發報頭 conn.sendall(back_msg) #在發真實的內容 conn.close()


#########################客戶端#########################from socket import *import struct,jsonip_port=('127.0.0.1',8080)client=socket(AF_INET,SOCK_STREAM)client.connect(ip_port)while True:cmd=input('>>: ')if not cmd:continueclient.send(bytes(cmd,encoding='utf-8'))head=client.recv(4)head_json_len=struct.unpack('i',head)[0]head_json=json.loads(client.recv(head_json_len).decode('utf-8'))data_len=head_json['data_size']recv_size=0recv_data=b''while recv_size < data_len:recv_data+=client.recv(1024)recv_size+=len(recv_data)print(recv_data.decode('utf-8'))#print(recv_data.decode('gbk')) #windows默認gbk編碼
2.15 課堂講解實例解決粘包問題版本1
2.15.1 struct模塊的使用


####################struct模塊的使用#################import structheaders=struct.pack('i',132333)# print(headers,len(headers)) res=struct.unpack('i',headers)print(res[0])
2.15.2 服務端


###################服務端#############from socket import *import subprocessimport structserver=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()#1、制作固定長度的報頭 total_size = len(stdout) + len(stderr)headers=struct.pack('i',total_size)#2、先發送命令長度 conn.send(headers)#3、發送命令的執行結果 conn.send(stdout)conn.send(stderr)except ConnectionResetError:breakconn.close()server.close()
2.15.3 客戶端


##########################客戶端######################from socket import *import structclient=socket(AF_INET,SOCK_STREAM)client.connect(('127.0.0.1',8093))while True:cmd=input('>>: ').strip()if not cmd:continueclient.send(cmd.encode('utf-8'))#1、先接收命令長度 headers=client.recv(4)total_size = struct.unpack('i', headers)[0]#2、再收命令的結果 recv_size=0data=b''while recv_size < total_size:recv_data=client.recv(1024)data+=recv_datarecv_size+=len(recv_data)print(data.decode('gbk'))client.close()
2.16 課堂講解實例解決粘包問題版本2
2.16.1 struct模塊的使用


##################### struct模塊的使用###################import struct# headers=struct.pack('q',13233322222222222)# print(headers,len(headers))# res=struct.unpack('i',headers)# print(res[0])import jsonheaders={'filepath' : 'a.txt','md5' : '123sxd123x123','total_size' : 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111213123123123123123123111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111}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))
2.16.2 服務端


##########################服務端######################from socket import *import subprocessimport structimport jsonserver=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()#1、制作報頭 headers = {'filepath': 'a.txt','md5': '123sxd123x123','total_size': len(stdout) + len(stderr)}headers_json = json.dumps(headers)headers_bytes = headers_json.encode('utf-8')#2、先發報頭的長度 conn.send(struct.pack('i',len(headers_bytes)))#3、發送報頭 conn.send(headers_bytes)#4、發送命令的執行結果 conn.send(stdout)conn.send(stderr)except ConnectionResetError:breakconn.close()server.close()
2.16.3 客戶端


######################客戶端############################from socket import *import structimport jsonclient=socket(AF_INET,SOCK_STREAM)client.connect(('127.0.0.1',8093))while True:cmd=input('>>: ').strip()if not cmd:continueclient.send(cmd.encode('utf-8'))#1、先接收報頭的長度 headers_size=struct.unpack('i',client.recv(4))[0]#2、再收報頭 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']#3、再收命令的結果 recv_size=0data=b''while recv_size < total_size:recv_data=client.recv(1024)data+=recv_datarecv_size+=len(recv_data)print(data.decode('gbk'))client.close()
2.17 FTP作業:上傳下載文件
2.17.1 原博客實例


####################服務端#################import socketimport structimport jsonimport subprocessimport osclass MYTCPServer:address_family = socket.AF_INETsocket_type = socket.SOCK_STREAMallow_reuse_address = Falsemax_packet_size = 8192coding='utf-8'request_queue_size = 5server_dir='file_upload'def __init__(self, server_address, bind_and_activate=True):"""Constructor. May be extended, do not override."""self.server_address=server_addressself.socket = socket.socket(self.address_family,self.socket_type)if bind_and_activate:try:self.server_bind()self.server_activate()except:self.server_close()raisedef server_bind(self):"""Called by constructor to bind the socket."""if self.allow_reuse_address:self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)self.socket.bind(self.server_address)self.server_address = self.socket.getsockname()def server_activate(self):"""Called by constructor to activate the server."""self.socket.listen(self.request_queue_size)def server_close(self):"""Called to clean-up the server."""self.socket.close()def get_request(self):"""Get the request and client address from the socket."""return self.socket.accept()def close_request(self, request):"""Called to clean up an individual request."""request.close()def run(self):while True:self.conn,self.client_addr=self.get_request()print('from client ',self.client_addr)while True:try:head_struct = self.conn.recv(4)if not head_struct:breakhead_len = struct.unpack('i', head_struct)[0]head_json = self.conn.recv(head_len).decode(self.coding)head_dic = json.loads(head_json)print(head_dic)#head_dic={'cmd':'put','filename':'a.txt','filesize':123123} cmd=head_dic['cmd']if hasattr(self,cmd):func=getattr(self,cmd)func(head_dic)except Exception:breakdef put(self,args):file_path=os.path.normpath(os.path.join(self.server_dir,args['filename']))filesize=args['filesize']recv_size=0print('----->',file_path)with open(file_path,'wb') as f:while recv_size < filesize:recv_data=self.conn.recv(self.max_packet_size)f.write(recv_data)recv_size+=len(recv_data)print('recvsize:%s filesize:%s' %(recv_size,filesize))tcpserver1=MYTCPServer(('127.0.0.1',8080))tcpserver1.run()#下列代碼與本題無關class MYUDPServer:"""UDP server class."""address_family = socket.AF_INETsocket_type = socket.SOCK_DGRAMallow_reuse_address = Falsemax_packet_size = 8192coding='utf-8'def get_request(self):data, client_addr = self.socket.recvfrom(self.max_packet_size)return (data, self.socket), client_addrdef server_activate(self):# No need to call listen() for UDP.passdef shutdown_request(self, request):# No need to shutdown anything. self.close_request(request)def close_request(self, request):# No need to close anything.pass


#####################客戶端##################import socketimport structimport jsonimport osclass MYTCPClient:address_family = socket.AF_INETsocket_type = socket.SOCK_STREAMallow_reuse_address = Falsemax_packet_size = 8192coding='utf-8'request_queue_size = 5def __init__(self, server_address, connect=True):self.server_address=server_addressself.socket = socket.socket(self.address_family,self.socket_type)if connect:try:self.client_connect()except:self.client_close()raisedef client_connect(self):self.socket.connect(self.server_address)def client_close(self):self.socket.close()def run(self):while True:inp=input(">>: ").strip()if not inp:continuel=inp.split()cmd=l[0]if hasattr(self,cmd):func=getattr(self,cmd)func(l)def put(self,args):cmd=args[0]filename=args[1]if not os.path.isfile(filename):print('file:%s is not exists' %filename)returnelse:filesize=os.path.getsize(filename)head_dic={'cmd':cmd,'filename':os.path.basename(filename),'filesize':filesize}print(head_dic)head_json=json.dumps(head_dic)head_json_bytes=bytes(head_json,encoding=self.coding)head_struct=struct.pack('i',len(head_json_bytes))self.socket.send(head_struct)self.socket.send(head_json_bytes)send_size=0with open(filename,'rb') as f:for line in f:self.socket.send(line)send_size+=len(line)print(send_size)else:print('upload successful')client=MYTCPClient(('127.0.0.1',8080))client.run()
2.17.2 課堂講解實例


#######################服務端#######################import socketimport osimport jsonimport structSHARE_DIR=r'F:\Python周末20期\day8\08 上傳下載文件\SHARE'class FtpServer:def __init__(self,host,port):self.host=hostself.port=portself.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:breakparams=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:breakself.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):#1、制作報頭 headers = {'filename': filename,'md5': '123sxd123x123','filesize': os.path.getsize(filepath)}headers_json = json.dumps(headers)headers_bytes = headers_json.encode('utf-8')#2、先發報頭的長度 self.conn.send(struct.pack('i',len(headers_bytes)))#3、發送報頭 self.conn.send(headers_bytes)#4、發送真實的數據 with open(filepath,'rb') as f:for line in f:self.conn.send(line)def put(self):passif __name__ == '__main__':server=FtpServer('127.0.0.1',8081)server.serve_forever()


#####################客戶端#######################import socketimport structimport jsonimport osDOWNLOAD_DIR=r'F:\Python周末20期\day8\08 上傳下載文件\DOWNLOAD'class FtpClient:def __init__(self,host,port):self.host=hostself.port=portself.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.txtif not data:continueparams=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'))# 1、先接收報頭的長度 headers_size = struct.unpack('i', self.client.recv(4))[0]# 2、再收報頭 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)# 3、再收真實的數據 with open(filepath, 'wb') as f:recv_size = 0while 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()
2.18 認證客戶端的鏈接合法性
如果你想在分布式系統中實現一個簡單的客戶端鏈接認證功能,又不像SSL那么復雜,那么利用hmac+加鹽的方式來實現
2.18.1 服務端


#######################服務端######################_*_coding:utf-8_*___author__ = 'Linhaifeng'from socket import *import hmac,ossecret_key=b'linhaifeng bang bang bang'def conn_auth(conn):'''認證客戶端鏈接:param conn::return:'''print('開始驗證新鏈接的合法性')msg=os.urandom(32)conn.sendall(msg)h=hmac.new(secret_key,msg)digest=h.digest()respone=conn.recv(len(digest))return hmac.compare_digest(respone,digest)def data_handler(conn,bufsize=1024):if not conn_auth(conn):print('該鏈接不合法,關閉')conn.close()returnprint('鏈接合法,開始通信')while True:data=conn.recv(bufsize)if not data:breakconn.sendall(data.upper())def server_handler(ip_port,bufsize,backlog=5):'''只處理鏈接:param ip_port::return:'''tcp_socket_server=socket(AF_INET,SOCK_STREAM)tcp_socket_server.bind(ip_port)tcp_socket_server.listen(backlog)while True:conn,addr=tcp_socket_server.accept()print('新連接[%s:%s]' %(addr[0],addr[1]))data_handler(conn,bufsize)if __name__ == '__main__':ip_port=('127.0.0.1',9999)bufsize=1024server_handler(ip_port,bufsize)
2.18.2 客戶端(合法)


#########################客戶端(合法)######################_*_coding:utf-8_*___author__ = 'Linhaifeng'from socket import *import hmac,ossecret_key=b'linhaifeng bang bang bang'def conn_auth(conn):'''驗證客戶端到服務器的鏈接:param conn::return:'''msg=conn.recv(32)h=hmac.new(secret_key,msg)digest=h.digest()conn.sendall(digest)def client_handler(ip_port,bufsize=1024):tcp_socket_client=socket(AF_INET,SOCK_STREAM)tcp_socket_client.connect(ip_port)conn_auth(tcp_socket_client)while True:data=input('>>: ').strip()if not data:continueif data == 'quit':breaktcp_socket_client.sendall(data.encode('utf-8'))respone=tcp_socket_client.recv(bufsize)print(respone.decode('utf-8'))tcp_socket_client.close()if __name__ == '__main__':ip_port=('127.0.0.1',9999)bufsize=1024client_handler(ip_port,bufsize)
2.18.3 客戶端(非法:不知道加密方式)


##########################客戶端(非法:不知道加密方式)###################_*_coding:utf-8_*___author__ = 'Linhaifeng'from socket import *def client_handler(ip_port,bufsize=1024):tcp_socket_client=socket(AF_INET,SOCK_STREAM)tcp_socket_client.connect(ip_port)while True:data=input('>>: ').strip()if not data:continueif data == 'quit':breaktcp_socket_client.sendall(data.encode('utf-8'))respone=tcp_socket_client.recv(bufsize)print(respone.decode('utf-8'))tcp_socket_client.close()if __name__ == '__main__':ip_port=('127.0.0.1',9999)bufsize=1024client_handler(ip_port,bufsize)
2.18.4 客戶端(非法:不知道secret_key)


#########################客戶端(非法:不知道secret_key)####################_*_coding:utf-8_*___author__ = 'Linhaifeng'from socket import *import hmac,ossecret_key=b'linhaifeng bang bang bang1111'def conn_auth(conn):'''驗證客戶端到服務器的鏈接:param conn::return:'''msg=conn.recv(32)h=hmac.new(secret_key,msg)digest=h.digest()conn.sendall(digest)def client_handler(ip_port,bufsize=1024):tcp_socket_client=socket(AF_INET,SOCK_STREAM)tcp_socket_client.connect(ip_port)conn_auth(tcp_socket_client)while True:data=input('>>: ').strip()if not data:continueif data == 'quit':breaktcp_socket_client.sendall(data.encode('utf-8'))respone=tcp_socket_client.recv(bufsize)print(respone.decode('utf-8'))tcp_socket_client.close()if __name__ == '__main__':ip_port=('127.0.0.1',9999)bufsize=1024client_handler(ip_port,bufsize)
2.19 socketserver實現并發
解讀socketserver源碼http://www.cnblogs.com/Eva-J/p/5081851.html
?
2.19.1 server端


########################### server端#####################import socketserverclass Myserver(socketserver.BaseRequestHandler):def handle(self):self.data = self.request.recv(1024).strip()print("{} wrote:".format(self.client_address[0]))print(self.data)self.request.sendall(self.data.upper())if __name__ == "__main__":HOST, PORT = "127.0.0.1", 9999# 設置allow_reuse_address允許服務器重用地址 socketserver.TCPServer.allow_reuse_address = True# 創建一個server, 將服務地址綁定到127.0.0.1:9999 server = socketserver.TCPServer((HOST, PORT),Myserver)# 讓server永遠運行下去,除非強制停止程序 server.serve_forever()
2.19.2 client端


################################# client端#######################import socketHOST, PORT = "127.0.0.1", 9999data = "hello"# 創建一個socket鏈接,SOCK_STREAM代表使用TCP協議 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:sock.connect((HOST, PORT)) # 鏈接到客戶端 sock.sendall(bytes(data + "\n", "utf-8")) # 向服務端發送數據 received = str(sock.recv(1024), "utf-8")# 從服務端接收數據print("Sent: {}".format(data))print("Received: {}".format(received))
2.19.3 FtpServer


###########################FtpServer####################import socketserverimport structimport jsonimport osclass FtpServer(socketserver.BaseRequestHandler):coding='utf-8'server_dir='file_upload'max_packet_size=1024BASE_DIR=os.path.dirname(os.path.abspath(__file__))def handle(self):print(self.request)while True:data=self.request.recv(4)data_len=struct.unpack('i',data)[0]head_json=self.request.recv(data_len).decode(self.coding)head_dic=json.loads(head_json)# print(head_dic) cmd=head_dic['cmd']if hasattr(self,cmd):func=getattr(self,cmd)func(head_dic)def put(self,args):file_path = os.path.normpath(os.path.join(self.BASE_DIR,self.server_dir,args['filename']))filesize = args['filesize']recv_size = 0print('----->', file_path)with open(file_path, 'wb') as f:while recv_size < filesize:recv_data = self.request.recv(self.max_packet_size)f.write(recv_data)recv_size += len(recv_data)print('recvsize:%s filesize:%s' % (recv_size, filesize))ftpserver=socketserver.ThreadingTCPServer(('127.0.0.1',8080),FtpServer)ftpserver.serve_forever()
2.19.4 FtpClient


########################### FtpClient########################import socketimport structimport jsonimport osclass MYTCPClient:address_family = socket.AF_INETsocket_type = socket.SOCK_STREAMallow_reuse_address = Falsemax_packet_size = 8192coding='utf-8'request_queue_size = 5def __init__(self, server_address, connect=True):self.server_address=server_addressself.socket = socket.socket(self.address_family,self.socket_type)if connect:try:self.client_connect()except:self.client_close()raisedef client_connect(self):self.socket.connect(self.server_address)def client_close(self):self.socket.close()def run(self):while True:inp=input(">>: ").strip()if not inp:continuel=inp.split()cmd=l[0]if hasattr(self,cmd):func=getattr(self,cmd)func(l)def put(self,args):cmd=args[0]filename=args[1]if not os.path.isfile(filename):print('file:%s is not exists' %filename)returnelse:filesize=os.path.getsize(filename)head_dic={'cmd':cmd,'filename':os.path.basename(filename),'filesize':filesize}print(head_dic)head_json=json.dumps(head_dic)head_json_bytes=bytes(head_json,encoding=self.coding)head_struct=struct.pack('i',len(head_json_bytes))self.socket.send(head_struct)self.socket.send(head_json_bytes)send_size=0with open(filename,'rb') as f:for line in f:self.socket.send(line)send_size+=len(line)print(send_size)else:print('upload successful')client=MYTCPClient(('127.0.0.1',8080))client.run()
第3章 作業
?
?
?
?