python網路編程-異常處理-異常捕獲-拋出異常-斷言-自定義異常-UDP通訊-socketserver模組應用-03
- 2019 年 10 月 7 日
- 筆記
異常捕獲
異常:程式在運行過程中出現了不可預知的錯誤,並且該錯誤沒有對應的處理機制,那麼就會以異常的形式表現出來
影響:整個程式無法再正常運行

異常的結構
異常的類型
NameError
異常的資訊
name 'safaf' is not defined
異常的位置
''' Traceback (most recent call last): File "E:/PyCharm 2019.1.3/ProjectFile/day010/day029/01 異常處理.py", line 1, in <module> safaf '''
異常的種類
語法錯誤
程式錯誤編譯成一般都會直接有提示,提示就應該直接解決的,出現語法錯誤一般是基礎太差。
邏輯錯誤
這種錯誤是可以被容忍的,邏輯錯誤無法一眼看出來
針對邏輯上的錯誤可以採用異常處理機制來進行捕獲
常見的錯誤類型
常見的出錯類型 | 原因 |
|---|---|
NameError | 名字錯誤 |
SyntaxError | 語法錯誤 |
keyError | 鍵不存在 |
ValueError | 值錯誤 |
IndexError | 索引錯誤 |
如何避免
異常處理
使用:在你認為可能會出現問題的程式碼上方用 try 包起來
注意:try 內部的程式碼塊越少越好 異常處理能少用就少用,不然該報錯的不報錯哪裡錯了你都不知道 像服務端、客戶端斷開鏈接就需要用到異常處理,你不知道他什麼時候會斷開
異常處理使用模板(try.. except.. else.. finally..)
try: 可能出錯的程式碼 except 出錯類型 as e: # 將報錯資訊賦值給e 錯誤的處理程式碼 except 出錯類型2 as e: # 將報錯資訊賦值給e 錯誤的處理程式碼 except 出錯類型3 as e: # 將報錯資訊賦值給e 錯誤的處理程式碼 # 或者 Exception直接捕獲所有的 except Exception as e: # 可以匹配所有的錯誤類型 BaseException 是 Exception 的父類,也可以捕獲所有類型 錯誤的處理程式碼 else: 當被檢測的程式碼沒有任何異常發生,才會執行這裡面的語句 finally: 無論被檢測的程式碼有沒有異常發生,都會在程式碼執行完後執行這裡面的程式碼
案例
try: safaf except NameError as e: print(e) else: print("沒有報錯才會執行我哦~") finally: print("管你報不報錯,我都會執行的!") # name 'safaf' is not defined # 管你報不報錯,我都會執行的! try: print("我沒錯哦~") except Exception as e: # 捕捉所有異常 print("管你啥錯,我都抓了!") else: print("沒有報錯才會執行我哦~") finally: print("管你報不報錯,我都會執行的!") # 我沒錯哦~ # 沒有報錯才會執行我哦~ # 管你報不報錯,我都會執行的!補充
出錯後會立即停止程式碼運行,去與except中的錯誤類型一個個比較,匹配上了就執行裡面的程式碼,沒匹配上就直接報錯

主動拋出異常raise
if 'egon' == 'DSB': pass else: raise TypeError('盡說大實話') # 這一行就是報錯的位置 # raise 關鍵字就是用來主動拋出異常的斷言assert
斷言不成立直接報錯
l = [1, 2, 3] assert len(1) < 0 # assert 斷言,預言,猜某個數據的狀態,猜對了不影響程式碼執行,猜錯了直接報錯
自定義異常(繼承異常類)
報錯類型,其實對應的就是一個個類(可以自定義拼接異常的格式)
class MyError(BaseException): def __init__(self, msg): super().__init__() self.msg = msg def __str__(self): return f'----<{self.msg}>----' raise MyError('自定義的異常') # Traceback (most recent call last): # File "E:/PyCharm 2019.1.3/ProjectFile/day010/day029/test.py", line 15, in <module> # raise MyError('自定義的異常') # __main__.MyError: ----<自定義的異常>----UDP通訊
UDP協議又叫用戶數據報協議
它沒有雙向通道,類似於發簡訊(只管發,不管對方有沒有收到,不需要對方立即回應)
UDP的程式可以先啟動客戶端再啟動服務端(客戶端發數據給服務端之前都沒問題)
UDP類似於發簡訊
TCP類似於打電話,你一句我一句的
普通使用
服務端
import socket server = socket.socket(type=socket.SOCK_DGRAM) # type=socket.SOCK_DGRAM 指定成 UDP 協議 type=socket.SOCK_STREAM TCP協議(默認就是,不用指定) server.bind(('127.0.0.1', 8080)) # UDP 不需要設置半連接池(server.listen(5)),也沒有半連接池的概念 # UDP 沒有雙向通道,所以也不需要建立連接(conn, addr = server.accept()) # 直接就是通訊循環 while True: # 這裡只需要直接通訊(交互)即可 data, addr = server.recvfrom(1024) print("數據:", data.decode('utf-8')) # 客戶端發來的消息 print("地址:", addr) # 客戶端的地址 re_msg = input("Please input your response msg:").strip() # 會阻塞在這裡,這裡過了,才能發出資訊,看到下一條資訊 server.sendto(re_msg.encode('utf-8'), addr) # 向客戶端發送消息 # 數據: hi # 地址: ('127.0.0.1', 64821) # Please input your response msg:o hi # 數據: hihihi # 地址: ('127.0.0.1', 64823) # Please input your response msg:xxixixi # 數據: aha e # 地址: ('127.0.0.1', 64828) # Please input your response msg:emmm? # 數據: # 地址: ('127.0.0.1', 64828) # Please input your response msg:adsa客戶端
import socket client = socket.socket(type=socket.SOCK_DGRAM) # UDP 不需要建立連接(client.connect(('127.0.0.1', 8080))) server_addr = ('127.0.0.1', 8080) # UDP sendto發消息時需要一個伺服器的地址及埠號 while True: msg = input("Please input your msg:").strip() client.sendto(msg.encode('utf-8'), server_addr) # 向伺服器發送數據,要附帶伺服器端地址及埠(基於網路傳輸的數據都必須是二進位的) data, msg_from_server_addr = client.recvfrom(1024) # 收到消息,且得到地址 print("服務端發來的數據:", data.decode('utf-8')) print("伺服器端的ip及埠", msg_from_server_addr) # 窗口1 # Please input your msg:hi # 服務端發來的數據: o hi # 伺服器端的ip及埠 ('127.0.0.1', 8080) # Please input your msg: # 窗口2 # Please input your msg:hihihi # 服務端發來的數據: xxixixi # 伺服器端的ip及埠 ('127.0.0.1', 8080) # Please input your msg: # 窗口3 # Please input your msg:aha e # # 服務端發來的數據: emmm? # 伺服器端的ip及埠 ('127.0.0.1', 8080) # Please input your msg:服務端發來的數據: adsa # 伺服器端的ip及埠 ('127.0.0.1', 8080) # Please input your msg:TCP與UDP之間的區別
- UDP 允許發空數據,不會有影響
- UDP 直接啟動客戶端未啟動服務端不會報錯
- UDP 不會有粘包問題(自帶報頭)
- UDP 支援並發
服務端
import socket server = socket.socket(type=socket.SOCK_DGRAM) server.bind(('127.0.0.1', 8080)) while True: data, addr = server.recvfrom(1024) print(data.decode('utf-8')) server.sendto(data.upper(), addr)客戶端
import socket client = socket.socket(type=socket.SOCK_DGRAM) while True: msg = input(">>>:") # ------------------------------------------- # 1.UDP 允許發空數據,不會有影響 # ------------------------------------------- # UDP自帶報頭,就算數據為空,另一端也能處理 client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080)) # 第二參數,目標伺服器地址 # ------------------------------------------- # 2.UDP 直接啟動客戶端未啟動服務端不會報錯 # 發數據找不到服務端也還是會報錯 # ------------------------------------------- # 下面兩行程式碼直接注釋掉,服務端沒啟動,都不會報錯,只管給伺服器發(收沒收到不管) # data, server_addr = client.recvfrom(1024) # print(data) # ------------------------------------------- # 3.UDP 不會有粘包問題(自帶報頭) # ------------------------------------------- # client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080)) # client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080)) # client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080)) # # server.sendto(data.upper(), addr) # server.sendto(data.upper(), addr) # server.sendto(data.upper(), addr) # ------------------------------------------- # 4.UDP 支援並發 # TCP是保持連接,而UDP不需要保持連接 # 與一個客戶端斷開連接才會和下一個客戶端建立連接 # -------------------------------------------簡易版的QQ
伺服器端
import socket server = socket.socket(type=socket.SOCK_DGRAM) server.bind(('127.0.0.1', 8080)) while True: data, client_addr = server.recvfrom(1024) print(data.decode('utf-8')) msg = input(">>>:").strip() server.sendto(msg.encode('utf-8'), client_addr) # 來自star3的消息:helo # >>>:hi # 來自star2的消息:aha # >>>:haa # 來自star的消息:hello world # >>>:ha # 來自star2的消息:jason nnn # >>>:jj客戶端1、2、3共用同一份程式碼
import socket client = socket.socket(type=socket.SOCK_DGRAM) username = 'star' # 客戶端對應改成 star2 star3 server_address = ('127.0.0.1', 8080) # 指定一個發消息的目標伺服器 while True: msg = input(">>>:").strip() msg = f'來自{username}的消息:{msg}' # 是哪個用戶名發出的數據不應該由這裡傳過去,用戶可以隨便改,實際意義不大 ''' user_dict = { "username1": (ip1 + port1), "username2": (ip2 + port2), "username3": (ip3 + port3), } # 可以在每個端都存這樣一個對照表,根據ip與port就可以知道用戶名了 ''' client.sendto(msg.encode('utf-8'), server_address) data, server_addr = client.recvfrom(1024) # server_addr 收到消息的服務端地址 print(data.decode('utf-8')) # 各個窗口的控制台輸入與輸出 # >>>:helo # hi # >>>: # >>>:aha # haa # >>>:jason nnn # jj # >>>: # >>>:hello world # ha # >>>:概念科普
- 並發
看起來像同時運行(直接啟動多個UDP客戶端)(默認的UDP程式多開就是這個效果)
- 並行
真正意義上的同時運行
socketserver模組科普
是給服務端用的(客戶端還是用socket模組),能夠支援服務端的並發
TCP模擬UDP實現並發
伺服器端
import socketserver # 文件名不要和模組衝突了,不然都不知道導哪個了 class MyServer(socketserver.BaseRequestHandler): def handle(self): # 與客戶端進行通訊 # print("來啦 老弟") while True: # 需要保持通訊(後續 client.send() 可沒有附帶伺服器地址, connect 被關閉了) data = self.request.recv(1024) print(self.client_address) # 客戶端地址 print(data.decode('utf-8')) self.request.send(data.upper()) if __name__ == '__main__': '''只要有客戶端連接,會自動交給自定義類中的handle方法去處理''' server = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MyServer) # 創建一個基於TCP的對象 server.serve_forever() # 啟動該服務對象 # ('127.0.0.1', 14327) # dAddA # ('127.0.0.1', 14326) # ADD # ('127.0.0.1', 14325) # ADsafga客戶端
# TCP 實現UDP import socket client = socket.socket() client.connect(('127.0.0.1', 8080)) while True: res = input(">>>:") client.send(res.encode('utf-8')) data = client.recv(1024) print(data.decode('utf-8')) # 窗口1 控制台數據(輸入與輸出) # >>>:dAddA # DADDA # >>>: # 窗口2 控制台數據(輸入與輸出) # >>>:ADD # ADD # >>>: # 窗口1 控制台數據(輸入與輸出) # >>>:ADsafga # ADSAFGA # >>>:socketserver之UDP
伺服器端
import socketserver # 文件名不要和模組衝突了,不然都不知道導哪個了 class MyServer(socketserver.BaseRequestHandler): def handle(self): # 與客戶端進行通訊 # while True: # UDP 不需要通訊循環,每次 sendto 都有伺服器的地址 data, sock = self.request print(self.client_address) # 客戶端地址 print(data.decode('utf-8')) sock.sendto(data.lower(), self.client_address) if __name__ == '__main__': '''只要有客戶端連接,會自動交給自定義類中的handle方法去處理''' server = socketserver.ThreadingUDPServer(('127.0.0.1', 8080), MyServer) # 創建一個基於UDP的對象 server.serve_forever() # 啟動該服務對象 # 控制台列印的數據 # ('127.0.0.1', 52524) # CLient2 # ('127.0.0.1', 52529) # clet1 # ('127.0.0.1', 52529) # CLienT1 # ('127.0.0.1', 54485) # CLiEnt3客戶端
import socket client = socket.socket(type=socket.SOCK_DGRAM) server_addr = ('127.0.0.1', 8080) while True: res = input(">>>:").strip() client.sendto(res.encode('utf-8'), server_addr) data, response_server_addr = client.recvfrom(1024) print(data.decode('utf-8'), response_server_addr) # 窗口1 控制台數據(輸入與輸出) # >>>:clIeNt1 # clet1 ('127.0.0.1', 8080) # >>>:CLienT1 # client1 ('127.0.0.1', 8080) # >>>: # 窗口2 控制台數據(輸入與輸出) # >>>:CLient2 # client2 ('127.0.0.1', 8080) # >>>: # 窗口1 控制台數據(輸入與輸出) # >>>:CLiEnt3 # client3 ('127.0.0.1', 8080) # >>>:為什麼UDP時重寫的handle方法里不用寫通訊循環
handle 是處理一次連接請求的,handle結束連接就斷開了 UDP是不需要保持(雙向)連接的,所以每次sendto 都是單個請求(都附帶伺服器端地址及埠),不能寫通訊循環(不然就拿著一個sendto 過來的數據循環列印了) 而TCP是基於雙向通道通訊的,handle結束後連接就斷開了(再client.send() 這個連接狀態就已經不存在了),所以TCP這邊的服務端要寫通訊循環保持連接來多次通訊