Python基础之网络编程
大家好我是J哥,今天學(xué)習(xí)網(wǎng)絡(luò)編程,什么是網(wǎng)絡(luò)編程呢?
一.楔子
你現(xiàn)在已經(jīng)學(xué)會(huì)了寫python代碼,假如你寫了兩個(gè)python文件a.py和b.py,分別去運(yùn)行,你就會(huì)發(fā)現(xiàn),這兩個(gè)python的文件分別運(yùn)行的很好。但是如果這兩個(gè)程序之間想要傳遞一個(gè)數(shù)據(jù),你要怎么做呢?
這個(gè)問題以你現(xiàn)在的知識(shí)就可以解決了,我們可以創(chuàng)建一個(gè)文件,把a(bǔ).py想要傳遞的內(nèi)容寫到文件中,然后b.py從這個(gè)文件中讀取內(nèi)容就可以了
但是當(dāng)你的a.py和b.py分別在不同電腦上的時(shí)候,你要怎么辦呢?
類似的機(jī)制有計(jì)算機(jī)網(wǎng)盤,qq等等。我們可以在我們的電腦上和別人聊天,可以在自己的電腦上向網(wǎng)盤中上傳、下載內(nèi)容。這些都是兩個(gè)程序在通信。
二.軟件開發(fā)的架構(gòu)
我們了解的涉及到兩個(gè)程序之間通訊的應(yīng)用大致可以分為兩種:
第一種是應(yīng)用類:qq、微信、網(wǎng)盤、優(yōu)酷這一類是屬于需要安裝的桌面應(yīng)用
第二種是web類:比如百度、知乎、博客園等使用瀏覽器訪問就可以直接使用的應(yīng)用
這些應(yīng)用的本質(zhì)其實(shí)都是兩個(gè)程序之間的通訊。而這兩個(gè)分類又對應(yīng)了兩個(gè)軟件開發(fā)的架構(gòu)~
1.C/S架構(gòu)
C/S即:Client與Server ,中文意思:客戶端與服務(wù)器端架構(gòu),這種架構(gòu)也是從用戶層面(也可以是物理層面)來劃分的。
這里的客戶端一般泛指客戶端應(yīng)用程序EXE,程序需要先安裝后,才能運(yùn)行在用戶的電腦上,對用戶的電腦操作系統(tǒng)環(huán)境依賴較大。
2.B/S架構(gòu)
B/S即:Browser與Server,中文意思:瀏覽器端與服務(wù)器端架構(gòu),這種架構(gòu)是從用戶層面來劃分的。
Browser瀏覽器,其實(shí)也是一種Client客戶端,只是這個(gè)客戶端不需要大家去安裝什么應(yīng)用程序,只需在瀏覽器上通過HTTP請求服務(wù)器端相關(guān)的資源(網(wǎng)頁資源),客戶端Browser瀏覽器就能進(jìn)行增刪改查。
三.網(wǎng)絡(luò)基礎(chǔ)
計(jì)算機(jī)網(wǎng)絡(luò) - Eva_J - 博客園
1.一個(gè)程序如何在網(wǎng)絡(luò)上找到另一個(gè)程序?
首先,程序必須要啟動(dòng),其次,必須有這臺(tái)機(jī)器的地址,我們都知道我們?nèi)说牡刂反蟾啪褪菄襖省\市\(zhòng)區(qū)\街道\樓\門牌號(hào)這樣字。那么每一臺(tái)聯(lián)網(wǎng)的機(jī)器在網(wǎng)絡(luò)上也有自己的地址,它的地址是怎么表示的呢?
就是使用一串?dāng)?shù)字來表示的,例如:127.4.5.6
什么是IP?
IP地址是指互聯(lián)網(wǎng)協(xié)議地址(英語:Internet Protocol Address,又譯為網(wǎng)際協(xié)議地址),是IP Address的縮寫。IP地址是IP協(xié)議提供的一種統(tǒng)一的地址格式,它為互聯(lián)網(wǎng)上的每一個(gè)網(wǎng)絡(luò)和每一臺(tái)主機(jī)分配一個(gè)邏輯地址,以此來屏蔽物理地址的差異。IP地址是一個(gè)32位的二進(jìn)制數(shù),通常被分割為4個(gè)“8位二進(jìn)制數(shù)”(也就是4個(gè)字節(jié))。IP地址通常用“點(diǎn)分十進(jìn)制”表示成(a.b.c.d)的形式,其中,a,b,c,d都是0~255之間的十進(jìn)制整數(shù)。例:點(diǎn)分十進(jìn)IP地址(100.4.5.6),實(shí)際上是32位二進(jìn)制數(shù)(01100100.00000100.00000101.00000110)。什么是端口?
"端口"是英文port的意譯,可以認(rèn)為是設(shè)備與外界通訊交流的出口。在windows上如何查看端口占用的情況?
netstat -aon|findstr "49157" ipconfig tasklist因此ip地址精確到具體的一臺(tái)電腦,而端口精確到具體的程序。
2.osi七層模型
引子
須知一個(gè)完整的計(jì)算機(jī)系統(tǒng)是由硬件、操作系統(tǒng)、應(yīng)用軟件三者組成,具備了這三個(gè)條件,一臺(tái)計(jì)算機(jī)系統(tǒng)就可以自己跟自己玩了(打個(gè)單機(jī)游戲,玩?zhèn)€掃雷啥的)
如果你要跟別人一起玩,那你就需要上網(wǎng)了,什么是互聯(lián)網(wǎng)?
互聯(lián)網(wǎng)的核心就是由一堆協(xié)議組成,協(xié)議就是標(biāo)準(zhǔn),比如全世界人通信的標(biāo)準(zhǔn)是英語,如果把計(jì)算機(jī)比作人,互聯(lián)網(wǎng)協(xié)議就是計(jì)算機(jī)界的英語。所有的計(jì)算機(jī)都學(xué)會(huì)了互聯(lián)網(wǎng)協(xié)議,那所有的計(jì)算機(jī)都就可以按照統(tǒng)一的標(biāo)準(zhǔn)去收發(fā)信息從而完成通信了。
osi七層模型
人們按照分工不同把互聯(lián)網(wǎng)協(xié)議從邏輯上劃分了層級(jí):
3.socket概念
socket層
理解socket
Socket是應(yīng)用層與TCP/IP協(xié)議族通信的中間軟件抽象層,它是一組接口。在設(shè)計(jì)模式中,Socket其實(shí)就是一個(gè)門面模式,它把復(fù)雜的TCP/IP協(xié)議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數(shù)據(jù),以符合指定的協(xié)議。
其實(shí)站在你的角度上看,socket就是一個(gè)模塊。我們通過調(diào)用模塊中已經(jīng)實(shí)現(xiàn)的方法建立兩個(gè)進(jìn)程之間的連接和通信。 也有人將socket說成ip+port,因?yàn)閕p是用來標(biāo)識(shí)互聯(lián)網(wǎng)中的一臺(tái)主機(jī)的位置,而port是用來標(biāo)識(shí)這臺(tái)機(jī)器上的一個(gè)應(yīng)用程序。 所以我們只要確立了ip和port就能找到一個(gè)應(yīng)用程序,并且使用socket模塊來與之通信。3.套接字(socket)的發(fā)展史
套接字起源于 20 世紀(jì) 70 年代加利福尼亞大學(xué)伯克利分校版本的 Unix,即人們所說的 BSD Unix。 因此,有時(shí)人們也把套接字稱為“伯克利套接字”或“BSD 套接字”。一開始,套接字被設(shè)計(jì)用在同 一臺(tái)主機(jī)上多個(gè)應(yīng)用程序之間的通訊。這也被稱進(jìn)程間通訊,或 IPC。套接字有兩種(或者稱為有兩個(gè)種族),分別是基于文件型的和基于網(wǎng)絡(luò)型的。?
基于文件類型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字調(diào)用的就是底層的文件系統(tǒng)來取數(shù)據(jù),兩個(gè)套接字進(jìn)程運(yùn)行在同一機(jī)器,可以通過訪問同一個(gè)文件系統(tǒng)間接完成通信
基于網(wǎng)絡(luò)類型的套接字家族
套接字家族的名字:AF_INET
(還有AF_INET6被用于ipv6,還有一些其他的地址家族,不過,他們要么是只用于某個(gè)平臺(tái),要么就是已經(jīng)被廢棄,或者是很少被使用,或者是根本沒有實(shí)現(xiàn),所有地址家族中,AF_INET是使用最廣泛的一個(gè),python支持很多種地址家族,但是由于我們只關(guān)心網(wǎng)絡(luò)編程,所以大部分時(shí)候我么只使用AF_INET)
4.tcp協(xié)議和udp協(xié)議
TCP(Transmission Control Protocol)可靠的、面向連接的協(xié)議(eg:打電話)、傳輸效率低全雙工通信(發(fā)送緩存&接收緩存)、面向字節(jié)流。使用TCP的應(yīng)用:Web瀏覽器;電子郵件、文件傳輸程序。
UDP(User Datagram Protocol)不可靠的、無連接的服務(wù),傳輸效率高(發(fā)送前時(shí)延小),一對一、一對多、多對一、多對多、面向報(bào)文,盡最大努力服務(wù),無擁塞控制。使用UDP的應(yīng)用:域名系統(tǒng)?(DNS);視頻流;IP語音(VoIP)。
我知道說這些你們也不懂,直接上圖。
四.套接字(socket)初使用
基于TCP協(xié)議的socket
tcp是基于鏈接的,必須先啟動(dòng)服務(wù)端,然后再啟動(dòng)客戶端去鏈接服務(wù)端
server端
import socket sk = socket.socket() sk.bind(('127.0.0.1',8898)) #把地址綁定到套接字 sk.listen() #監(jiān)聽鏈接 conn,addr = sk.accept() #接受客戶端鏈接 ret = conn.recv(1024) #接收客戶端信息 print(ret) #打印客戶端信息 conn.send(b'hi') #向客戶端發(fā)送信息 conn.close() #關(guān)閉客戶端套接字 sk.close() #關(guān)閉服務(wù)器套接字(可選)client端
import socket sk = socket.socket() # 創(chuàng)建客戶套接字 sk.connect(('127.0.0.1',8898)) # 嘗試連接服務(wù)器 sk.send(b'hello!') ret = sk.recv(1024) # 對話(發(fā)送/接收) print(ret) sk.close() # 關(guān)閉客戶套接字問題:有的小白在重啟服務(wù)端時(shí)可能會(huì)遇到
解決方法:
#加入一條socket配置,重用ip和端口 import socket from socket import SOL_SOCKET,SO_REUSEADDR sk = socket.socket() sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加 sk.bind(('127.0.0.1',8898)) #把地址綁定到套接字 sk.listen() #監(jiān)聽鏈接 conn,addr = sk.accept() #接受客戶端鏈接 ret = conn.recv(1024) #接收客戶端信息 print(ret) #打印客戶端信息 conn.send(b'hi') #向客戶端發(fā)送信息 conn.close() #關(guān)閉客戶端套接字 sk.close() #關(guān)閉服務(wù)器套接字(可選)基于UDP協(xié)議的socket
udp是無鏈接的,啟動(dòng)服務(wù)之后可以直接接受消息,不需要提前建立鏈接
簡單使用
server端
import socket udp_sk = socket.socket(type=socket.SOCK_DGRAM) #創(chuàng)建一個(gè)服務(wù)器的套接字 udp_sk.bind(('127.0.0.1',8080)) #綁定服務(wù)器套接字 msg,addr = udp_sk.recvfrom(1024) print(msg) udp_sk.sendto(b'hi',addr) # 對話(接收與發(fā)送) udp_sk.close() # 關(guān)閉服務(wù)器套接字client端
import socket ip_port=('127.0.0.1',8080) 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)帶兩個(gè)例子:
?qq聊天
server端
#_*_coding:utf-8_*_ import socket ip_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('回復(fù)消息: ').strip()udp_server_sock.sendto(back_msg.encode('utf-8'),addr)serverclient端
#_*_coding:utf-8_*_ import socket BUFSIZE=1024 udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)qq_name_dic={'tom':('127.0.0.1',8081),'tony':('127.0.0.1',8081),'egg':('127.0.0.1',8081),'kk':('127.0.0.1',8081), }while True:qq_name=input('請選擇聊天對象: ').strip()while True:msg=input('請輸入消息,回車發(fā)送,輸入q結(jié)束和他的聊天: ').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()client五.黏包
黏包現(xiàn)象
讓我們基于tcp先制作一個(gè)遠(yuǎn)程執(zhí)行命令的程序(命令ls -l ; lllllll ; pwd)
res=subprocess.Popen(cmd.decode('utf-8'), shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)的結(jié)果的編碼是以當(dāng)前所在的系統(tǒng)為準(zhǔn)的,如果是windows,那么res.stdout.read()讀出的就是GBK編碼的,在接收端需要用GBK解碼且只能從管道里讀一次結(jié)果注意同時(shí)執(zhí)行多條命令之后,得到的結(jié)果很可能只有一部分,在執(zhí)行其他命令的時(shí)候又接收到之前執(zhí)行的另外一部分結(jié)果,這種顯現(xiàn)就是黏包。
基于tcp協(xié)議實(shí)現(xiàn)的黏包
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 - servertcp-client
#_*_coding:utf-8_*_ import socket BUFSIZE=1024 ip_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='')tcp - client基于udp協(xié)議實(shí)現(xiàn)的黏包
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()#發(fā)消息udp_server.sendto(stderr,addr)udp_server.sendto(stdout,addr) udp_server.close()udp - serverclient
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='')udp - client注意:只有TCP有粘包現(xiàn)象,UDP永遠(yuǎn)不會(huì)粘包
UDP(user datagram protocol,用戶數(shù)據(jù)報(bào)協(xié)議)是無連接的,面向消息的,提供高效率服務(wù)。 不會(huì)使用塊的合并優(yōu)化算法,, 由于UDP支持的是一對多的模式,所以接收端的skbuff(套接字緩沖區(qū))采用了鏈?zhǔn)浇Y(jié)構(gòu)來記錄每一個(gè)到達(dá)的UDP包,在每個(gè)UDP包中就有了消息頭(消息來源地址,端口等信息),這樣,對于接收端來說,就容易進(jìn)行區(qū)分處理了。 即面向消息的通信是有消息保護(hù)邊界的。 對于空消息:tcp是基于數(shù)據(jù)流的,于是收發(fā)的消息不能為空,這就需要在客戶端和服務(wù)端都添加空消息的處理機(jī)制,防止程序卡住,而udp是基于數(shù)據(jù)報(bào)的,即便是你輸入的是空內(nèi)容(直接回車),也可以被發(fā)送,udp協(xié)議會(huì)幫你封裝上消息頭發(fā)送過去。 不可靠不黏包的udp協(xié)議:udp的recvfrom是阻塞的,一個(gè)recvfrom(x)必須對唯一一個(gè)sendinto(y),收完了x個(gè)字節(jié)的數(shù)據(jù)就算完成,若是y;x數(shù)據(jù)就丟失,這意味著udp根本不會(huì)粘包,但是會(huì)丟數(shù)據(jù),不可靠。補(bǔ)充說明:udp和tcp一次性發(fā)送長度的限制
用UDP協(xié)議發(fā)送時(shí),用sendto函數(shù)最大能發(fā)送數(shù)據(jù)的長度為:65535- IP頭(20) – UDP頭(8)=65507字節(jié)。用sendto函數(shù)發(fā)送數(shù)據(jù)時(shí),如果發(fā)送數(shù)據(jù)長度大于該值,則函數(shù)會(huì)返回錯(cuò)誤。(丟棄這個(gè)包,不進(jìn)行發(fā)送) 用TCP協(xié)議發(fā)送時(shí),由于TCP是數(shù)據(jù)流協(xié)議,因此不存在包大小的限制(暫不考慮緩沖區(qū)的大小),這是指在用send函數(shù)時(shí),數(shù)據(jù)長度參數(shù)不受限制。而實(shí)際上,所指定的這段數(shù)據(jù)并不一定會(huì)一次性發(fā)送出去,如果這段數(shù)據(jù)比較長,會(huì)被分段發(fā)送,如果比較短,可能會(huì)等待和下一次數(shù)據(jù)一起發(fā)送。總結(jié)
黏包現(xiàn)象只發(fā)生在tcp協(xié)議中:
1.從表面上看,黏包問題主要是因?yàn)榘l(fā)送方和接收方的緩存機(jī)制、tcp協(xié)議面向流通信的特點(diǎn)。
2.實(shí)際上,主要還是因?yàn)榻邮辗讲恢老⒅g的界限,不知道一次性提取多少字節(jié)的數(shù)據(jù)所造成的
黏包的解決方案
解決方案一
問題的根源在于,接收端不知道發(fā)送端將要傳送的字節(jié)流的長度,所以解決粘包的方法就是圍繞,如何讓發(fā)送端在發(fā)送數(shù)據(jù)前,把自己將要發(fā)送的字節(jié)流總大小讓接收端知曉,然后接收端來一個(gè)死循環(huán)接收完所有數(shù)據(jù)。
服務(wù)端
#_*_coding:utf-8_*_ import socket,subprocess ip_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()服務(wù)端客戶端
#_*_coding:utf-8_*_ import socket,time s=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'))客戶端 存在的問題: 程序的運(yùn)行速度遠(yuǎn)快于網(wǎng)絡(luò)傳輸速度,所以在發(fā)送一段字節(jié)前,先用send去發(fā)送該字節(jié)流長度,這種方式會(huì)放大網(wǎng)絡(luò)延遲帶來的性能損耗解決方案進(jìn)階
剛剛的方法,問題在于我們我們在發(fā)送
我們可以借助一個(gè)模塊,這個(gè)模塊可以把要發(fā)送的數(shù)據(jù)長度轉(zhuǎn)換成固定長度的字節(jié)。這樣客戶端每次接收消息之前只要先接受這個(gè)固定長度字節(jié)的內(nèi)容看一看接下來要接收的信息大小,那么最終接受的數(shù)據(jù)只要達(dá)到這個(gè)值就停止,就能剛好不多不少的接收完整的數(shù)據(jù)了。
struct模塊
該模塊可以把一個(gè)類型,如數(shù)字,轉(zhuǎn)成固定長度的bytes
>>> struct.pack('i',1111111111111) struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #這個(gè)是范圍 import json,struct #假設(shè)通過客戶端上傳1T:1073741824000的文件a.txt#為避免粘包,必須自定制報(bào)頭 header={'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T數(shù)據(jù),文件路徑和md5值#為了該報(bào)頭能傳送,需要序列化并且轉(zhuǎn)為bytes head_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化并轉(zhuǎn)成bytes,用于傳輸#為了讓客戶端知道報(bào)頭的長度,用struck將報(bào)頭長度這個(gè)數(shù)字轉(zhuǎn)成固定長度:4個(gè)字節(jié) head_len_bytes=struct.pack('i',len(head_bytes)) #這4個(gè)字節(jié)里只包含了一個(gè)數(shù)字,該數(shù)字是報(bào)頭的長度#客戶端開始發(fā)送 conn.send(head_len_bytes) #先發(fā)報(bào)頭的長度,4個(gè)bytes conn.send(head_bytes) #再發(fā)報(bào)頭的字節(jié)格式 conn.sendall(文件內(nèi)容) #然后發(fā)真實(shí)內(nèi)容的字節(jié)格式#服務(wù)端開始接收 head_len_bytes=s.recv(4) #先收報(bào)頭4個(gè)bytes,得到報(bào)頭長度的字節(jié)格式 x=struct.unpack('i',head_len_bytes)[0] #提取報(bào)頭的長度head_bytes=s.recv(x) #按照報(bào)頭長度x,收取報(bào)頭的bytes格式 header=json.loads(json.dumps(header)) #提取報(bào)頭#最后根據(jù)報(bào)頭的內(nèi)容提取真實(shí)的數(shù)據(jù),比如 real_data_len=s.recv(header['file_size']) s.recv(real_data_len)借助struct模塊,我們知道長度數(shù)字可以被轉(zhuǎn)換成一個(gè)標(biāo)準(zhǔn)大小的4字節(jié)數(shù)字。因此可以利用這個(gè)特點(diǎn)來預(yù)先發(fā)送數(shù)據(jù)長度
服務(wù)端
import socket,struct,json import subprocess phone=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))) #先發(fā)back_msg的長度conn.sendall(back_msg) #在發(fā)真實(shí)的內(nèi)容conn.close()服務(wù)端(自定制報(bào)頭)客戶端
#_*_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默認(rèn)gbk編碼客戶端(自定制報(bào)頭)我們還可以把報(bào)頭做成字典,字典里包含將要發(fā)送的真實(shí)數(shù)據(jù)的詳細(xì)信息,然后json序列化,然后用struck將序列化后的數(shù)據(jù)長度打包成4個(gè)字節(jié)(4個(gè)自己足夠用了)
服務(wù)端-復(fù)雜修改
import socket,struct,json import subprocess phone=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))) #先發(fā)報(bào)頭的長度conn.send(head_json_bytes) #再發(fā)報(bào)頭conn.sendall(back_msg) #在發(fā)真實(shí)的內(nèi)容conn.close()服務(wù)端:定制稍微復(fù)雜一點(diǎn)的報(bào)頭客戶端
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默認(rèn)gbk編碼客戶端六.socketserver
server端
import socketserver class 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# 設(shè)置allow_reuse_address允許服務(wù)器重用地址socketserver.TCPServer.allow_reuse_address = True# 創(chuàng)建一個(gè)server, 將服務(wù)地址綁定到127.0.0.1:9999server = socketserver.TCPServer((HOST, PORT),Myserver)# 讓server永遠(yuǎn)運(yùn)行下去,除非強(qiáng)制停止程序server.serve_forever()server端client
import socketHOST, PORT = "127.0.0.1", 9999 data = "hello"# 創(chuàng)建一個(gè)socket鏈接,SOCK_STREAM代表使用TCP協(xié)議 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:sock.connect((HOST, PORT)) # 鏈接到客戶端sock.sendall(bytes(data + "\n", "utf-8")) # 向服務(wù)端發(fā)送數(shù)據(jù)received = str(sock.recv(1024), "utf-8")# 從服務(wù)端接收數(shù)據(jù)print("Sent: {}".format(data)) print("Received: {}".format(received))client看到這里就就下你的痕跡吧!算是對J哥最大的支持!
總結(jié)
以上是生活随笔為你收集整理的Python基础之网络编程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ubuntu 9.04 X3100 显卡
- 下一篇: python的网络编程用途_python