python的网络编程用途_python---网络编程
一、軟件開發的架構
1: C/S架構
Client與Server? 客戶端與服務器端,這里的客戶端一般泛指客戶端應用EXE,程序需要先安裝后,才能運行在用戶的電腦上,對用戶的電腦操作系統環境依賴較大。
2: B/S架構
Browser與Server? 瀏覽器端與服務器端。
Browser瀏覽器,其實也是一種Client客戶端,只是這個客戶端不需要去安裝什么應用程序,只需在瀏覽器上通過HTTP請求服務器端相關的資源。
二、網絡基礎
IP地址:指互聯網協議地址。
IP地址是IP協會提供的一種統一的地址格式,它為互聯網上的每一個網絡和每一臺主機分配一個邏輯地址,以此來屏蔽物理地址的差異。
IP地址是一個32位的二進制數,通常被分割為4個‘8位二進制數’。
IP地址通常用“點分十進制”表示(a,b,c,d)的形式,其中,a,b,c,d都是0~255之間的十進制整數。
端口可以認為是設備與外界通訊交流的出口。
因此IP地址可以精確到具體的一臺電腦,而端口精確到具體的程序。
通過子網掩碼,我們就能判斷,任意兩個IP地址是否處在同一個子網絡。方法是將兩個IP地址與子網掩碼分別進行AND運算(兩個位數都是1,則結果1,反之0),然后比較結果是否相同,如果是的話,就表示它們在同一個子網絡中,否則就不是。
總結一下,IP協議的作用主要有兩個:一個是為每一臺計算機分配IP地址,另一個是確定哪些地址在同一個子網絡。
TCP協議
TCP---傳輸控制協議,提供的是面向連接、可靠的字節流服務。當客戶與服務器彼此交換數據前,必須先在雙方之間建立一個TCP連接,之后才能傳輸數據。TCP提供超時重發,丟棄重復數據,檢驗數據,流量控制等功能,保證數據能從一端傳輸到另一端。
可靠的、面向連接的協議(eg:打電話)、傳輸效率低全雙工通信(發送緩存&接收緩存)、面向字節流。使用TCP的應用:Web瀏覽器;電子郵件、文件傳輸程序。
TCP是因特網中的傳輸層協議,使用三次握手協議建立連接。當主動方發出SYN連接請求后,等待對方回答SYN+ACK[1],并最終對對方的 SYN 執行 ACK 確認。這種建立連接的方法可以防止產生錯誤的連接。[1]
TCP三次握手的過程如下:
客戶端發送SYN(SEQ=x)報文給服務器端,進入SYN_SEND狀態。
服務器端收到SYN報文,回應一個SYN (SEQ=y)ACK(ACK=x+1)報文,進入SYN_RECV狀態。
客戶端收到服務器端的SYN報文,回應一個ACK(ACK=y+1)報文,進入Established狀態。
三次握手完成,TCP客戶端和服務器端成功地建立連接,可以開始傳輸數據了。
tcp的三次握手
建立一個連接需要三次握手,而終止一個連接要經過四次握手,這是由TCP的半關閉(half-close)造成的。
(1) 某個應用進程首先調用close,稱該端執行“主動關閉”(active close)。該端的TCP于是發送一個FIN分節,表示數據發送完畢。
(2) 接收到這個FIN的對端執行 “被動關閉”(passive close),這個FIN由TCP確認。
注意:FIN的接收也作為一個文件結束符(end-of-file)傳遞給接收端應用進程,放在已排隊等候該應用進程接收的任何其他數據之后,因為,FIN的接收意味著接收端應用進程在相應連接上再無額外數據可接收。
(3) 一段時間后,接收到這個文件結束符的應用進程將調用close關閉它的套接字。這導致它的TCP也發送一個FIN。
(4) 接收這個最終FIN的原發送端TCP(即執行主動關閉的那一端)確認這個FIN。[1]
既然每個方向都需要一個FIN和一個ACK,因此通常需要4個分節。
注意:
(1) “通常”是指,某些情況下,步驟1的FIN隨數據一起發送,另外,步驟2和步驟3發送的分節都出自執行被動關閉那一端,有可能被合并成一個分節。[2]
(2) 在步驟2與步驟3之間,從執行被動關閉一端到執行主動關閉一端流動數據是可能的,這稱為“半關閉”(half-close)。
(3) 當一個Unix進程無論自愿地(調用exit或從main函數返回)還是非自愿地(收到一個終止本進程的信號)終止時,所有打開的描述符都被關閉,這也導致仍然打開的任何TCP連接上也發出一個FIN。
無論是客戶還是服務器,任何一端都可以執行主動關閉。通常情況是,客戶執行主動關閉,但是某些協議,例如,HTTP/1.0卻由服務器執行主動關閉。[2]
tcp的四次揮手
UDP協議
UDP---用戶數據報協議,是一個簡單的面向數據報的運輸層協議。UDP不提供可靠性,它只是把應用程序傳給IP層的數據報發送出去,但是并不能保證它們能到達目的地。由于UDP在傳輸數據報前不用在客戶和服務器之間建立一個連接,且沒有超時重發等機制,故而傳輸速度很快。
不可靠的、無連接的服務,傳輸效率高(發送前時延小),一對一、一對多、多對一、多對多、面向報文,盡最大努力服務,無擁塞控制。使用UDP的應用:域名系統?(DNS);視頻流;IP語音(VoIP)。
互聯網協議按照功能不同分為osi七層或tcp/ip五層或tcp/ip四層
每層運行常見的物理設備
傳輸層——> 四層交換機、四層的路由器
網絡層——>路由器、三層交換機
數據鏈路層——>網橋、以太網交換機、網卡
物理層——>中繼器、集線器、雙絞線
每層運行常見的協議
應用層——>。。。
傳輸層——>TCP與UDP協議
網絡層——>ip協議
數據鏈路層——>arp協議 ? (通過ip找mac地址)
物理層——>。。。
交換機:廣播 單播 組播
ip協議:ip地址的格式
# ip地址 一臺機器在一個網絡內唯一的標識
# 子網掩碼? ip地址與子網掩碼做按位與運算,得到的結果是網段
# 網關ip 局域網內的機器訪問公網ip,就通過網關訪問
三、socket
Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。
socket和file的區別:
1:file模塊是針對某個指定文件進行打開、讀寫、關閉
2:socket模塊是針對服務器端和客戶端socket進行打開、讀寫、關閉
基于文件類型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程運行在同一機器,可以通過訪問同一個文件系統間接完成通信
基于網絡類型的套接字家族
套接字家族的名字:AF_INET
(還有AF_INET6被用于ipv6,還有一些其他的地址家族,不過,他們要么是只用于某個平臺,要么就是已經被廢棄,或者是很少被使用,或者是根本沒有實現,所有地址家族中,AF_INET是使用最廣泛的一個,python支持很多種地址家族,但是由于我們只關心網絡編程,所以大部分時候我么只使用AF_INET)
基于TCP協議的socket
tcp是基于鏈接的,必須先啟動服務端,然后再啟動客戶端去鏈接服務端
importsocket
sk=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() #關閉服務器套接字(可選)
服務端
importsocket
sk= socket.socket() #創建客戶套接字
sk.connect(('127.0.0.1',8898)) #嘗試連接服務器
sk.send(b'hello!')
ret= sk.recv(1024) #對話(發送/接收)
print(ret)
sk.close()#關閉客戶套接字
客戶端
tcp實現與多個客戶端通信,必須結束一個客戶端,才能到下一個客戶端。
#服務器端
importsocket#tcp協議
sk = socket.socket() #創建一個socket對象
sk.bind(('127.0.0.1',8080)) #給server端綁定一個ip和端口
sk.listen()whileTrue:
conn,addr=sk.accept()whileTrue:
msg= conn.recv(1024).decode('utf-8') #阻塞,直到收到一個客戶端發來的消息
print(msg)if msg == 'bye':breakinfo= input('>>>')if info == 'bye':
conn.send(b'bye')breakconn.send(info.encode('utf-8')) #發消息
conn.close() #關閉連接
sk.close() #關閉socket對象,如果不關閉,還能繼續接收
#客戶端1
importsocket
sk=socket.socket()
sk.connect(('127.0.0.1',8080))whileTrue:
msg= input('>>>')if msg == 'bye':
sk.send(b'bye')breaksk.send(msg.encode('utf-8'))
ret= sk.recv(1024).decode('utf-8')if ret == 'bye':break
print(ret)
sk.close()#客戶端2
importsocket
sk=socket.socket()
sk.connect(('127.0.0.1',8080))whileTrue:
msg= input('client2:>>>')if msg == 'bye':
sk.send(b'bye')breaksk.send(('client2 :'+msg).encode('utf-8'))
ret= sk.recv(1024).decode('utf-8')if ret == 'bye':break
print(ret)
sk.close()
tcp服務器與多個客戶端通信
基于udp協議的socket
#服務器端
importsocket
sk= socket.socket(type=socket.SOCK_DGRAM) #DGRAM datagram
sk.bind(('127.0.0.1',8080)) #只有服務端有的
msg,addr= sk.recvfrom(1024)print(msg.decode('utf-8'))
sk.sendto(b'bye',addr)
sk.close()#udp的server 不需要進行監聽也不需要建立連接#在啟動服務之后只能被動的等待客戶端發送消息過來#客戶端發送消息的同時還會 自帶地址信息#消息回復的時候 不僅需要發送消息,還需要把對方的地址填寫上
#客戶端
importsocket
sk= socket.socket(type=socket.SOCK_DGRAM)
ip_port= ('127.0.0.1',8080)
sk.sendto(b'hello',ip_port)
ret,addr= sk.recvfrom(1024)print(ret.decode('utf-8'))
sk.close()#client端不需要connect 因為UDP協議是不需要建立連接的#直接了解到對方的ip和端口信息就發送數據就行了#sendto和recvfrom的使用方法是完全和server端一致的
#client端不需要connect 因為UDP協議是不需要建立連接的#直接了解到對方的ip和端口信息就發送數據就行了#sendto和recvfrom的使用方法是完全和server端一致的
基于udp協議的socket
#服務器端
importsocket
sk= socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',8080))whileTrue:
msg,addr= sk.recvfrom(1024)print(addr)print(msg.decode('utf-8'))
info= input('>>>').encode('utf-8')
sk.sendto(info,addr)
sk.close()#客戶端1
importsocket
sk= socket.socket(type=socket.SOCK_DGRAM)
ip_port= ('127.0.0.1',8080)whileTrue:
info= input('tiger :')
info= ('\033[34m來自tiger的消息 :%s\033[0m'%info).encode('utf-8')
sk.sendto(info,ip_port)
msg,addr= sk.recvfrom(1024)print(msg.decode('utf-8'))
sk.close()#客戶端2
importsocket
sk= socket.socket(type=socket.SOCK_DGRAM)
ip_port= ('127.0.0.1',8080)whileTrue:
info= input('二哥 :')
info= ('\033[32m來自二哥的消息 :%s\033[0m'%info).encode('utf-8')
sk.sendto(info,ip_port)
msg,addr= sk.recvfrom(1024)print(msg.decode('utf-8'))
sk.close()
QQ聊天
#服務器端
importtimeimportsocket
sk= socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',8090))whileTrue:
strf,addr= sk.recvfrom(1024)
strf= strf.decode('utf-8')
res= time.strftime(strf).encode('utf-8')
sk.sendto(res,addr)
sk.close()#客戶端
importsocket
sk= socket.socket(type=socket.SOCK_DGRAM)
addr= ('127.0.0.1',8090)
info= input('>>>').encode('utf-8')
sk.sendto(info,addr)
ret,addr= sk.recvfrom(1024)print(ret.decode('utf-8'))
sk.close()
格式化時間
服務端套接字函數
s.bind()? 綁定(主機,端口號)到套接字
s.listen() ?? 開始TCP監聽
s.accept()? 被打接收TCP客戶的連接,出錯時返回出錯碼,而不是拋出異常
客戶端套接字函數
s.connect() 主動初始化TCP服務器連接
s.connect_ex() ? ? connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常
公共用途的套接字函數
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() 關閉套接字
面向鎖的套接字方法
s.setblocking() 設置套接字的阻塞與非阻塞模式
s.settimeout() 設置阻塞套接字操作的超時時間
s.gettimeout() 得到阻塞套接字操作的超時時間
面向文件的套接字的函數
s.fileno() 套接字的文件描述符
s.makefile() ? 創建一個與該套接字相關的文件
四、黏包
黏包:同時執行多條命令之后,得到的結果很可能只有一部分,在執行其他命令的時候又接收到之前執行的另外一部分結果,這種顯現就是黏包。
只有TCP有黏包現象,UDP永遠不會黏包:
1.從表面上看,黏包問題主要是因為發送方和接收方的緩存機制、tcp協議面向流通信的特點。
2.實際上,主要還是因為接收方不知道消息之間的界限,不知道一次性提取多少個字節的數據所造成的。
用UDP協議發送時,用sendto函數最大能發送數據的長度為:65535- IP頭(20) – UDP頭(8)=65507字節。用sendto函數發送數據時,如果發送數據長度大于該值,則函數會返回錯誤。(丟棄這個包,不進行發送)
用TCP協議發送時,由于TCP是數據流協議,因此不存在包大小的限制(暫不考慮緩沖區的大小),這是指在用send函數時,數據長度參數不受限制。而實際上,所指定的這段數據并不一定會一次性發送出去,如果這段數據比較長,會被分段發送,如果比較短,可能會等待和下一次數據一起發送。發送方引起的粘包是由TCP協議本身造成的,TCP為提高傳輸效率,發送方往往要收集到足夠多的數據后才發送一個TCP段。若連續幾次需要send的數據都很少,通常TCP會根據優化算法把這些數據合成一個TCP段后一次發送出去,這樣接收方就收到了粘包數據。
會發生黏包的兩種情況:
1、發送方的緩存機制
發送端需要等緩沖區滿才發送出去,造成黏包(發送數據時間間隔很短,數據量很小,會合到一起,產生黏包)
importsocket
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()
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()
服務端
importsocket
BUFSIZE=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('egg'.encode('utf-8'))
客戶端
2、接收方的緩存機制
接收方不及時接收緩沖區的包,造成多個包接收(客戶端發送了一段數據,服務端只收了一小部分,服務端下次再收的時候還是從緩沖區拿上次遺留的數據,產生黏包)
importsocket
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()
服務端
importsocket
BUFSIZE=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 egg'.encode('utf-8'))
客戶端
黏包的解決方案
方案一
importsocket
sk=socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
conn,addr=sk.accept()whileTrue:
cmd= input('>>>')if cmd == 'q':
conn.send(b'q')breakconn.send(cmd.encode('gbk'))
num= conn.recv(1024).decode('utf-8') #2048
conn.send(b'ok')
res= conn.recv(int(num)).decode('gbk')print(res)
conn.close()
sk.close()
服務端
importsocketimportsubprocess
sk=socket.socket()
sk.connect(('127.0.0.1',8080))whileTrue:
cmd= sk.recv(1024).decode('gbk')if cmd == 'q':breakres= subprocess.Popen(cmd,shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
std_out=res.stdout.read()
std_err=res.stderr.read()
sk.send(str(len(std_out)+len(std_err)).encode('utf-8')) #2000
sk.recv(1024) #ok
sk.send(std_out)
sk.send(std_err)
sk.close()#好處:確定了我到底要接收多大的數據
#要在文件中配置一個配置項 : 就是每一次recv的大小 buffer = 4096
#當我們要發送大數據的時候 ,要明確的告訴接收方要發送多大的數據,以便接收方能夠準確的接收到所有數據
#多用在文件傳輸的過程中
#大文件的傳輸 一定是按照字節讀 每一次讀固定的字節
#傳輸的過程中 一邊讀一邊傳 接收端 一邊收一邊寫
#不好的地方:多了一次交互#send sendto 在超過一定范圍的時候 都會報錯#程序的內存管理
客戶端
方案二
借用struct模塊,該模塊可以把一個類型,如數字,轉成固定長度的bytes。我們知道長度數字可以被轉換成一個標準大小的4字節數字。因此可以利用這個特點來預先發送數據長度。
發送時:
先發送struct轉換好的數據長度4字節;再發送數據。
接收時:
先接受4個字節使用struct轉換成數字來獲取要接收的數據長度;再按照長度接收數據。
importsocket,struct,jsonimportsubprocess
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)whileTrue:
conn,addr=phone.accept()whileTrue:
cmd=conn.recv(1024)if not cmd:break
print('cmd: %s' %cmd)
res=subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
err=res.stderr.read()print(err)iferr:
back_msg=errelse:
back_msg=res.stdout.read()
conn.send(struct.pack('i',len(back_msg))) #先發back_msg的長度
conn.sendall(back_msg) #在發真實的內容
conn.close()
服務端(自定制報頭)
importsocket,time,struct
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(('127.0.0.1',8080))whileTrue:
msg=input('>>:').strip()if len(msg) == 0:continue
if 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=0
data=b''
while r_s
r_d=s.recv(1024)
data+=r_d
r_s+=len(r_d)#print(data.decode('utf-8'))
print(data.decode('gbk')) #windows默認gbk編碼
客戶端(自定制報頭)
也可以把報頭坐車字典,字典里包含將要發送的真實數據的詳細信息,然后json序列化,再用struct將序列化后的數據長度打包成4個字節。
發送時:
先發報頭長度;再編碼報頭內容然后發送;最后發真實內容。
接收時:
先接收報頭長度,用struct取出來;根據取出的長度收取報頭內容,然后解碼,反序列化;最后從反序列化的結果中取出待取數據的詳細信息,最后去取真實的數據內容。
importsocket,struct,jsonimportsubprocess
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()whileTrue:
conn,addr=phone.accept()whileTrue:
cmd=conn.recv(1024)if not cmd:break
print('cmd: %s' %cmd)
res=subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
err=res.stderr.read()print(err)iferr:
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()
服務端
importsocketimportstruct,json
ip_port=('127.0.0.1',8080)
client=socket(AF_INET,SOCK_STREAM)
client.connect(ip_port)whileTrue:
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=0
recv_data=b''
while recv_size
recv_data+=client.recv(1024)
recv_size+=len(recv_data)print(recv_data.decode('utf-8'))#print(recv_data.decode('gbk')) #windows默認gbk編碼
客戶端
總結
以上是生活随笔為你收集整理的python的网络编程用途_python---网络编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SAP、Oracle客户及实施商名单
- 下一篇: mysqljdbc设置参数