python的網絡編程
1、系統
操作系統: (Operating System,簡稱OS)是管理和控制計算機硬件與軟件資源的計算機程序,是直接運行在“裸機”上的最基本的系統軟件,任何其他軟件都必須在操作系統的支持下才能運行。
2、osi七層協議
osi七層:
物理層
數據鏈路層
網絡層
傳輸層
會話層
表示層
應用層
tcp/ip五層:
物理層
數據鏈路層
網絡層
傳輸層
應用層
tcp/ip四層:
網絡接口層
網絡層
傳輸層
應用層
3、數據鏈路層
以太網協議:
# 一組電信號構成一個數據包,叫做‘幀’
# 每一數據幀分成:報頭head和數據data兩部分
head包含:(固定18個字節)
發送者/源地址,6個字節
接收者/目標地址,6個字節
數據類型,6個字節
data包含:(最短46字節,最長1500字節)
數據包的具體內容:
head長度+data長度=最短64字節,最長1518字節,超過最大限制就分片發送
4、網絡層
ip數據包也分為head和data部分
head:長度為20到60字節
data:最長為65,515字節
而以太網數據包的”數據”部分,最長只有1500字節。因此,如果IP數據包超過了1500字節,它就需要分割成幾個以太網數據包,分開發送了。
5、傳輸層
二、socket
1、介紹
我們經常把socket翻譯為套接字,socket是在應用層和傳輸層之間的一個抽象層,它把TCP/IP層復雜的操作抽象為幾個簡單的接口供應用層調用已實現進程在網絡中通信。
2、 套接字工作流程
服務器端先初始化Socket,然後與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端連接。在這時如果有個客戶端初始化一個Socket,然後連接服務器(connect),如果連接成功,這時客戶端與服務器端的連接就建立了。客戶端發送數據請求,服務器端接收請求並處理請求,然後把回應數據發送給客戶端,客戶端讀取數據,最後關閉連接,一次交互結束
3、套接字函數
#1、服務端套接字函數
s.bind() 綁定(主機,端口號)到套接字
s.listen() 開始TCP監聽
s.accept() 被動接受TCP客戶的連接,(阻塞式)等待連接的到來
#2、客戶端套接字函數
s.connect() 主動初始化TCP服務器連接
s.connect_ex() connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常
#3、公共用途的套接字函數
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() 關閉套接字
#4、面向鎖的套接字方法
s.setblocking() 設置套接字的阻塞與非阻塞模式
s.settimeout() 設置阻塞套接字操作的超時時間
s.gettimeout() 得到阻塞套接字操作的超時時間
#5、面向文件的套接字的函數
s.fileno() 套接字的文件描述符
s.makefile() 創建一個與該套接字相關的文件
4、實現基於TCP的套接字(先啟動服務端)
#服務端: import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp協議 phone.bind(('127.0.0.1',8081)) #綁定ip和端口,讓客戶端連接 phone.listen(5) #半連接池大小 print('starting...') conn,client_addr=phone.accept() #等待客戶端連接 print(conn,client_addr) data=conn.recv(1024) #基於建立好的conn鏈接對象收發消息 conn.send(data.upper()) conn.close() #斷開鏈接 phone.close() #終止服務 #客戶端: import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp協議 phone.connect(('127.0.0.1',8081)) #連接服務器的ip和端口 phone.send('hello'.encode('utf-8')) data=phone.recv(1024) print(data) phone.close()
5、最終版基於TCP的套接字
上面的值實現發送一條消息和連接一個客戶端,所以要對程序進行修改
#服務端: import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp協議 phone.bind(('127.0.0.1',8081)) #綁定ip和端口,讓客戶端連接 phone.listen(5) #半連接池大小 while True: conn,client_addr=phone.accept() #等待客戶端連接 print(conn,client_addr) while True: data=conn.recv(1024) #基於建立好的conn鏈接對象收發消息 conn.send(data.upper()) conn.close() #斷開鏈接 phone.close() #終止服務 #客戶端: import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp協議 phone.connect(('127.0.0.1',8081)) #連接服務器的ip和端口 while True: msg=input('>>: ').strip() if len(msg) == 0:continue phone.send(msg.encode('utf-8')) data=phone.recv(1024) print(data) phone.close()
6、粘包
應用程序所看到的數據是一個整體,或說是一個流(stream),一條消息有多少字節對應用程序是不可見的,因此TCP協議是面向流的協議,這也是容易出現粘包問題的原因。
所謂粘包問題主要還是因為接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所造成的。
此外,發送方引起的粘包是由TCP協議本身造成的,TCP為提高傳輸效率,發送方往往要收集到足夠多的數據後才發送一個TCP段。
若連續幾次需要send的數據都很少,通常TCP會根據優化算法把這些數據合成一個TCP段後一次發送出去,這樣接收方就收到了粘包數據。
7、解決粘包的處理方法
程序流程:客戶端發送命令,服務端在本地執行後,返回得到的結果給客戶端
# 服務端: from socket import * import subprocess import struct server=socket(AF_INET,SOCK_STREAM) server.bind(('127.0.0.1',8088)) server.listen(5) while True: conn,client_addr=server.accept() print(client_addr) while True: try: cmd=conn.recv(8096) if not cmd:break obj=subprocess.Popen(cmd.decode('utf-8'),shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) stdout=obj.stdout.read() stderr=obj.stderr.read() total_size = len(stdout) + len(stderr) #制作固定長度的報頭 headers=struct.pack('i',total_size) conn.send(headers) conn.send(stdout) #發送命令的執行結果 conn.send(stderr) except ConnectionResetError: break conn.close() server.close() # 客戶端: from socket import * import struct client=socket(AF_INET,SOCK_STREAM) client.connect(('127.0.0.1',8088)) while True: cmd=input('>>: ').strip() if not cmd:continue client.send(cmd.encode('utf-8')) #發送命令 headers=client.recv(4) #先接收命令長度,struct模塊生成一個4個字節的結果 total_size = struct.unpack('i', headers)[0] recv_size=0 #再收命令的結果 data=b'' while recv_size < total_size: recv_data=client.recv(1024) data+=recv_data recv_size+=len(recv_data) print(data.decode('gbk')) client.close()
8、解決粘包的處理方法加強版
#服務端: from socket import * import subprocess import struct import json server=socket(AF_INET,SOCK_STREAM) server.bind(('127.0.0.1',8093)) server.listen(5) while True: conn,client_addr=server.accept() print(client_addr) while True: try: cmd=conn.recv(8096) if not cmd:break obj=subprocess.Popen(cmd.decode('utf-8'),shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) stdout=obj.stdout.read() stderr=obj.stderr.read() headers = { #制作報頭 'filepath': 'a.txt', 'md5': '123sxd123x123', 'total_size': len(stdout) + len(stderr) } headers_json = json.dumps(headers) #把headers轉為json格式 headers_bytes = headers_json.encode('utf-8') #前面的json結果得到字節形式 conn.send(struct.pack('i',len(headers_bytes))) #先發報頭的長度 conn.send(headers_bytes) #發送報頭 conn.send(stdout) #發送真實數據,正確的stdout,錯誤的stderr conn.send(stderr) except ConnectionResetError: break conn.close() server.close() #客戶端: from socket import * import struct import json client=socket(AF_INET,SOCK_STREAM) client.connect(('127.0.0.1',8093)) while True: cmd=input('>>: ').strip() if not cmd:continue client.send(cmd.encode('utf-8')) headers_size=struct.unpack('i',client.recv(4))[0] headers_bytes=client.recv(headers_size) headers_json=headers_bytes.decode('utf-8') headers_dic=json.loads(headers_json) print('========>',headers_dic) total_size=headers_dic['total_size'] recv_size=0 data=b'' while recv_size < total_size: recv_data=client.recv(1024) data+=recv_data recv_size+=len(recv_data) print(data.decode('gbk')) client.close()
9、文件下載
#服務端 import socket import os import json import struct SHARE_DIR=r'F:\SHARE' #目標文件路徑 class FtpServer: def __init__(self,host,port): self.host=host self.port=port self.server=socket.socket(socket.AF_INET,socket.SOCK_STREAM) self.server.bind((self.host,self.port)) self.server.listen(5) def serve_forever(self): print('server starting...') while True: self.conn,self.client_addr=self.server.accept() print(self.client_addr) while True: try: data=self.conn.recv(1024) #params_json.encode('utf-8') if not data:break params=json.loads(data.decode('utf-8')) #params=['get','a.txt'] cmd=params[0] if hasattr(self,cmd): func=getattr(self,cmd) func(params) else: print('\033[45mcmd not exists\033[0m') except ConnectionResetError: break self.conn.close() self.server.close() def get(self,params): #params=['get','a.txt'] filename=params[1] #filename='a.txt' filepath=os.path.join(SHARE_DIR,filename) if os.path.exists(filepath): headers = { #制作報頭 'filename': filename, 'md5': '123sxd123x123', 'filesize': os.path.getsize(filepath) } headers_json = json.dumps(headers) headers_bytes = headers_json.encode('utf-8') self.conn.send(struct.pack('i',len(headers_bytes))) #先發報頭的長度 self.conn.send(headers_bytes) #發送報頭 with open(filepath,'rb') as f: #發送真實的數據 for line in f: self.conn.send(line) def put(self): pass if __name__ == '__main__': server=FtpServer('127.0.0.1',8081) server.serve_forever() ##客戶端 import socket import struct import json import os DOWNLOAD_DIR=r'F:\DOWNLOAD' #下載路徑 class FtpClient: def __init__(self,host,port): self.host=host self.port=port self.client=socket.socket(socket.AF_INET,socket.SOCK_STREAM) self.client.connect((self.host,self.port)) def interactive(self): while True: data=input('>>: ').strip() #get a.txt if not data:continue params=data.split() #parmas=['get','a.txt'] cmd=params[0] #cmd='get' if hasattr(self,cmd): func=getattr(self,cmd) func(params) #func(['get','a.txt']) def get(self,params): params_json=json.dumps(params) self.client.send(params_json.encode('utf-8')) headers_size = struct.unpack('i', self.client.recv(4))[0] #接收報頭長度 headers_bytes = self.client.recv(headers_size) #接收報頭 headers_json = headers_bytes.decode('utf-8' headers_dic = json.loads(headers_json) # print('========>', headers_dic) filename = headers_dic['filename'] filesize = headers_dic['filesize'] filepath = os.path.join(DOWNLOAD_DIR, filename) with open(filepath, 'wb') as f: #接收真實數據 recv_size = 0 while recv_size < filesize: line = self.client.recv(1024) recv_size += len(line) f.write(line) print('===>下載成功') if __name__ == '__main__': client=FtpClient('127.0.0.1',8081) client.interactive()
python的網絡編程