Python day13--网络编程
一、OSI七層,tcp,udp協(xié)議
?
? ? ? ? 首先,我們今天使用的計(jì)算機(jī)都是要聯(lián)網(wǎng)使用的。很少有那種單機(jī)走遍天下的情況。那么我們的計(jì)算機(jī)是如何通過(guò)網(wǎng)絡(luò)實(shí)現(xiàn)通信的。我們先了解一些關(guān)于網(wǎng)絡(luò)的基礎(chǔ)只是。然后再開(kāi)始學(xué)習(xí)一些關(guān)于網(wǎng)絡(luò)編程的內(nèi)容。第一個(gè)要解釋的名詞叫協(xié)議。我們只有明白協(xié)議是什么,后面再學(xué)習(xí)各種各樣的通信規(guī)則就容易的多了。
? ? ? ? 官方:網(wǎng)絡(luò)協(xié)議是通信計(jì)算機(jī)雙方必須共同遵從的一組約定。那么怎么樣建立連接、怎么樣互相識(shí)別等。只有遵守這個(gè)約定,計(jì)算機(jī)之間才能相互通信交流。
? ? ? ? 普通話:兩臺(tái)計(jì)算機(jī)之間約定好,我發(fā)送的數(shù)據(jù)格式是什么,你接收到數(shù)據(jù)后,使用相同的格式來(lái)拿到數(shù)據(jù)。
舉個(gè)栗子:
你和韓國(guó)人交流,你說(shuō)中文,他說(shuō)韓語(yǔ),你倆是不能明白對(duì)方說(shuō)什么的,怎么辦?你倆約定好,都說(shuō)英語(yǔ),實(shí)現(xiàn)交流;這里這個(gè)約定就叫做協(xié)議。
? ? ? ? 網(wǎng)絡(luò)協(xié)議:互聯(lián)網(wǎng)之間互相傳遞消息的時(shí)候使用統(tǒng)一的一系列約定。
? ? ? ? 再今天的互聯(lián)網(wǎng)數(shù)據(jù)傳輸種一般使用的是OSI七層協(xié)議。也有簡(jiǎn)稱(chēng)為五層,四層協(xié)議。只是對(duì)不同網(wǎng)絡(luò)層的定義不同。內(nèi)部原理和作用是一樣的。
????????
????????每一層分別是做什么的?這里涉及到的網(wǎng)絡(luò)知識(shí)非常的負(fù)責(zé)。只做簡(jiǎn)單介紹,如果想深入研究,請(qǐng)參考大學(xué)課本(計(jì)算機(jī)網(wǎng)絡(luò))。
? ? ? ? 首先是物理層,這一層沒(méi)啥說(shuō)的,作用就是把0101等電信號(hào)發(fā)送出去,也就是我們常說(shuō)的數(shù)模轉(zhuǎn)換與模數(shù)轉(zhuǎn)換(數(shù)字信號(hào)轉(zhuǎn)換模擬信號(hào),模擬信號(hào)轉(zhuǎn)換數(shù)字信號(hào)),物理層也是網(wǎng)絡(luò)傳輸?shù)幕A(chǔ),首先要保證物理連接是沒(méi)有問(wèn)題才能進(jìn)行數(shù)據(jù)傳輸。
? ? ? ? 數(shù)據(jù)鏈路層,這一層負(fù)責(zé)裝配自己和對(duì)方主機(jī)的MAC地址。MAC地址:每個(gè)網(wǎng)絡(luò)設(shè)備唯一的編碼。全球唯一,由不同廠商直接燒錄再網(wǎng)卡上。作用:在龐大的網(wǎng)絡(luò)系統(tǒng)種,你要發(fā)送的數(shù)據(jù)到底是要給誰(shuí),由誰(shuí)發(fā)送出來(lái)的;這就相當(dāng)于你寫(xiě)信時(shí)候的信封,上面得寫(xiě)清楚收信人地址。
? ? ? ? 網(wǎng)絡(luò)層:
? ? ? ? 在有了MAC地址其實(shí)我們的電腦就可以開(kāi)始通信了。但是,此時(shí)的通信方式是廣播。相當(dāng)于通信基本靠吼。你發(fā)送一個(gè)數(shù)據(jù)出去,會(huì)自動(dòng)的發(fā)給當(dāng)前網(wǎng)絡(luò)下的所有計(jì)算機(jī)。然后每個(gè)計(jì)算機(jī)的網(wǎng)卡會(huì)看一眼這個(gè)數(shù)據(jù)是不是發(fā)給自己的。像這樣的通信方式,如果計(jì)算機(jī)的數(shù)據(jù)量少,是沒(méi)有問(wèn)題的。但是,如果全球所有計(jì)算機(jī)都按照這樣的方式來(lái)傳輸消息,那不僅僅是效率的問(wèn)題了,絕對(duì)是災(zāi)難性的。那怎么辦,大家就想到了一個(gè)新的方案,這個(gè)方案叫IP協(xié)議,使用IP協(xié)議就把不同區(qū)域的計(jì)算機(jī)劃分成一個(gè)一個(gè)的子網(wǎng),子網(wǎng)內(nèi)的通信使用廣播來(lái)傳遞消息。廣播外通過(guò)路由進(jìn)行傳遞消息,可以理解為不同快遞公司的分撥中心,我給你寄一個(gè)快遞,先看一下你是不是和我一個(gè)區(qū)域的,是自己區(qū)域直接挨家挨戶(hù)找就OK了。但是如果你不是我這個(gè)區(qū)域的,就通過(guò)你得分撥中心下發(fā)給你,這里IP協(xié)議的作用就體現(xiàn)出來(lái)了。
網(wǎng)絡(luò)層提出了子網(wǎng)(局域網(wǎng))的概念。通過(guò)IP地址和子網(wǎng)掩碼來(lái)劃分子網(wǎng)。
????????傳輸層:
????????我們現(xiàn)在解決了外界的數(shù)據(jù)傳輸問(wèn)題。使用MAC地址和IP地址可以唯一的定位到一臺(tái)計(jì)算機(jī)了。那么我們知道一臺(tái)計(jì)算機(jī)內(nèi)是很有可能運(yùn)行著多個(gè)網(wǎng)絡(luò)應(yīng)用和程序的。比如你開(kāi)著QQ,登陸這微信,還看著芒果TV,還瀏覽的微博。那么此時(shí)你得計(jì)算機(jī)網(wǎng)卡接收到了來(lái)自遠(yuǎn)方的一條數(shù)據(jù),這條數(shù)據(jù)到底給哪個(gè)應(yīng)用程序呢?說(shuō)白了,快遞到你公司了,地址沒(méi)問(wèn)題,可是你公司那么多人,這個(gè)快遞到底給誰(shuí)?不能隨便給一個(gè)人吧,怎么辦呢?互聯(lián)網(wǎng)大佬們想到了一個(gè)新詞叫做‘端口’。
傳輸層規(guī)定:給每一個(gè)應(yīng)用程序分配一個(gè)唯一的端口號(hào),當(dāng)有數(shù)據(jù)發(fā)送過(guò)來(lái)后,通過(guò)端口號(hào)來(lái)決定該數(shù)據(jù)發(fā)送的具體應(yīng)用程序。
但是根據(jù)不同的應(yīng)用程序需求,傳輸層分為2個(gè)協(xié)議,一個(gè)叫TCP,一個(gè)叫UDP。TCP可靠,UDP速度快;TCP對(duì)系統(tǒng)資源要求較多,UDO對(duì)系統(tǒng)資源要求較少。
????????應(yīng)用層:
????????這一層順理成章,TCP+IP就可以定位到計(jì)算機(jī)上的某個(gè)應(yīng)用了。但是不同應(yīng)用傳輸?shù)臄?shù)據(jù)格式可能不是一樣的。就好比快遞,有的是大包裹,有的是小文件,一個(gè)要用大麻袋裝,一個(gè)要用文件袋裝。到了應(yīng)用層,我們一般是根據(jù)不同類(lèi)型的應(yīng)用程序進(jìn)行的再一次封裝。比如,HTTP協(xié)議,SMTP協(xié)議,FTP協(xié)議等等。
二、初識(shí)Socket-TCP編程
在python, 哦不, 是幾乎所有的編程語(yǔ)言中, 我們?cè)诰帉?xiě)網(wǎng)絡(luò)程序的時(shí)候都要使用到socket. socket翻譯過(guò)來(lái)叫套接字(很癟嘴. 所以沒(méi)人這么叫它). 我們上面也了解到了一次網(wǎng)絡(luò)通信的數(shù)據(jù)需要包裹著mac, ip, port等信息. 但是如果每次我們開(kāi)發(fā)都要程序員去一個(gè)一個(gè)的去準(zhǔn)備數(shù)據(jù), 那工作量絕對(duì)是絕望的. 所以, 計(jì)算機(jī)提出了socket. socket幫助我們完成了網(wǎng)絡(luò)通信中的絕大多數(shù)操作. 我們只需要告訴socket. 我要向哪臺(tái)計(jì)算機(jī)(ip, port)發(fā)送數(shù)據(jù). 剩下的所有東西都由socket幫我們完成. 所以使用socket完成數(shù)據(jù)傳輸是非常方便的.
基本的socket-tcp編程
服務(wù)器:
import socket# 創(chuàng)建一個(gè)socket通道 sk = socket.socket()# 注冊(cè)一家洗腳城 sk.bind(("127.0.0.1", 5000)) # 綁定一個(gè)ip和端口, 選址 sk.listen() # 開(kāi)始監(jiān)聽(tīng) 開(kāi)張 print("服務(wù)器端準(zhǔn)備就緒, 等待連接") conn, address = sk.accept() # 程序會(huì)停在這里. 阻塞 等待客人上門(mén) print("有人來(lái)連我了, 他的地址是:", address) # 這個(gè)address里放著你要的客戶(hù)端ip和端口 conn.send("來(lái)啊, 大爺, 來(lái)玩兒啊".encode("utf-8")) # 發(fā)送出去的內(nèi)容只能是bytes. 給客人服務(wù)客戶(hù)端:
import socketsk = socket.socket() # 知道有洗腳城 print("客戶(hù)端初始化完畢") sk.connect(("127.0.0.1", 5000)) # 建立鏈接 登門(mén)拜訪xxxx洗腳城 print("客戶(hù)端鏈接成功") print(sk.recv(1024).decode("utf-8")) # 最大接收1024個(gè)字節(jié)的內(nèi)容 接收服務(wù)此時(shí), 我們可以讓客戶(hù)端和服務(wù)器端進(jìn)行基本的網(wǎng)絡(luò)通信了. 那如果是想要連續(xù)發(fā)送數(shù)據(jù)怎么辦? 加循環(huán)啊
服務(wù)器:
import socketsk = socket.socket() sk.bind(("127.0.0.1", 5000)) sk.listen() conn, address = sk.accept() while 1: conn.send(input(">>>:").encode("utf-8")) msg = conn.recv(1024).decode("utf-8")print("收到的內(nèi)容是: ", msg)客戶(hù)端:
import socketsk = socket.socket() sk.connect(("127.0.0.1", 5000)) while 1:msg = sk.recv(1024).decode("utf-8")print("收到的內(nèi)容是:", msg)sk.send(input(">>>:").encode("utf-8"))最基本的TCP編程咱們就先了解這么多. 稍后還會(huì)繼續(xù)進(jìn)行拓展的.
三、 初識(shí)Socket-UDP編程
使用udp編程
server端:
import socketsk = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) sk.bind(("127.0.0.1", 12123))msg, address = sk.recvfrom(1024) print(msg)sk.sendto(b"i love you", address)client端
import socketsk = socket.socket(type=socket.SOCK_DGRAM)sk.sendto(b'hello', ("127.0.0.1", 12123)) msg, addr = sk.recvfrom(1024)print(msg)四、TCP和UDP的對(duì)比
兩種網(wǎng)絡(luò)編程的方式最基本的代碼已經(jīng)OK了. 也能進(jìn)行通信了. 那么問(wèn)題來(lái)了. TCP和UDP的區(qū)別是什么?
TCP是基于鏈接的. 在傳輸數(shù)據(jù)之前必須保證鏈接的存在. 通信是通過(guò)conn來(lái)完成的. 所以, TCP通信具有以下特點(diǎn):
1. 可靠
2. 連續(xù)
3. 效率低
三次握手
TCP協(xié)議, 為了保障數(shù)據(jù)傳輸?shù)倪B續(xù)性可靠性. 建立鏈接的時(shí)候. 會(huì)給服務(wù)器發(fā)送一個(gè)x. 服務(wù)器返回客戶(hù)端x+1同時(shí)返回一個(gè)y, 客戶(hù)端接收到y(tǒng), 返回y+1作為回執(zhí). 用來(lái)保證客戶(hù)端和服務(wù)器是正常鏈接狀態(tài). 之后才開(kāi)始進(jìn)行傳輸數(shù)據(jù). 有點(diǎn)兒類(lèi)似我們打電話的時(shí)候,
我: 你好, 是周杰倫么
你: 你好, 我是周杰倫, 你是么林俊杰
我: 我是林俊杰, 咱倆開(kāi)始聊天吧
目的就是確保在傳輸數(shù)據(jù)之前我們兩個(gè)身份確認(rèn)成功.
四次揮手:
在傳輸完數(shù)據(jù)之后, 客戶(hù)端會(huì)告訴服務(wù)器. 我這完事兒了. 給一個(gè)狀態(tài)x+2,y+1. 服務(wù)器收到之后. 返回x+3, 等數(shù)據(jù)接收完畢之后返回y+1. 告訴客戶(hù)端傳輸完畢了. 客戶(hù)端最終返回y+2作為回執(zhí). 告訴服務(wù)器. 我走了.
我: 我要說(shuō)的就這么多
你: 好的, 我記一下
你: 我記好了. 你可以滾蛋了
我: 好的,我滾了
?
經(jīng)典問(wèn)題: 為什么TCP協(xié)議是三次握手, 四次揮手?
因?yàn)樵谖帐值臅r(shí)候, 兩邊其實(shí)還沒(méi)有開(kāi)始發(fā)送數(shù)據(jù). 只是建立鏈接, 假設(shè)數(shù)據(jù)從Client向Server端發(fā)送. 客戶(hù)端先發(fā)送一個(gè)SYN報(bào)文告訴服務(wù)器. 我要連你, 服務(wù)器回客戶(hù)端, 我收到了(ACK), 相應(yīng)的, 服務(wù)器也可以給客戶(hù)端發(fā)送數(shù)據(jù)啊. 服務(wù)器也要發(fā)送一個(gè)SYN報(bào)文. 所以呢, 為了省事兒. 服務(wù)器把發(fā)送請(qǐng)求的SYN報(bào)文和應(yīng)答的ACK報(bào)文就一起發(fā)出去了. 最后客戶(hù)端發(fā)送一個(gè)應(yīng)答(ACK) 這是三次握手. 實(shí)際上是4個(gè)動(dòng)作. 只不過(guò)中間的服務(wù)器的SYN和ACK是一起發(fā)的.
那么在揮手的時(shí)候?yàn)槭裁词谴文? 梳理一下過(guò)程: 客戶(hù)端發(fā)送完數(shù)據(jù). 給服務(wù)器放FIN報(bào)文. 告訴服務(wù)器. 我完事兒了. 服務(wù)器先答應(yīng)一下(ACK), oK 我知道了. 然后此時(shí). 服務(wù)器有可能還有數(shù)據(jù)沒(méi)發(fā)完呢. 只是你客戶(hù)端說(shuō)的. 你完事兒了. 我這不一定完事兒了啊. 所以, 服務(wù)器就只是發(fā)送了一個(gè)ACK, 告訴客戶(hù)端, 我收到了. 等服務(wù)器這邊忙完了. 該收的收了. 該發(fā)的發(fā)了. 服務(wù)器給客戶(hù)端發(fā)送一個(gè)FIN報(bào)文. 告訴客戶(hù)端, 我這也完事兒了. 然后客戶(hù)端說(shuō): 好的(ACK) . 整個(gè)鏈接關(guān)閉.
我什么是4次揮手呢. 因?yàn)椴徽撃囊环桨l(fā)起的FIN. 對(duì)方只能先回應(yīng)一下(ACK). 得等對(duì)方完成了相關(guān)操作, 才能返回FIN. 最后在ACK. 所以. 關(guān)閉鏈接必須是4步.
五、黏[nián]包現(xiàn)象
在使用TCP協(xié)議進(jìn)行數(shù)據(jù)傳輸?shù)臅r(shí)候, 會(huì)有以下問(wèn)題出現(xiàn).
client:
import socketsk = socket.socket() sk.connect(("127.0.0.1", 8101))# 連續(xù)發(fā)送數(shù)據(jù) s = "我愛(ài)你" sk.send(s.encode("utf-8")) sk.send(s.encode("utf-8"))print("發(fā)送完畢")sk.close()server:
import socketsk = socket.socket() sk.bind(("127.0.0.1", 8101)) sk.listen() conn, addr = sk.accept()msg1 = conn.recv(1024) print(msg1.decode("utf-8"))msg2 = conn.recv(1024) print(msg2.decode("utf-8"))sk.close()運(yùn)行結(jié)果
?
我們發(fā)現(xiàn), 打印來(lái)的效果是兩個(gè)數(shù)據(jù)包合在一起了. 為什么會(huì)這樣呢? 在數(shù)據(jù)傳輸?shù)臅r(shí)候客戶(hù)端發(fā)送兩次數(shù)據(jù). 這兩個(gè)數(shù)據(jù)并不是直接發(fā)送出去的. 首先會(huì)存放在緩沖區(qū). 等緩沖區(qū)數(shù)據(jù)裝滿或者經(jīng)過(guò)一段時(shí)間后. 會(huì)把緩沖區(qū)中的數(shù)據(jù)一起發(fā)送出去. 這就導(dǎo)致了一個(gè)很坑的現(xiàn)象. 明明是兩次發(fā)送的數(shù)據(jù). 被合在了一起. 這就是典型的黏包現(xiàn)象.
注意, 黏包現(xiàn)象只有TCP才會(huì)出現(xiàn). UDP是不會(huì)出現(xiàn)黏包的. 因?yàn)閁DP的不連續(xù)性. 每次發(fā)送的數(shù)據(jù)都會(huì)立刻打包成數(shù)據(jù)包然后發(fā)出去. 數(shù)據(jù)包與數(shù)據(jù)包之間是有邊界隔離的. 你可以認(rèn)為是一個(gè)sendto對(duì)應(yīng)一個(gè)recvfrom. 因此UDP不會(huì)出現(xiàn)黏包.
那么如何解決黏包問(wèn)題呢? 很簡(jiǎn)單. 之所以出現(xiàn)黏包就是因?yàn)閿?shù)據(jù)沒(méi)有邊界. 直接把兩個(gè)包混合成了一個(gè)包. 那么我可以在發(fā)送數(shù)據(jù)的時(shí)候. 指定邊界. 告訴對(duì)方. 我接下來(lái)這個(gè)數(shù)據(jù)包有多大. 對(duì)面接收數(shù)據(jù)的時(shí)候呢, 先讀取該數(shù)據(jù)包的大小.然后再讀取數(shù)據(jù). 就不會(huì)產(chǎn)生黏包了.
普通話: 發(fā)送數(shù)據(jù)的時(shí)候制定數(shù)據(jù)的格式: 長(zhǎng)度+數(shù)據(jù) 接收的時(shí)候就知道有多少是當(dāng)前這個(gè)數(shù)據(jù)包的大小了. 也就相當(dāng)于定義了分隔邊界了.
client:
import socketsk = socket.socket() sk.connect(("127.0.0.1", 8101))# 連續(xù)發(fā)送數(shù)據(jù) s = "我愛(ài)你" bs = s.encode("utf-8") # 計(jì)算數(shù)據(jù)長(zhǎng)度. 格式化成4位數(shù)字 bs_len = format(len(bs), "04d").encode("utf-8") # 發(fā)送數(shù)據(jù)之前. 先發(fā)送長(zhǎng)度 # 整個(gè)數(shù)據(jù)包: 0009\x\x\x\x\x\x... sk.send(bs_len) sk.send(bs)sk.send(bs_len) sk.send(bs)print("發(fā)送完畢")sk.close()server:
import socketsk = socket.socket() sk.bind(("127.0.0.1", 8101))sk.listen()conn, addr = sk.accept()# 整個(gè)數(shù)據(jù)包: 0009\x\x\x\x\x\x... # 接收4個(gè)字節(jié). 轉(zhuǎn)換成數(shù)字 bs_len = int(conn.recv(4).decode('Utf-8'))# 讀取數(shù)據(jù) msg1 = conn.recv(bs_len) print(msg1.decode("utf-8"))bs_len = int(conn.recv(4).decode('Utf-8')) msg2 = conn.recv(bs_len) print(msg2.decode("utf-8"))sk.close()如果每次發(fā)送數(shù)據(jù)都要經(jīng)過(guò)這么一次. 屬實(shí)有點(diǎn)兒累. 沒(méi)關(guān)系. python提供了一個(gè)很好用的模塊來(lái)幫我們解決這個(gè)惡心的問(wèn)題
import struct# 打包. 把一個(gè)數(shù)字打包成字節(jié) ret = struct.pack("i", 123456789) print(ret)print(len(ret)) # 4 不論數(shù)字大小, 定死了4個(gè)字節(jié)# 把字節(jié)還原回?cái)?shù)字 bs = b'\x15\xcd[\x07' num = struct.unpack("i", bs)[0] print(num)六、優(yōu)雅的解決黏包問(wèn)題
client:
import socket import structsk = socket.socket() sk.connect(("127.0.0.1", 8123))msg_bs = "我愛(ài)你".encode("utf-8") msg_struct_len = struct.pack("i", len(msg_bs))# 發(fā)一次 sk.send(msg_struct_len) sk.send(msg_bs)# 發(fā)兩次 sk.send(msg_struct_len) sk.send(msg_bs)server:
import socket import structsk = socket.socket()sk.bind(("127.0.0.1", 8123))sk.listen() conn, addr = sk.accept()# 接收一個(gè)數(shù)據(jù)包 msg_struct_len = conn.recv(4) msg_len = struct.unpack("i", msg_struct_len)[0] data = conn.recv(msg_len) print(data.decode('utf-8'))# 接收第二個(gè)數(shù)據(jù)包 msg_struct_len = conn.recv(4) msg_len = struct.unpack("i", msg_struct_len)[0] data = conn.recv(msg_len) print(data.decode('utf-8'))看著還是別扭. 提取一個(gè)模塊試試看
my_socket_util
import structdef my_send(sk, msg):msg_bs = msg.encode("utf-8")msg_struct_len = struct.pack("i", len(msg_bs))sk.send(msg_struct_len)sk.send(msg_bs)def my_recv(sk):# 接收一個(gè)數(shù)據(jù)包msg_struct_len = sk.recv(4)msg_len = struct.unpack("i", msg_struct_len)[0]data = sk.recv(msg_len)return data.decode("utf-8")client:
import socket import my_sk_util as msusk = socket.socket() sk.connect(("127.0.0.1", 8123))msu.my_send(sk, "我愛(ài)你") msu.my_send(sk, "我愛(ài)你")server:
import socket import my_sk_util as msusk = socket.socket()sk.bind(("127.0.0.1", 8123))sk.listen() conn, addr = sk.accept()print(msu.my_recv(conn)) print(msu.my_recv(conn))七、文件上傳
client:
import socket import os import struct import jsonsk = socket.socket() sk.connect(("172.10.1.16", 14399))# 要發(fā)送的文件 file_path = "my_sk_util.py"# 拿到文件大小和文件名字 file_size = os.path.getsize(file_path) file_name = os.path.basename(file_path)# 組裝一個(gè)字典. file_json = {"file_name": file_name, "file_size": file_size} # 轉(zhuǎn)化成json字符串, 里面存著數(shù)據(jù) file_json_str = json.dumps(file_json) # 把json字符串發(fā)送出去. 防止黏包. 需要先發(fā)送數(shù)據(jù)大小 file_json_bs = file_json_str.encode("utf-8") file_len_bs = struct.pack("i", len(file_json_bs)) sk.send(file_len_bs)# 發(fā)送json數(shù)據(jù) sk.send(file_json_bs)# 發(fā)送文件數(shù)據(jù) with open(file_path, mode="rb") as f:while file_size > 0:bs = f.read(1024) # 每次最多發(fā)送1024個(gè)字節(jié)sk.send(bs)file_size -= len(bs) # 發(fā)一次少一些字節(jié)print("上傳完畢") sk.close()server:
import socket import struct import jsonsk = socket.socket() sk.bind(("172.10.1.16", 14399)) sk.listen()conn, address = sk.accept()# 接收json長(zhǎng)度, 放黏包 file_json_len_bs = conn.recv(4) file_json_len = struct.unpack("i", file_json_len_bs)[0] # 獲取json字符串 file_json_str = conn.recv(file_json_len).decode('utf-8') # 轉(zhuǎn)化回字典 file_json = json.loads(file_json_str)with open(f"上傳/{file_json['file_name']}", mode="wb") as f:while file_json['file_size'] > 0:bs = conn.recv(1024)file_json['file_size'] -= len(bs)f.write(bs)print("one part")print("上傳完畢") sk.close()文件下載的邏輯和上傳的邏輯是一樣的.
八、socketserver
由于TCP是連續(xù)的. 就我們目前的代碼而言. 服務(wù)器端是無(wú)法處理多個(gè)人的請(qǐng)求的(同時(shí)). 就比如. 寫(xiě)一個(gè)這樣的代碼:
server:
import socket import struct import json import subprocess sk = socket.socket() sk.bind(("127.0.0.1", 12233)) sk.listen()conn, address = sk.accept()while 1:shell_len_bs = conn.recv(4)shell_len = struct.unpack("i", shell_len_bs)[0]shell = conn.recv(shell_len).decode("utf-8")# 用來(lái)執(zhí)行shell腳本.ret = subprocess.Popen(shell, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)d = {"msg": ret.stdout.read().decode("utf-8"), "error": ret.stderr.read().decode("utf-8")}msg_json_bs = json.dumps(d).encode("utf-8")msg_json_len_bs = struct.pack("i", len(msg_json_bs))conn.send(msg_json_len_bs)conn.send(msg_json_bs)client:
import socket import struct import json import subprocesssk = socket.socket() sk.bind(("127.0.0.1", 12233)) sk.listen()conn, address = sk.accept()while 1:shell_len_bs = conn.recv(4)shell_len = struct.unpack("i", shell_len_bs)[0]shell = conn.recv(shell_len).decode("utf-8")ret = subprocess.Popen(shell, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)d = {"msg": ret.stdout.read().decode("utf-8"), "error": ret.stderr.read().decode("utf-8")}msg_json_bs = json.dumps(d).encode("utf-8")msg_json_len_bs = struct.pack("i", len(msg_json_bs))conn.send(msg_json_len_bs)conn.send(msg_json_bs)此時(shí)我們發(fā)現(xiàn)只有一個(gè)人能連接到服務(wù)器. 因?yàn)樵诜?wù)器端只有accept來(lái)接收客戶(hù)端的連接. 并且接收客戶(hù)端鏈接和接發(fā)數(shù)據(jù)是串行的. 如果我們需要一個(gè)能并行執(zhí)行的server怎么辦呢? python中提供了socketserver來(lái)解決這個(gè)問(wèn)題
牛B版本socketserver:
import socketserver import subprocess import struct import jsonclass MyServer(socketserver.BaseRequestHandler):def handle(self):conn = self.requestwhile 1:shell_len_bs = conn.recv(4)shell_len = struct.unpack("i", shell_len_bs)[0]shell = conn.recv(shell_len).decode("utf-8")ret = subprocess.Popen(shell, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)d = {"msg":ret.stdout.read().decode("utf-8"), "error": ret.stderr.read().decode("utf-8")}msg_json_bs = json.dumps(d).encode("utf-8")msg_json_len_bs = struct.pack("i", len(msg_json_bs))conn.send(msg_json_len_bs)conn.send(msg_json_bs)if __name__ == '__main__':sock = socketserver.ThreadingTCPServer(("127.0.0.1", 8991), MyServer)sock.serve_forever()總結(jié)
以上是生活随笔為你收集整理的Python day13--网络编程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 观《从你的全世界路过》
- 下一篇: python 实现:如果文件夹不存在就创