一、客戶端/服務器架構
1.1基本概念
客戶端/服務器架構即:C/S架構,包括:
(1)硬件C/S架構(打印機)
(2)軟件C/S架構(Web服務器)
1.2舉例
生活中的C/S架構:飯店為S端,所有食客C端
二、TCP/udp/osi七層
2.1TCP/UDP協議
TCP(Transmission Control Protocol)可靠的、面向連接的協議(eg:打電話)、傳輸效率低全雙工通信(發送緩存&接收緩存)、面向字節流。使用TCP的應用:Web瀏覽器;電子郵件、文件傳輸程序。
UDP(User Datagram Protocol)不可靠的、無連接的服務,傳輸效率高(發送前時延小),一對一、一對多、多對一、多對多、面向報文,盡最大努力服務,無擁塞控制。使用UDP的應用:域名系統 (DNS);視頻流;IP語音(VoIP)。
2.1tcp的三次握手
所謂三次握手(Three-Way Handshake)即建立TCP連接,就是指建立一個TCP連接時,需要客戶端和服務端總共發送3個包以確認連接的建立。在socket編程中,這一過程由客戶端執行connect來觸發,整個流程如下圖所示:
(1)第一次握手:Client將標志位SYN置為1,隨機產生一個值seq=J,並將該數據包發送給Server,Client進入SYN_SENT狀態,等待Server確認。
(2)第二次握手:Server收到數據包后由標志位SYN=1知道Client請求建立連接,Server將標志位SYN和ACK都置為1,ack=J+1,隨機產生一個值seq=K,並將該數據包發送給Client以確認連接請求,Server進入SYN_RCVD狀態。
(3)第三次握手:Client收到確認后,檢查ack是否為J+1,ACK是否為1,如果正確則將標志位ACK置為1,ack=K+1,並將該數據包發送給Server,Server檢查ack是否為K+1,ACK是否為1,如果正確則連接建立成功,Client和Server進入ESTABLISHED狀態,完成三次握手,隨后Client與Server之間可以開始傳輸數據了。
簡單來說,就是
1、建立連接時,客戶端發送SYN包(SYN=i)到服務器,並進入到SYN-SEND狀態,等待服務器確認
2、服務器收到SYN包,必須確認客戶的SYN(ack=i+1),同時自己也發送一個SYN包(SYN=k),即SYN+ACK包,此時服務器進入SYN-RECV狀態
3、客戶端收到服務器的SYN+ACK包,向服務器發送確認報ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手,客戶端與服務器開始傳送數據。
SYN攻擊:
在三次握手過程中,Server發送SYN-ACK之后,收到Client的ACK之前的TCP連接稱為半連接(half-open connect),此時Server處於SYN_RCVD狀態,當收到ACK后,Server轉入ESTABLISHED狀態。SYN攻擊就是Client在短時間內偽造大量不存在的IP地址,並向Server不斷地發送SYN包,Server回復確認包,並等待Client的確認,由於源地址是不存在的,因此,Server需要不斷重發直至超時,這些偽造的SYN包將產時間占用未連接隊列,導致正常的SYN請求因為隊列滿而被丟棄,從而引起網絡堵塞甚至系統癱瘓。SYN攻擊時一種典型的DDOS攻擊,檢測SYN攻擊的方式非常簡單,即當Server上有大量半連接狀態且源IP地址是隨機的,則可以斷定遭到SYN攻擊了,使用如下命令可以讓之現行:
#netstat -nap | grep SYN_RECV
2.2tcp的四次揮手
所謂四次揮手(Four-Way Wavehand)即終止TCP連接,就是指斷開一個TCP連接時,需要客戶端和服務端總共發送4個包以確認連接的斷開。在socket編程中,這一過程由客戶端或服務端任一方執行close來觸發,整個流程如下圖所示:
由於TCP連接時全雙工的,因此,每個方向都必須要單獨進行關閉,這一原則是當一方完成數據發送任務后,發送一個FIN來終止這一方向的連接,收到一個FIN只是意味着這一方向上沒有數據流動了,即不會再收到數據了,但是在這個TCP連接上仍然能夠發送數據,直到這一方向也發送了FIN。首先進行關閉的一方將執行主動關閉,而另一方則執行被動關閉,上圖描述的即是如此。
(1)第一次揮手:Client發送一個FIN,用來關閉Client到Server的數據傳送,Client進入FIN_WAIT_1狀態。
(2)第二次揮手:Server收到FIN后,發送一個ACK給Client,確認序號為收到序號+1(與SYN相同,一個FIN占用一個序號),Server進入CLOSE_WAIT狀態。
(3)第三次揮手:Server發送一個FIN,用來關閉Server到Client的數據傳送,Server進入LAST_ACK狀態。
(4)第四次揮手:Client收到FIN后,Client進入TIME_WAIT狀態,接着發送一個ACK給Server,確認序號為收到序號+1,Server進入CLOSED狀態,完成四次揮手。
為什么要建立三次握手四次揮手呢?
這是因為服務端在LISTEN狀態下,收到建立連接請求的SYN報文后,把ACK和SYN放在一個報文里發送給客戶端。而關閉連接時,當收到對方的FIN報文時,僅僅表示對方不再發送數據了但是還能接收數據,己方也未必全部數據都發送給對方了,所以己方可以立即close,也可以發送一些數據給對方后,再發送FIN報文給對方來表示同意現在關閉連接,因此,己方ACK和FIN一般都會分開發送。
為什么TIME_WAIT狀態需要經過2MSL(最大報文段生存時間)才能返回到CLOSE狀態?
原因有二: 一、保證TCP協議的全雙工連接能夠可靠關閉 二、保證這次連接的重復數據段從網絡中消失
先說第一點,如果Client直接CLOSED了,那么由於IP協議的不可靠性或者是其它網絡原因,導致Server沒有收到Client最后回復的ACK。那么Server就會在超時之后繼續發送FIN,此時由於Client已經CLOSED了,就找不到與重發的FIN對應的連接,最后Server就會收到RST而不是ACK,Server就會以為是連接錯誤把問題報告給高層。這樣的情況雖然不會造成數據丟失,但是卻導致TCP協議不符合可靠連接的要求。所以,Client不是直接進入CLOSED,而是要保持TIME_WAIT,當再次收到FIN的時候,能夠保證對方收到ACK,最后正確的關閉連接。
再說第二點,如果Client直接CLOSED,然后又再向Server發起一個新連接,我們不能保證這個新連接與剛關閉的連接的端口號是不同的。也就是說有可能新連接和老連接的端口號是相同的。一般來說不會發生什么問題,但是還是有特殊情況出現:假設新連接和已經關閉的老連接端口號是一樣的,如果前一次連接的某些數據仍然滯留在網絡中,這些延遲數據在建立新連接之后才到達Server,由於新連接和老連接的端口號是一樣的,又因為TCP協議判斷不同連接的依據是socket pair,於是,TCP協議就認為那個延遲的數據是屬於新連接的,這樣就和真正的新連接的數據包發生混淆了。所以TCP連接還要在TIME_WAIT狀態等待2倍MSL,這樣可以保證本次連接的所有數據都從網絡中消失。
2.3為什么Tcp連接的創立需要三次握手 ,而斷開需要四次揮手
這是經典的三次握手,為啥是3次?
剛開始不明白,后來讀了一些書,看了一些資料,了解了基於TCP協議傳遞信息流的通信方式。
發送方以確定SYN標志,同時生成一個ISN(初始序列號),也就是消息序號來發送信息(消息字節數n)。接收方如果收到了信息,會以ACK標志和下次需要對方傳遞的序號值發送給對方,ack標志告訴對方我已經收到了信息,傳遞序號ISN+n告訴對方下次從這個序號的地方開始發送。
兩次消息的傳遞,意味着一次通信的完成。后面消息的序號都是基於ISN和傳遞消息的字節數逐漸累加計算得來。
TCP協議的通信方式規定是這樣的。同時,基於tcp協議的雙方是雙全工的,也就是說通信雙方都可以向對方發送消息,也都可以獨立關閉自己一方的通信通道。
基於通信方式和雙全工的特性,所以在tcp連接建立時
client需要將自己的ISN序號告知對方,同時需要對方的確定。
server也需要將自己的ISN序號告知對方,同時也要對方的確定。
在上圖中,server將自己的ack和發出的syn標志的告知對方ISN的合並在一次傳遞中,這樣子節省流量。所以三次握手很合理。
至於四次揮手,同樣也是基於以上的原理。尤其是通信雙方都可以獨立關閉自己的通信通道,也就是半關閉。
client先發送FIN告知對方我已經完成數據發送了,server回復ack來確定我知道了。這樣一個流程,就關閉了client的發送信息通道。但是還可以接收來自server方的數據。
server此時已經知道接收不到client的數據了,但是還可以給它發送數據。如果server也沒有啥數據要發送給對方了,server也會以FIN標志位發送一個信息給client,client接到后,也會傳遞一個ack表示知道了。這樣子,雙方都完成了關閉。
2.4 osi七層協議
互聯網的核心就是由一堆協議組成,協議就是標准,比如全世界人通信的標准是英語,如果把計算機比作人,互聯網協議就是計算機界的英語。所有的計算機都學會了互聯網協議,那所有的計算機都就可以按照統一的標准去收發信息從而完成通信了
網絡中的計算機與終端間要想正確的傳送信息和數據,必須在數據傳輸的順序、數據的格式及內容等方面有一個約定或規則,這種約定或規則稱做協議。
應用層:
與其它計算機進行通訊的一個應用,它是對應應用程序的通信服務的。例如,一個沒有通信功能的字處理程序就不能執行通信的代碼,從事字處理工作的程序員也不關心OSI的第7層。但是,如果添加了一個傳輸文件的選項,那么字處理器的程序就需要實現OSI的第7層。示例:TELNET,HTTP,FTP,NFS,SMTP等。
表示層:
這一層的主要功能是定義數據格式及加密。例如,FTP允許你選擇以二進制或ASCII格式傳輸。如果選擇二進制,那么發送方和接收方不改變文件的內容。如果選擇ASCII格式,發送方將把文本從發送方的字符集轉換成標准的ASCII后發送數據。在接收方將標准的ASCII轉換成接收方計算機的字符集。示例:加密,ASCII等。
會話層:
它定義了如何開始、控制和結束一個會話,包括對多個雙向消息的控制和管理,以便在只完成連續消息的一部分時可以通知應用,從而使表示層看到的數據是連續的,在某些情況下,如果表示層收到了所有的數據,則用數據代表表示層。示例:RPC,SQL等。
傳輸層:
這層的功能包括是否選擇差錯恢復協議還是無差錯恢復協議,及在同一主機上對不同應用的數據流的輸入進行復用,還包括對收到的順序不對的數據包的重新排序功能。示例:TCP,UDP,SPX。
網絡層:
這層對端到端的包傳輸進行定義,它定義了能夠標識所有結點的邏輯地址,還定義了路由實現的方式和學習的方式。為了適應最大傳輸單元長度小於包長度的傳輸介質,網絡層還定義了如何將一個包分解成更小的包的分段方法。示例:IP,IPX等。
數據鏈路層:
它定義了在單個鏈路上如何傳輸數據。這些協議與被討論的各種介質有關。示例:ATM,FDDI等
物理層:
OSI的物理層規范是有關傳輸介質的特性,這些規范通常也參考了其他組織制定的標准。連接頭、幀、幀的使用、電流、編碼及光調制等都屬於各種物理層規范中的內容。物理層常用多個規范完成對所有細節的定義。示例:Rj45,802.3等。
三、socket(套接字)
tcp是基於鏈接的,必須先啟動服務端,然后再啟動客戶端去鏈接服務端。
3.1 socket的服務端

import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.bind(('192.168.137.173',7680)) phone.listen(5) print('----->') conn,addr=phone.accept() msg=conn.recv(1024) print('客戶端發來的消息是:',msg) conn.send(msg.upper()) conn.close()
3.2 socket的客戶端

import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('192.168.137.173',7680)) phone.send('hello'.encode('utf-8')) data=phone.recv(1024) print('收到服務端發來的消息是:',data)
四、異常處理
python為每一種異常定制了一個類型,然后提供了一種特定的語法結構用來執行異常處理。
part1:基本語法 try: "被檢測的代碼塊” except異常類型:
4.1異常處理

# age=input('---->>:') # int(age) # age=input('---->>:') # if age.isdigit(): # int(age) #主邏輯 # # elif age.isspace(): # print('--->用戶輸入的是空格') # # elif len(age)==0: # print('---->>用戶輸入為空') # # else: # print('其他的非法輸入') #上述代碼的不可取之處在於:如果有相同的代碼,要重復寫。如: #第二段代碼 # age2=input('----->>:') # int(age2) #第三段代碼 # age3=input('----->>:') # int(age3) #出現以上情況,就得反復寫if判斷語句來預防異常情況出現,這種方法不可取 #有人建議加and,如下; # age=input('---->>:') # age2=input('----->>:') # if age.isdigit() and age2.isdigit(): # int(age) #主邏輯 # int(age2) # elif age.isspace and age2.isspace(): # print('--->用戶輸入的是空格') # # elif len(age)==0: # print('---->>用戶輸入為空') # # else: # print('其他的非法輸入') #這種方法的不可取之處在於:如果代碼比較多,就得反復寫and。 # def test(): # print('test running') # choice_dic={ # '1':test # } # # while True: # choice=input('--->>:').strip() # if not choice or choice not in choice_dic: # continue # choice_dic[choice]() #真正的異常處理 # try: # age=input('--->>:') # int(age) # age2=input('---->>') # int(age2) # # except ValueError as e: # print(e) # try: # age=input('--->>:') # int(age) # age2=input('---->>') # int(age2) # l=[1,'asc',23] # l[100] # # except ValueError as e: # print(e) # except KeyError as e: # print('--->:',e) # except IndexError as e: # print('====>:',e) # # print('111') #萬能異常處理 # try: # age=input('--->>:') # int(age) # age2=input('---->>') # int(age2) # l=[1,'asc',23] # l[100] # # except Exception as e: # print(e) # # print('111') # while True: # try: # age = input('--->>:') # int(age) # age2 = input('---->>') # int(age2) # break # except Exception as e: # print("請重新輸入:",e) # # print('111')
4.2異常的其他結構

s1='hello' try: int(s1) except ValueError as e: print(e) except KeyError as e: print('--->:',e) except IndexError as e: print('====>:',e) except Exception as e: print(e) else: print('try內代碼塊沒有異常則執行') finally: print('無論異常與否,都執行該模塊,通常是進行清理工作') print(111) print(22222)
4.3自定義異常
class czdException(BaseException): def __init__(self,msg): self.msg=msg def __str__(self): return self.msg try: raise czdException('類型錯誤') except czdException as e: print(e)
4.4異常的幾種標識
#異常的標識: # AttributeError:試圖訪問一個沒有對象的樹形,比如:foo.x,但是foo沒有屬性x。 # IOError:輸入/輸出異常,基本上式無法打開文件 # ImportError:無法引入模塊或包,基本上是路徑問題或名稱錯誤 # IndexError:下標索引超出序列邊界,比如當x只有三個元素,卻試圖訪問x[5] # IndentationError:語法錯誤(的子類);代碼沒有正確對齊 # KeyError:試圖訪問字典里不存在的鍵 # KeyboardInterrupt:Ctrl+c被按下 # NameError:使用一個還未被賦予對象的變量 # SyntaxError:代碼非法,代碼不能編譯。 # TypeError:傳入對象類型與要求不符 # UnboundLocalError:試圖訪問一個還未被設置的局部變量,基本上是由於另一個同名的全局變量,導致你以為正在訪問它 # ValueError:傳入一個調用者不期望的值,即使值得類型是正確的。
注:二、TCP/udp/osi七層摘自:水一的博客