網絡編程基礎(一)
-
- TCP/IP協議
- OSI/RM協議
- 特點:獨立於計算機硬件和操作系統,統一分配網絡地址,提供可靠服務,隔離了物理網絡的硬件差異
- 協議分層(百度):網絡接口層:IEE802.3(以太網協議集),IEEE802.4(令牌環網);網絡層(IP);傳輸層(tcp/udp);應用層(FTP/HTTP/SMTP/DNS)
- IP地址和端口
- TCP/IP協議
- 網絡編程基礎(二)
- UDP協議
- TCP協議
- 套接字Socket
- TCP連接的端點稱作套接字
- 表示方法:IP地址:端口號,一個socket就是:(ip地址:端口號)
- 一個TCP連接就是兩個套接字,也就是{(IP地址:端口號),(IP地址:端口號)}
- 每一條TCP連接被兩個套接字確定,同一個ip地址可以有不同的TCP連接,同一個端口號可以出現在不同的TCP連接中
- TCP和UDP的不同點
- TCP先建立連接,再通信,最后釋放連接,udp不用連接
- TCP保證數據可靠交付;TCP不保證可靠交付,用戶自行處理可靠性
- TCP連接開銷大,UDP小;TCP使用實時性低,數據可靠性高的場合,UDP適合實用性高,數據可靠性低的場合
- TCP和UDP的相同點
- 都位於TCP/IP協議的第四層
- 為應用層提供服務,都要通過網際層來一線數據的傳輸ICMP協議
- TCP協議
- HTTP,FTP,TELNET,POP,SMTP
- UDP協議
- TFTP,DNS,SNMP,VOIP,QQ
- 服務器端socket的建立
- C/S模式簡介:客戶/服務器模式,客戶端為主動向服務器發出服務請求的一方。服務器一般在系統啟動時自動調用運行,等待客戶機的請求
與C/S模式相對的是B/S(瀏覽器/服務器模式),客戶端使用同意的瀏覽器,而不用裝門部署,服務器和瀏覽器使用HTTP協議進行通信 - 套接字網絡編程
- TCP通信

- UDP通信

- C/S模式簡介:客戶/服務器模式,客戶端為主動向服務器發出服務請求的一方。服務器一般在系統啟動時自動調用運行,等待客戶機的請求
- Python中的socket
- socket對象是支持網絡通信的,socket對象支持使用TCP/UDP協議進行網絡通信(只能選擇其中一個協議)
- socket編程所需要的對象函數和常量
- 創建套接字:socket.socket(family,type) family表示套接字使用什么版本協議 Type=SOCK_STREAM(使用TCP) type=sock_DGRAM(UDP協議)
- 服務器端套接字的方法
- bind(address)綁定,address為(ip:port):將套接字綁定到本地主機的ip或者端口上;
- listen(backlog):開始Tcp轉入連接。backlog拒絕連接前允許操作系統掛起的連接數,1-5
- accept():接收TCP連接,並返回連接上的套接字對象和客戶端地址構成的元組。返回的連接上的套接字對象可用於接收和發送信息
- 客戶端socket對象的方法
- connect(address),address=(hostname,port)構成的元組,建立與服務器間的連接
- TCP協議的socket收發數據的方法
- recv(【buffersize】):接收數據,參數為接收最大數據量,返回接收的數據
- send(bytes)通過socket發送數據
- sendall(bytes)通過socket發送數據(返回前將數據都發送出去)
- UDP協議的socket收發數據方法
- recvfrom(與上面類似)
- sendto(bytes,address)發送的字節和指定發送的目的地址
- 關閉socket;close()
- 其他相關函數
- gethostname()返回本地主機名
- gethostbyname_ex(hostname)#返回元組(hostname,aliaslist,ipaddrrlist)
- 用socket建立TCP服務器端方法
- 基本步驟
- 創建套接字並綁定地址,開始監聽連接,接收連接並收發數據,關閉套接字
-
View Code#coding=gbk import socket HOST = '' PORT = 321 s=socket.socket() s.bind((HOST,PORT)) s.listen(5) client,addr=s.accept()#接收客戶端的連接,返回一個客戶端, print('client address:',addr) while True: data = client.recv(1024)#接收數據 if not data:#為空,斷開連接 break else: print('receive data :',data.decode('utf-8'))#數據不為空,輸出 client.send(data)#將數據發揮客戶端 client.close() s.close()
- 基本步驟
- 用socket建立UDP服務器端方法
- 基本步驟
- 創建套接字並綁定地址,開始監聽連接,收發數據,關閉套接字
-
View Code#coding=gbk import socket HOST = '' PORT = 3214 #socket.socket(family,type) family表示套接字使用什么版本協議 Type=SOCK_STREAM(使用TCP) type=sock_DGRAM(UDP協議) s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)#使用iPv4協議 s.bind((HOST,PORT)) data = True while data: data,addr = s.recvfrom(1024) if data==b'bye':#為空,斷開連接 break else: print('receive data :',data.decode('utf-8'))#數據不為空,輸出 s.send(data,addr)#將數據發揮客戶端 s.close()
- 基本步驟
- 客戶端socket建立
- socket的異常
- error:套接字或者地址有關的錯誤
- herror:error的子類,表示發生與地址有關的錯誤
- gaierror:getaddressinfo()或者gethostinfo()中的與地址有關的錯誤
- timeout:套接字超時錯誤
- 處理異常
- try,catch進行
- 用TCP實現客戶端
- 基本步驟
- 創建套接字,用套接字連接服務器;收發數據;關閉套接字
View Code#coding=gbk import socket HOST = '127.0.0.1' PORT = 3215 s=socket.socket() try: s.connect((HOST,PORT)) data="nihao!" while data: s.sendall(data.encode('utf-8'))#編碼發送出去的信息 data=s.recv(1024)#接收數據 print('reveive is :\n',data.decode('utf-8'))#解碼打印收到的數據 data=input('please input string:\n') except socket.errno as err: print(err) finally: s.close()
- 創建套接字,用套接字連接服務器;收發數據;關閉套接字
- 基本步驟
- 用UDP實現客戶端
- 基本步驟
- 創建套接字,收發數據,關閉套接字
-
View Code#coding=gbk import socket HOST = '127.0.0.1' PORT = 3215 s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)#使用iPv4協議) data='nihao' while data: s.sendto(data.encode('utf-8'),(HOST,PORT))#編碼發送出去的信息 if data=='bye': break data,addr=s.recvfrom(1024) print('receive is :\n',data.decode('utf-8')) data=input('please input string:\n') s.close()
- 基本步驟
- socket的異常
- 備份服務器的服務器端的實現
- 嘗試一個C/S模式下網絡遠程備份系統,C/S模式下開發服務器端和客戶端,穿送的是文件所以使用TCP協議進行
- 備份服務器的功能分析
- 可以自定義服務的IP的地址和端口號
- 指定保存備份文件的目錄
- 關閉服務器
- 以多線程的方式同時為多個客戶端提供備份服務
-
View Code#coding=gbk from tkinter import * from tkinter.ttk import * import socket import struct def start(host,port): pass def MyFrame(Frame): #初始化構造方法 def __init__(self,root): super().__init__(root) self.root=root self.grid()#布局方式網格布局 self.local_ip='127.0.0.1'#服務器端默認的IP地址 self.serv_ports=[10888,20888,30888]#三個服務器的端口 self.init_components()#初始化界面方法 #界面函數的實現 def init_components(self):#初始化用戶界面的方法 proj_name=Label(self,text='遠程備份服務器') proj_name.grid(columnspan=2)#網格式布局,占用兩列 serve_ip_label=Label(self,text='服務地址') serve_ip_label.grid(row=1)#列是默認為0列 #下拉列表,顯示服務器的地址拱用戶選擇 self.serv_ip=Combobox(self,values=self.get_ipaddr()) #設置默認的服務器的ip地址 self.serv_ip.set(self.local_ip) self.serv_ip.grid(row=1,column=1) #服務端口的LABEL serv_port_label=Label(self,text='服務端口') serv_port_label.grid(row=2) #下拉列表,顯示服務器的服務端口拱用戶選擇 self.serv_port=Combobox(self,values=self.serv_ports) #設置默認的服務端口 self.serv_port.set(self.serv_ports[1]) #網格布局 放置指定位置 self.serv_port.grid(row=2,column=1) #啟動按鈕 self.start_serv_btn=Button(self,text='啟動服務',command=self.start_serv) self.start_serv_btn.grid(row=3) #退出服務的按鈕 self.start_exit_btn=Button(self,text='退出服務',command=self.root.destroy) self.start_exit_btn.grid(row=3,column=1) def get_ipaddr(self): #獲取本機的ip地址 #獲取名字 host_name=socket.gethostname() #獲取信息 info=socket.gethostbyname_ex(host_name) info=info[2] info.append(self.local_ip) #定義啟動服務器的方法 def start_serv(self): print(self.serv_ip.get(),self.serv_port.get()) start(self.serv_ip.get(),self.serv_port.get()) if __name__=='__main__': root=Tk() root.title('備份服務器') root.resizable(FALSE, FALSE)#允許用戶更改窗體大小 a=MyFrame(root) a.mainloop()
- 最簡備份服務器端建立
- 同一時間只能連接一個客戶並且為其備份
- 備份客戶端的一個目錄及其子目錄的所有文件
- 與客戶端交互
- 客戶端發送即將要發送文件信息的大小
- 服務器端接收客戶端通知的文件信息的大小
- 客戶端發送文件信息(包括文件大小。文件名)
- 服務器端依照文件信息的大小接收文件信息
- 客戶端逐個發送文件數據,每發送完一個文件數據,接收該文件的備份結果
- 服務器端接收文件數據並保存備份至文件系統,每接收完一個文件就返回備份結果
-
View Code#coding=gbk from tkinter import * from tkinter.ttk import * import socket import os import struct import pickle #建立一個默認的備份目錄 BAK_PATH=r'e:\bak' #根據指定長度來接受文件信息 def recv_unit_data(clnt,infos_len): data=b'' #原來文件為空 #如果要接受的文件在0-1024之間就直接接收該文件,如果大於1024需要循環分段接收,每次只是接收1024個字節,剩下的在全部接收即可 if 0<infos_len<=1024:# data+=clnt.recv(infos_len)#接收文件 else:#長度太長 while True: if infos_len >1024: data+=clnt.recv(1024) infos_len-=1024 else: data+=clnt.recv(infos_len) break return data def get_files_info(clnt): fmt_str='Q'#用於向服務器端傳送文件信息的大小 headsize=struct.calcsize(fmt_str)#計算長度 data=clnt.recv(headsize) infos_len=struct.unpack(fmt_str, data)[0] data =recv_unit_data(clnt, infos_len) return pickle.loads(data)#得到文件信息的列表 def mk_math(filepath): #建立文件路徑 paths=filepath.split(os.path.sep)[:-1]#將文件路徑進行分割 p=BAK_PATH for path in paths:#遍歷用戶端傳來的路徑 p=os.path.join(p,path)#將保存的路徑添加到默認的路徑上 if not os.path.exists(p):#如果路徑不存在就建立路徑 os.mkdir(p) #接收客戶端傳來文件,並且根據文件信息來進行保存備份 def recv_file(clnt,infos_len,filepath): mk_math(filepath)#遍歷文件 通過路徑 filepath = os.path.join(BAK_PATH,filepath)#服務器上的路徑的文件名 f = open(filepath,'wb+')#新建一個文件 #接收文件 try: if 0 < infos_len <=1024: data = clnt.recv(infos_len) f.write(data) else: while True: if infos_len >1024: data=clnt.recv(1024) f.write(data) infos_len-=1024 else: data = clnt.recv(infos_len) f.write(data) break except: print('error') else: return True finally: f.close() #向客戶端發送失敗成功消息 def send_echo(clnt,res): if res: clnt.sendall(b'success') else: clnt.sendall(b'failure') #啟動服務器的方法 def start(host,port): if not os.path.exists(BAK_PATH): os.mkdir(BAK_PATH) st=socket.socket() #tcp協議 st.bind(host,port) #綁定套接字 st.listen(1) #偵聽網絡,一個客戶端連接 client,addr=st.accept() #接收連接,建立連接 files_lst=get_files_info(client)#獲取客戶端要傳送的文件列表(包括文件大小和文件路徑:元組) for size,filepath in files_lst:#遍歷得到文件大小和路徑 res = recv_file(client,size,filepath)#接收所有的文件 返回備份的結果:true或者false send_echo(client,res)#保存成功標志,發送給客戶端 client.close()#關閉客戶端 st.close() def MyFrame(Frame): #初始化構造方法 def __init__(self,root): super().__init__(root) self.root=root self.grid()#布局方式網格布局 self.local_ip='127.0.0.1'#服務器端默認的IP地址 self.serv_ports=[10888,20888,30888]#三個服務器的端口 self.init_components()#初始化界面方法 #界面函數的實現 def init_components(self):#初始化用戶界面的方法 proj_name=Label(self,text='遠程備份服務器') proj_name.grid(columnspan=2)#網格式布局,占用兩列 serve_ip_label=Label(self,text='服務地址') serve_ip_label.grid(row=1)#列是默認為0列 #下拉列表,顯示服務器的地址拱用戶選擇 self.serv_ip=Combobox(self,values=self.get_ipaddr()) #設置默認的服務器的ip地址 self.serv_ip.set(self.local_ip) self.serv_ip.grid(row=1,column=1) #服務端口的LABEL serv_port_label=Label(self,text='服務端口') serv_port_label.grid(row=2) #下拉列表,顯示服務器的服務端口拱用戶選擇 self.serv_port=Combobox(self,values=self.serv_ports) #設置默認的服務端口 self.serv_port.set(self.serv_ports[1]) #網格布局 放置指定位置 self.serv_port.grid(row=2,column=1) #啟動按鈕 self.start_serv_btn=Button(self,text='啟動服務',command=self.start_serv) self.start_serv_btn.grid(row=3) #退出服務的按鈕 self.start_exit_btn=Button(self,text='退出服務',command=self.root.destroy) self.start_exit_btn.grid(row=3,column=1) def get_ipaddr(self): #獲取本機的ip地址 #獲取名字 host_name=socket.gethostname() #獲取信息 info=socket.gethostbyname_ex(host_name) info=info[2] info.append(self.local_ip) #定義啟動服務器的方法 def start_serv(self): print(self.serv_ip.get(),self.serv_port.get()) start(self.serv_ip.get(),int(self.serv_port.get())) if __name__=='__main__': root=Tk() root.title('備份服務器') root.resizable(FALSE, FALSE)#允許用戶更改窗體大小 a=MyFrame(root) a.mainloop()
- 備份服務器的基本客戶端實現
- 功能
- 設置連接服務器的IP地址和端口號
- 輸入備份目錄,備份其中的所有文件
- 顯示服務器端發來的備份結果
- 選擇備份時啟用壓縮備份
- 功能
- 客戶端與服務器最終版
-
View Code#coding=gbk from tkinter import * from tkinter.ttk import * import socket import os import struct import pickle #建立一個默認的備份目錄 BAK_PATH=r'e:\bak' #根據指定長度來接受文件信息 def recv_unit_data(clnt,infos_len): data=b'' #原來文件為空 #如果要接受的文件在0-1024之間就直接接收該文件,如果大於1024需要循環分段接收,每次只是接收1024個字節,剩下的在全部接收即可 if 0<infos_len<=1024:# data+=clnt.recv(infos_len)#接收文件 else:#長度太長 while True: if infos_len >1024: data+=clnt.recv(1024) infos_len-=1024 else: data+=clnt.recv(infos_len) break return data def get_files_info(clnt): fmt_str='Q'#用於向服務器端傳送文件信息的大小 headsize=struct.calcsize(fmt_str)#計算長度 data=clnt.recv(headsize) infos_len=struct.unpack(fmt_str, data)[0] data =recv_unit_data(clnt, infos_len) return pickle.loads(data)#得到文件信息的列表 def mk_math(filepath): #建立文件路徑 paths=filepath.split(os.path.sep)[:-1]#將文件路徑進行分割 p=BAK_PATH for path in paths:#遍歷用戶端傳來的路徑 p=os.path.join(p,path)#將保存的路徑添加到默認的路徑上 if not os.path.exists(p):#如果路徑不存在就建立路徑 os.mkdir(p) #接收客戶端傳來文件,並且根據文件信息來進行保存備份 def recv_file(clnt,infos_len,filepath): mk_math(filepath)#遍歷文件 通過路徑 filepath = os.path.join(BAK_PATH,filepath)#服務器上的路徑的文件名 f = open(filepath,'wb+')#新建一個文件 #接收文件 try: if 0 < infos_len <=1024: data = clnt.recv(infos_len) f.write(data) else: while True: if infos_len >1024: data=clnt.recv(1024) f.write(data) infos_len-=1024 else: data = clnt.recv(infos_len) f.write(data) break except: print('error') else: return True finally: f.close() #向客戶端發送失敗成功消息 def send_echo(clnt,res): if res: clnt.sendall(b'success') else: clnt.sendall(b'failure') #啟動服務器的方法 def start(host,port): if not os.path.exists(BAK_PATH): os.mkdir(BAK_PATH) st=socket.socket() #tcp協議 st.bind((host,port)) #綁定套接字 st.listen(1) #偵聽網絡,一個客戶端連接 client,addr=st.accept() #接收連接,建立連接 files_lst=get_files_info(client)#獲取客戶端要傳送的文件列表(包括文件大小和文件路徑:元組) for size,filepath in files_lst:#遍歷得到文件大小和路徑 res = recv_file(client,size,filepath)#接收所有的文件 返回備份的結果:true或者false send_echo(client,res)#保存成功標志,發送給客戶端 client.close()#關閉客戶端 st.close() class MyFrame(Frame): #初始化構造方法 def __init__(self,root): super().__init__(root) self.root=root self.grid()#布局方式網格布局 self.local_ip='127.0.0.1'#服務器端默認的IP地址 self.serv_ports=[10888,20888,30888]#三個服務器的端口 self.init_components()#初始化界面方法 #界面函數的實現 def init_components(self):#初始化用戶界面的方法 proj_name=Label(self,text='遠程備份服務器') proj_name.grid(columnspan=2)#網格式布局,占用兩列 serve_ip_label=Label(self,text='服務地址') serve_ip_label.grid(row=1)#列是默認為0列 #下拉列表,顯示服務器的地址拱用戶選擇 self.serv_ip=Combobox(self,values=self.get_ipaddr()) #設置默認的服務器的ip地址 self.serv_ip.set(self.local_ip) self.serv_ip.grid(row=1,column=1) #服務端口的LABEL serv_port_label=Label(self,text='服務端口') serv_port_label.grid(row=2) #下拉列表,顯示服務器的服務端口拱用戶選擇 self.serv_port=Combobox(self,values=self.serv_ports) #設置默認的服務端口 self.serv_port.set(self.serv_ports[1]) #網格布局 放置指定位置 self.serv_port.grid(row=2,column=1) #啟動按鈕 self.start_serv_btn=Button(self,text='啟動服務',command=self.start_serv) self.start_serv_btn.grid(row=3) #退出服務的按鈕 self.start_exit_btn=Button(self,text='退出服務',command=self.root.destroy) self.start_exit_btn.grid(row=3,column=1) def get_ipaddr(self): #獲取本機的ip地址 #獲取名字 host_name=socket.gethostname() #獲取信息 info=socket.gethostbyname_ex(host_name) info=info[2] info.append(self.local_ip) #定義啟動服務器的方法 def start_serv(self): print(self.serv_ip.get(),self.serv_port.get()) start(self.serv_ip.get(),int(self.serv_port.get())) if __name__=='__main__': root=Tk() root.title('備份服務器') root.resizable(FALSE, FALSE)#允許用戶更改窗體大小 a=MyFrame(root) a.mainloop() #coding=gbk from tkinter import * from tkinter.ttk import * import socket import os import pickle import struct #獲取給出路徑的文件信息 def get_file_info(path): if not path or not os.path.exists(path): return NONE files=os.walk(path)#獲取文件 infos=[] file_paths=[] for p,d,fs in files:#文件都在fs列表中 for f in fs: file_name=os.path.join(p,f)#獲取文件的文件名 file_size=os.stat(file_name).st_size#獲取文件的大小 file_paths.append(file_name)#加入到file_path file_name=file_name[len(path)+1:] infos.append((file_size,file_name))#將文件信息加入到文件信息中 return infos,file_paths #向服務器端發送文件信息 def send_files_infos(my_sock,infos): fmt_str='Q' infos_bytes=pickle.dumps(infos)#對文件信息進行二進制編碼 infos_bytes_len=len(infos_bytes)#獲取長度 infos_len_pack=struct.pack(fmt_str,infos_bytes_len)#對長度利用struct進行二進制編碼 my_sock.sendall(infos_len_pack)#將整個發送放到服務器端 my_sock.sendall(infos_bytes)#發送文件信息 def send_files(my_sock,file_path):#本機文件,本機文件路徑 f = open(file_path,'rb') try: while True: data=f.read(1024) if data: my_sock.sendall(data)#發送 else: break finally: f.close() def get_bak_info(my_sock,size=7): info = my_sock.recv(size) print(info.decode('utf-8')) def start(host,port,src): if not os.path.exists(src): print('備份的目標不存在!') return s = socket.socket()#TCP協議 s.connect((host,port)) path = src#獲取用戶的備份路徑 file_infos,file_paths=get_file_info(path)#獲取要備份的文件信息和路徑 send_files_infos(s,file_infos)#發送文件信息 for fp in file_paths:#發送所有信息至S send_files(s,fp) print(fp)#把發送出去的文件的信息打印 get_bak_info(s)#獲取備份的結果 s.close() class MyFrame(Frame): #初始化構造方法 def __init__(self,root): super().__init__(root) self.root=root self.grid()#布局方式網格布局 self.remote_ip='127.0.0.1'#服務器端的IP地址默認值 self.remote_ports=10888#默認的端口 self.remote_ip_var=StringVar()#輸入框 self.remote_ports_var=IntVar() self.bak_src_var=StringVar() self.init_components()#初始化界面方法 #界面函數的實現 def init_components(self):#初始化用戶界面的方法 proj_name=Label(self,text='遠程備份客戶端') proj_name.grid(columnspan=2)#網格式布局,占用兩列 serve_ip_label=Label(self,text='服務地址:') serve_ip_label.grid(row=1)#列是默認為0列 #下拉列表,顯示服務器的地址拱用戶選擇 self.serv_ip=Entry(self,textvariable=self.remote_ip_var) #設置默認的服務器的ip地址e self.remote_ports_var.set(self.remote_ip) self.serv_ip.grid(row=1,column=1) #服務端口的LABEL serv_port_label=Label(self,text='服務端口:') serv_port_label.grid(row=2) #下拉列表,顯示服務器的服務端口拱用戶選擇 self.serv_port=Entry(self,textvariable=self.remote_ports_var) #設置默認的服務端口 self.remote_ports_var.set(self.remote_ports) #網格布局 放置指定位置 self.serv_port.grid(row=2,column=1) #用戶備份的數據 src_label=Label(self,text='備份的目標:') src_label.grid(row=3) #輸入框 self.bak_src=Entry(self,textvariable=self.bak_src_var) self.bak_src.grid(row=3,column=1 ) # self.start_serv_btn=Button(self,text='開始備份',command=self.start_send) self.start_serv_btn.grid(row=4) # self.start_exit_btn=Button(self,text='退出程序',command=self.root.destroy) self.start_exit_btn.grid(row=4,column=1) #定義啟動服務器的方法 def start_send(self): print(self.remote_ip_var.get(),self.remote_ports_var.get()) print('start...') start(self.remote_ip_var.get(),int(self.remote_ports_var.get()),self.bak_src_var.get())#想服務器發送東西 if __name__=='__main__': root=Tk() root.title('遠程備份客戶機') root.resizable(FALSE, FALSE)#允許用戶更改窗體大小 a = MyFrame(root) a.mainloop()
- 通過多線程實現備份服務器端
- 單線程服務端問題
- 啟動服務,界面停止響應
- 一個客戶端正在備份,其他客戶端不能連接
- 建立多線程服務器
- 解決點擊啟動服務,頁面停止響應的問題,實現多個客戶端進行交互
- 退出服務器:將服務線程配置為后台線程(可能使文件丟失);應用線程同步的手段退出服務(這個方法好)
- 可壓縮備份服務
- 客戶端發送已壓縮文件
- 與客戶端交互基本流程
- 單線程服務端問題
- 通過多線程實現備份客戶端
- 最終版
-
View Code#coding=gbk from tkinter import * from tkinter.ttk import * import socket import threading import os import struct import pickle import threading #使用戶圖形界面和服務器退出循環變量 SERV_RUN_FLAG=TRUE flag_lock = threading.Lock() #建立一個默認的備份目錄 BAK_PATH=r'e:\bak' #根據指定長度來接受文件信息 def recv_unit_data(clnt,infos_len): data=b'' #原來文件為空 #如果要接受的文件在0-1024之間就直接接收該文件,如果大於1024需要循環分段接收,每次只是接收1024個字節,剩下的在全部接收即可 if 0<infos_len<=1024:# data+=clnt.recv(infos_len)#接收文件 else:#長度太長 while True: if infos_len >1024: data+=clnt.recv(1024) infos_len-=1024 else: data+=clnt.recv(infos_len) break return data def get_files_info(clnt): fmt_str='Q?'#用於向服務器端傳送文件信息的大小,文件信息的壓縮選項 headsize=struct.calcsize(fmt_str)#計算長度 data=clnt.recv(headsize) infos_len,compress=struct.unpack(fmt_str, data) data =recv_unit_data(clnt, infos_len) return pickle.loads(data),compress#得到文件信息的列表 def mk_math(filepath): #建立文件路徑 paths=filepath.split(os.path.sep)[:-1]#將文件路徑進行分割 p=BAK_PATH for path in paths:#遍歷用戶端傳來的路徑 p=os.path.join(p,path)#將保存的路徑添加到默認的路徑上 if not os.path.exists(p):#如果路徑不存在就建立路徑 os.mkdir(p) def get_compress_size(clnt): fmt_str = 'Q'#長整型 size=struct.calcsize(fmt_str) data = clnt.recv(size) size,=struct.unpack(fmt_str,data)#得到壓縮后文件的大小 return size #接收客戶端傳來文件,並且根據文件信息來進行保存備份 def recv_file(clnt,infos_len,filepath,compress): mk_math(filepath)#遍歷文件 通過路徑 filepath = os.path.join(BAK_PATH,filepath)#服務器上的路徑的文件名 #根據壓縮選項判斷 if compress : infos_len = get_compress_size(clnt)#壓縮后文件的長度 filepath = ''.join(os.path.splitext(filepath)[0],'.tar.gz') f = open(filepath,'wb+')#新建一個文件 #接收文件 try: if 0 < infos_len <=1024: data = clnt.recv(infos_len) f.write(data) else: while True: if infos_len >1024: data=clnt.recv(1024) f.write(data) infos_len-=1024 else: data = clnt.recv(infos_len) f.write(data) break except: print('error') else: return True finally: f.close() #向客戶端發送失敗成功消息 def send_echo(clnt,res): if res: clnt.sendall(b'success') else: clnt.sendall(b'failure') def client_operate(client): #compress 可壓縮選項 files_lst,compress=get_files_info(client)#獲取客戶端要傳送的文件列表(包括文件大小和文件路徑:元組) for size,filepath in files_lst:#遍歷得到文件大小和路徑 res = recv_file(client,size,filepath,compress)#接收所有的文件 返回備份的結果:true或者false send_echo(client,res)#保存成功標志,發送給客戶端 client.close()#關閉客戶端 #啟動服務器的方法 def start(host,port): if not os.path.exists(BAK_PATH): os.mkdir(BAK_PATH) st=socket.socket() #tcp協議 st.settimeout(1) #為了退出的時候能夠有時間獲得共享資源的鎖,保證服務器端正常的退出;防止while中可以退出 st.bind((host,port)) #綁定套接字 st.listen(1) #偵聽網絡,一個客戶端連接 #獲得serv_run_falg的訪問權 flag_lock.acquire() while SERV_RUN_FLAG:#多次服務 多線程 flag_lock.release()#釋放訪問權 client=None try: client,addr=st.accept() #接收連接,建立連接 #線程化的啟動client_operater函數 防止當前進程正在執行的時候其他線程也要進程這個服務,所以我們就每次有客戶端想要進行連接的時候,我們就創建一個線程去為每一個要求服務的東西提供服務 #多個服務器為多個客戶端進行服務 except socket.timeout:#超時 pass if client: t =threading.Thread(target=client_operate,args=(client,)) t.start() flag_lock.acquire()#為了下次進入循環的時候仍然要鎖定共享變量 st.close() class MyFrame(Frame): #初始化構造方法 def __init__(self,root): super().__init__(root) self.root=root self.grid()#布局方式網格布局 self.local_ip='127.0.0.1'#服務器端默認的IP地址 self.serv_ports=[10888,20888,30888]#三個服務器的端口 self.init_components()#初始化界面方法 #界面函數的實現 def init_components(self):#初始化用戶界面的方法 proj_name=Label(self,text='遠程備份服務器') proj_name.grid(columnspan=2)#網格式布局,占用兩列 serve_ip_label=Label(self,text='服務地址') serve_ip_label.grid(row=1)#列是默認為0列 #下拉列表,顯示服務器的地址拱用戶選擇 self.serv_ip=Combobox(self,values=self.get_ipaddr()) #設置默認的服務器的ip地址 self.serv_ip.set(self.local_ip) self.serv_ip.grid(row=1,column=1) #服務端口的LABEL serv_port_label=Label(self,text='服務端口') serv_port_label.grid(row=2) #下拉列表,顯示服務器的服務端口拱用戶選擇 self.serv_port=Combobox(self,values=self.serv_ports) #設置默認的服務端口 self.serv_port.set(self.serv_ports[1]) #網格布局 放置指定位置 self.serv_port.grid(row=2,column=1) #啟動按鈕 self.start_serv_btn=Button(self,text='啟動服務',command=self.start_serv) self.start_serv_btn.grid(row=3) #退出服務的按鈕 self.start_exit_btn=Button(self,text='退出服務',command=self.root.destroy) self.start_exit_btn.grid(row=3,column=1) def get_ipaddr(self): #獲取本機的ip地址 #獲取名字 host_name=socket.gethostname() #獲取信息 info=socket.gethostbyname_ex(host_name) info=info[2] info.append(self.local_ip) #定義啟動服務器的方法 def start_serv(self): #線程化運行 # print(self.serv_ip.get(),self.serv_port.get()) # start(self.serv_ip.get(),int(self.serv_port.get())) host = self.serv_ip.get()#獲取服務器地址 port = int(self.serv_port.get())#獲取服務端口,轉化為整型 serv_th= threading.Thread(target=start,args=(host,port))#建立線程化服務器 serv_th.start() #當點擊啟動服務之后,我們關閉這個按鈕不讓服務再次啟動 self.start_serv_btn.state(['disabled',]) #建立一個自己的跟窗口的類,為了退出 class MyTk(Tk): def destroy(self): global SERV_RUN_FLAG while True: if flag_lock.acquire():#獲取全局共享變量 SERV_RUN_FLAG=False flag_lock.release() break super().destroy() if __name__=='__main__': root=MyTk() root.title('備份服務器') root.resizable(FALSE, FALSE)#允許用戶更改窗體大小 a=MyFrame(root) a.mainloop() #coding=gbk from tkinter import * from tkinter.ttk import * import socket import os import pickle import struct import time import threading import tarfile,tempfile #獲取給出路徑的文件信息 def get_file_info(path): if not path or not os.path.exists(path): return NONE files=os.walk(path)#獲取文件 infos=[] file_paths=[] for p,d,fs in files:#文件都在fs列表中 for f in fs: file_name=os.path.join(p,f)#獲取文件的文件名 file_size=os.stat(file_name).st_size#獲取文件的大小 file_paths.append(file_name)#加入到file_path file_name=file_name[len(path)+1:] infos.append((file_size,file_name))#將文件信息加入到文件信息中 return infos,file_paths #向服務器端發送文件信息 def send_files_infos(my_sock,infos,compress): fmt_str='Q?' infos_bytes=pickle.dumps(infos)#對文件信息進行二進制編碼 infos_bytes_len=len(infos_bytes)#獲取長度 infos_len_pack=struct.pack(fmt_str,infos_bytes_len,compress)#對長度利用struct進行二進制編碼 my_sock.sendall(infos_len_pack)#將整個發送放到服務器端 my_sock.sendall(infos_bytes)#發送文件信息 def send_files(my_sock,file_path,compress):#本機文件,本機文件路徑 if not compress: f = open(file_path,'rb') else: f = tempfile.NamedTemporaryFile() tar=tarfile.open(mode='w|gz',fileobj=f) tar.add(file_path) tar.close() f.seek(0) filesize = os.stat(f.name).st_size filesize_bytes=struct.pack('Q', filesize) my_sock.sendall(filesize_bytes) try: while True: data=f.read(1024) if data: my_sock.sendall(data)#發送 else: break finally: f.close() def get_bak_info(my_sock,size=7): info = my_sock.recv(size) print(info.decode('utf-8')) def start(host,port,src,compress): if not os.path.exists(src): print('備份的目標不存在!') return s = socket.socket()#TCP協議 s.connect((host,port)) path = src#獲取用戶的備份路徑 file_infos,file_paths=get_file_info(path)#獲取要備份的文件信息和路徑 send_files_infos(s,file_infos,compress)#發送文件信息 for fp in file_paths:#發送所有信息至S send_files(s,fp,compress) print(fp)#把發送出去的文件的信息打印 get_bak_info(s)#獲取備份的結果 s.close() class MyFrame(Frame): #初始化構造方法 def __init__(self,root): super().__init__(root) self.root=root self.grid()#布局方式網格布局 self.remote_ip='127.0.0.1'#服務器端的IP地址默認值 self.remote_ports=10888#默認的端口 self.remote_ip_var=StringVar()#輸入框 self.remote_ports_var=IntVar() self.bak_src_var=StringVar() self.compress_var=BooleanVar() self.init_components()#初始化界面方法 #界面函數的實現 def init_components(self):#初始化用戶界面的方法 proj_name=Label(self,text='遠程備份客戶端') proj_name.grid(columnspan=2)#網格式布局,占用兩列 serve_ip_label=Label(self,text='服務地址:') serve_ip_label.grid(row=1)#列是默認為0列 #下拉列表,顯示服務器的地址拱用戶選擇 self.serv_ip=Entry(self,textvariable=self.remote_ip_var) #設置默認的服務器的ip地址e self.remote_ports_var.set(self.remote_ip) self.serv_ip.grid(row=1,column=1) #服務端口的LABEL serv_port_label=Label(self,text='服務端口:') serv_port_label.grid(row=2) #下拉列表,顯示服務器的服務端口拱用戶選擇 self.serv_port=Entry(self,textvariable=self.remote_ports_var) #設置默認的服務端口 self.remote_ports_var.set(self.remote_ports) #網格布局 放置指定位置 self.serv_port.grid(row=2,column=1) #用戶備份的數據 src_label=Label(self,text='備份的目標:') src_label.grid(row=3) #輸入框 self.bak_src=Entry(self,textvariable=self.bak_src_var) self.bak_src.grid(row=3,column=1) tar_label=Label(self,text='備份壓縮:') tar_label.grid(row =4 ) self.compress_on=Checkbutton(self,text='開始壓縮',variable=self.compress_var,onvalue=1,offvalue=0) self.compress_on.grid(row=4,column=1) # self.start_serv_btn=Button(self,text='開始備份',command=self.start_send) self.start_serv_btn.grid(row=5) # self.start_exit_btn=Button(self,text='退出程序',command=self.root.destroy) self.start_exit_btn.grid(row=5,column=1) #定義啟動服務器的方法 def start_send(self): # print(self.remote_ip_var.get(),self.remote_ports_var.get()) # print('start...') host = self.remote_ip_var.get() port = self.remote_ports_var.get() compress=self.compress_var.get() src=self.bak_src_var.get() self.bak_src_var.set('') t = threading.Thread(target=start,args=(host,int(port),src,compress)) t.start() # start(self.remote_ip_var.get(),int(self.remote_ports_var.get()),self.bak_src_var.get())#想服務器發送東西 if __name__=='__main__': root=Tk() root.title('遠程備份客戶機') root.resizable(FALSE, FALSE)#允許用戶更改窗體大小 a = MyFrame(root) a.mainloop()
- socketserver框架的使用
- 編寫網絡服務應用器的框架,划分了一個基本服務器框架,划分了處理請求(服務器類和請求處理器類)
- 服務器構建
- 建立客戶端處理類;初始化服務器類傳入相關參數;啟動服務器
- 基本對象
- BasesSrver(通過繼承定制服務器類)
- 方法介紹:serve_forever啟動服務器,
- handle_request()處理請求:順序:先調用get_request()獲取客戶端請求連接,verify()對客戶端請求連接進行認證,process_request():實現與客戶端進行交互
- finish_request()創建
- 關閉服務器shutdown();shutdown必須在serve_forever()不同線程中調用才能關閉服務器
- 方法介紹:serve_forever啟動服務器,
- TCPServer
- 繼承baseserver的服務器類,可以直接初始化TCP服務器,初始化參數:(host,post)服務器服務地址;handler類:處理客戶端數據
- UDPServer:TCPServer的子類,可以直接初始化 BaseRequestHandle
- setup方法:准備請求處理器
- handle方法:完成請求具體操作(一般只用這個)
- finish方法:清理setup期間的相關資源
- StreamRequestHandler(上面那個類的子類)使用TCP協議
- 定制請求處理器時可以只覆蓋handle()
- 實例屬性request代表和客戶端連接的socket可以用它實現TCP數據的接收
-
View Code#coding=gbk import socketserver import threading #關閉服務器 def sd(): if serv: serv.shutdown()#關閉服務器 #shutdown必須在serve_forever()不同線程中調用才能關閉服務器 class MyHdl(socketserver.StreamRequestHandler):#tcp協議的服務器 def handle(self):#覆蓋handle方法 #和客戶端進行交互 while True: data = self.request.recv(1024)#接收數據 print('收到數據:',data.decode('utf-8'))#解碼 if data==b'bye': break self.request.sendall(data)#將收到的數據傳回給客戶端 print('本次服務結束') threading.Thread(target=sd).start() if __name__=='__main__': HOST='' PORT=3214 #實例化TCPserver serv=socketserver.TCPServer((HOST,PORT),MyHdl)#服務的地址,自定義的類 #啟動服務器 serv.serve_forever() #coding=gbk import socket HOST = '127.0.0.1' PORT = 3214 s=socket.socket()#tcp協議 s.connect((HOST,PORT)) data='你好' while data: #發送數據到服務器端 s.sendall(data.encode('utf_8')) if data=='bye': break #從服務器端接收數據 data = s.recv(1024) print('收到數據;',data.decode('utf-8')) data=input('輸入要發送的信息:') s.close()
- 客戶端發過來的數據也可以rfile屬性來處理,rfile是一個類file對象,有緩沖,可以按行分次讀取,其方法主要有:read(n),readline()
- 發往客戶端的數據通過wfile屬性來處理,wfile不軟沖數據,對客戶端發送的數據需要一次性寫入,寫入時用write(data)
-
View Code#coding=gbk import socketserver class MyHdl(socketserver.StreamRequestHandler): def handle(self): while True: #從客戶端讀取一行數據 data = self.rfile.readline() if not data: break print('收到:',data.decode('utf-8'.strip('\n')))#strip去除末尾的換行符 #把收到數據發回給客戶端 self.wfile.write(data) if __name__=='__main__': HOST = '' PORT = 3214 s = socketserver.TCPServer((HOST,PORT),MyHdl) s.serve_forever() #coding=gbk import socket HOST = '127.0.0.1' PORT = 3214 s=socket.socket()#tcp協議 s.connect((HOST,PORT)) data='你好' while data: #為了讓服務器按行接收 data +='\n' s.sendall(data.encode('utf_8')) data = s.recv(1024) print('收到數據;',data.decode('utf-8').strip('\n'))#strip 去除換行符 data=input('輸入要發送的信息:') s.close()
- DatagramRequestHandeler 使用udp協議
- 略
-
View Code#coding=gbk import socketserver class MyHdl(socketserver.DatagramRequestHandler): def handle(self): #UDP無連接的,從客戶端獲取字符和套接字 data,socket = self.request print('收到:',data.decode('utf-8'.strip('\n')))#strip去除末尾的換行符 #把收到數據發回給客戶端 socket.sendto(data,self.client_address) if __name__=='__main__': HOST = '' PORT = 3214 s = socketserver.UDPServer((HOST,PORT),MyHdl) s.serve_forever() #coding=gbk import socket HOST = '127.0.0.1' PORT = 3214 s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)#UDP協議 data='你好' s.sendto(data.encode('utf-8'),(HOST,PORT)) while data != 'bye': data =b'' while len(data)==0: data,addr=s.recvfrom(1024) print('收到數據;',data.decode('utf-8')) data=input('輸入要發送的信息:') if data == '': data = 'bye' s.sendto(data.encode('utf-8'),(HOST,PORT)) s.close()
- BasesSrver(通過繼承定制服務器類)
- 使用socketserver重新編寫備份服務器端
-
View Code#coding=gbk from tkinter import * from tkinter.ttk import * import socket import threading import os import struct import pickle import threading import socketserver #建立一個默認的備份目錄 BAK_PATH=r'e:\bak' #根據指定長度來接受文件信息 def recv_unit_data(clnt,infos_len): data=b'' #原來文件為空 #如果要接受的文件在0-1024之間就直接接收該文件,如果大於1024需要循環分段接收,每次只是接收1024個字節,剩下的在全部接收即可 if 0<infos_len<=1024:# data+=clnt.recv(infos_len)#接收文件 else:#長度太長 while True: if infos_len >1024: data+=clnt.recv(1024) infos_len-=1024 else: data+=clnt.recv(infos_len) break return data def get_files_info(clnt): fmt_str='Q?'#用於向服務器端傳送文件信息的大小,文件信息的壓縮選項 headsize=struct.calcsize(fmt_str)#計算長度 data=clnt.recv(headsize) infos_len,compress=struct.unpack(fmt_str, data) data =recv_unit_data(clnt, infos_len) return pickle.loads(data),compress#得到文件信息的列表 def mk_math(filepath): #建立文件路徑 paths=filepath.split(os.path.sep)[:-1]#將文件路徑進行分割 p=BAK_PATH for path in paths:#遍歷用戶端傳來的路徑 p=os.path.join(p,path)#將保存的路徑添加到默認的路徑上 if not os.path.exists(p):#如果路徑不存在就建立路徑 os.mkdir(p) def get_compress_size(clnt): fmt_str = 'Q'#長整型 size=struct.calcsize(fmt_str) data = clnt.recv(size) size,=struct.unpack(fmt_str,data)#得到壓縮后文件的大小 return size #接收客戶端傳來文件,並且根據文件信息來進行保存備份 def recv_file(clnt,infos_len,filepath,compress): mk_math(filepath)#遍歷文件 通過路徑 filepath = os.path.join(BAK_PATH,filepath)#服務器上的路徑的文件名 #根據壓縮選項判斷 if compress : infos_len = get_compress_size(clnt)#壓縮后文件的長度 filepath = ''.join(os.path.splitext(filepath)[0],'.tar.gz') f = open(filepath,'wb+')#新建一個文件 #接收文件 try: if 0 < infos_len <=1024: data = clnt.recv(infos_len) f.write(data) else: while True: if infos_len >1024: data=clnt.recv(1024) f.write(data) infos_len-=1024 else: data = clnt.recv(infos_len) f.write(data) break except: print('error') else: return True finally: f.close() #向客戶端發送失敗成功消息 def send_echo(clnt,res): if res: clnt.sendall(b'success') else: clnt.sendall(b'failure') def client_operate(client): #compress 可壓縮選項 files_lst,compress=get_files_info(client)#獲取客戶端要傳送的文件列表(包括文件大小和文件路徑:元組) for size,filepath in files_lst:#遍歷得到文件大小和路徑 res = recv_file(client,size,filepath,compress)#接收所有的文件 返回備份的結果:true或者false send_echo(client,res)#保存成功標志,發送給客戶端 client.close()#關閉客戶端 #建立服務器 class BakHdl(socketserver.StreamRequestHandler): def handle(self): client_operate(self.request) #建立服務器和啟動服務器 def start(host,port): #初始化 server = socketserver.ThreadingTCPServer((host,port),BakHdl) #線程的方法啟動服務器 s = threading.Thread(target=server.serve_forever) s.start() return server class MyFrame(Frame): #初始化構造方法 def __init__(self,root): super().__init__(root) self.root=root self.server = None self.grid()#布局方式網格布局 self.local_ip='127.0.0.1'#服務器端默認的IP地址 self.serv_ports=[10888,20888,30888]#三個服務器的端口 self.init_components()#初始化界面方法 #界面函數的實現 def init_components(self):#初始化用戶界面的方法 proj_name=Label(self,text='遠程備份服務器') proj_name.grid(columnspan=2)#網格式布局,占用兩列 serve_ip_label=Label(self,text='服務地址') serve_ip_label.grid(row=1)#列是默認為0列 #下拉列表,顯示服務器的地址拱用戶選擇 self.serv_ip=Combobox(self,values=self.get_ipaddr()) #設置默認的服務器的ip地址 self.serv_ip.set(self.local_ip) self.serv_ip.grid(row=1,column=1) #服務端口的LABEL serv_port_label=Label(self,text='服務端口') serv_port_label.grid(row=2) #下拉列表,顯示服務器的服務端口拱用戶選擇 self.serv_port=Combobox(self,values=self.serv_ports) #設置默認的服務端口 self.serv_port.set(self.serv_ports[1]) #網格布局 放置指定位置 self.serv_port.grid(row=2,column=1) #啟動按鈕 self.start_serv_btn=Button(self,text='啟動服務',command=self.start_serv) self.start_serv_btn.grid(row=3) #退出服務的按鈕 self.start_exit_btn=Button(self,text='退出服務',command=self.exit)#退出服務關閉圖形界面 self.start_exit_btn.grid(row=3,column=1) def exit(self): if self.server: threading.Thread(target=self.server.shutdown).start()#啟動關閉服務器的線程 self.root.destroy() def get_ipaddr(self): #獲取本機的ip地址 #獲取名字 host_name=socket.gethostname() #獲取信息 info=socket.gethostbyname_ex(host_name) info=info[2] info.append(self.local_ip) #定義啟動服務器的方法 def start_serv(self): if not os.path.exists(BAK_PATH): os.mkdir(BAK_PATH) # print(self.serv_ip.get(),self.serv_port.get()) # start(self.serv_ip.get(),int(self.serv_port.get())) host = self.serv_ip.get()#獲取服務器地址 port = int(self.serv_port.get())#獲取服務端口,轉化為整型 self.server = start(host,port) # serv_th= threading.Thread(target=start,args=(host,port))#建立線程化服務器 # serv_th.start() # #當點擊啟動服務之后,我們關閉這個按鈕不讓服務再次啟動 self.start_serv_btn.state(['disabled',]) if __name__=='__main__': root=Tk() root.title('備份服務器') root.resizable(FALSE, FALSE)#允許用戶更改窗體大小 a=MyFrame(root) a.mainloop()
-
- 用socket實現FTP服務器和FTP客戶端
- FTP協議
- 提供可靠的文件傳輸,屬於TCP/IP協議,位於應用層,采用典型的C/S模式工作,可用匿名或者指定用戶登錄,下層采用有連接可靠的TCP協議
- 工作原理及過程
- FTP客戶端 FTP服務器端
登錄服務器 <----->登錄驗證
傳輸文件操作 <-----> 接收或者發送文件
退出登錄結束 <----->結束FTP服務
文件操作
- FTP客戶端 FTP服務器端
- 工作模式
- 主動模式(PORT):數據連接有服務器端發起,客戶端建立接收服務器
- 被動模式(PASV):和上面相反
- ftp最小實現:
- FTP命令:USER,QUIT,PORT,TYPE,MODE,STRU,RETR,STOP,NOOP
- 命令返回碼:2XX命令成功;4XX客戶端錯誤;5XX服務錯誤
- 傳輸方式:流模式文件傳輸,文件傳輸
- 功能分析
- 控制模塊
- 接收客戶端命令,操作后返回結果;用戶可以用空用戶名或者匿名用戶名登錄;用port/pasv是服務器工作於不同模式
- 數據傳輸模塊:與客戶端進行文件傳輸及其他數據交換
- 控制模塊
- FTP協議
- FTP最終代碼
-
View Code#coding=gbk import socket import socketserver import threading import time import os #requesthandler類 class FTPHdl(socketserver.StreamRequestHandler): def __init__(self,request=None,client_address=None,server=None): self.coms_keys = ('QUIT','USER','NOOP','TYPE','PASV','PORT','RETP','STOR') #建立命令所對應的方法 self.coms={} # self.init_coms() self.server #服務器命令模塊的端口 self.cmd_port=21 #數據模塊端口 self.data_port=20 #保存ip地址和端口號 self.pasv_data_ip=None self.pasv_data_port=None self.args=None self.loged=False #模式 self.pasv_mode=None super().__init__(request, client_address, server) #字典方法 命令 def init_coms(self): for k in self.coms_keys: self.coms[k]=getattr(self,'exe_' + k.lower())#獲取當前類的方法 exe為前綴,lower為命令的小寫 #用於對客戶單進行處理 def handle(self): while True: #接收用戶端命令,讀取一行 cmds = self.rfile.readline() if not cmds: continue cmds = cmds.decode('utf-8') #建立命令動詞 命令行的分析 cmd = self.deal_args(cmds) if cmd in self.coms_keys:#命令動詞是否在ftp所有的命令中 self.coms.get(cmd)() else: #返回錯誤代碼 self.send(500,'Invaild command.') #如果命令為退出 if cmd == 'QUIT': break #分析命令信息 def deal_args(self,cmds): #如果空格在命令行中 必須分開 前面是命令動詞 后面是命令參數 if ' ' in cmds: cmd,args=cmds.split(' ') args = args.strip('\n').strip()#對參數進行處理 else: cmd=cmds.strip('\n')#刪除換行符 args='' if args: self.args=args return cmd.upper()#返回命令動詞 大寫 def exe_quit(self): self.send(221,'bye') def exe_user(self): user=self.args if user in ('','anonymous'): user = 'annoymous' self.loged=True self.send(230,'identified!') else: self.send(530,'Only use annoymous') def exe_pasv(self): if not self.loged: self.send(332,'Please login.') return if self.pasv_mode: info = 'entering passive mode (%s)' % self.make_pasv_info() self.send(227,info) return try: self.enter_pasv() info = 'entering passive mode (%s)' %self.make_pasv_info() self.pasv_mode=True self.send(227,info) except Exception as e: print(e) self.send(500,'failure change to passive mode.') def enter_pasv(self): if self.server.data_server is None: self.pasv_data_ip,self.pasv_data_port=self.server.create_data_server() def exe_port(self): self.send(500,'Do not offer port mode.') def exe_noop(self): self.send(200,'ok') def exe_type(self): self.send(200,'ok') def exe_retr(self): if not os.path.exists(self.args): self.send(550,'File is not exists.') return client_addr=self.request.getpeername()[0] self.add_opr_file(client_addr,('RETR',self.args)) self.send(150,'ok.') def exe_stor(self): client_addr=self.request.getpeername()[0] self.add_opr_file(client_addr,('STOP',self.args)) def add_opr_file(self,client_addr,item): if client_addr in DataHdl.client_opr: DataHdl.client_opr[client_addr].append(item) else: DataHdl.client_opr[client_addr]=[item,] def sebd(self,code,info): infos='%d %s\n' % (code,info) self.request.sendall(infos.encode('utf_8')) class MyThrTCPServ(socketserver.ThreadingTCPServer): def __init__(self,addr,Hdl): self.data_server=None super().__init__(addr,Hdl) def shutdown(self): if self.data_server: threading.Thread(target=self.data_server.shutdown).start() super().shutdown() def create_data_server(self): self.data_server=socketserver.ThreadingTCPServer(('127.0.0.1',0),DataHdl) pasv_data_ip,pasv_data_port=self.data_server.server_address threading.Thread(target=self.data_server.serve_forever).start() return pasv_data_ip,pasv_data_port class DataHdl(socketserver.StreamRequestHandler): client_opr={} def handle(self): peerip=self.request.getpeername()[0] opr=self.get_opr_args(peerip) if opr: if opr[0]=='RETR': self.retr_file(opr[1]) elif opr[0]=='STOR': self.stor_file(opr[1]) self.request.close() def get_opr_args(self,peerip): if peerip in self.client_opr: opr= self.client_opr[peerip].pop(0) if not self.client_opr[peerip]: self.client_opr.pop(peerip) return opr def retr_file(self,filepath): f = open(filepath,'rb') while True: data = f.read(1024) if data: self.request.sendall(data) else: break f.close() def stor_file(self,filepath): f=open(os.path.join('.','baket',filepath),'wb') while True: data =self.request.recv(1024) if data: f.write(data) else: break f.close() if __name__=='__main__': server = MyThrTCPServ(('127.0.0.1',21),FTPHdl) threading.Thread(target=server.serve_forever).start() print('FTP start...') time.sleep(30) server.shutdown() #coding=gbk import os import socket import threading import socketserver def get_file(host,port,filepath): s=socket.socket() s.connect((host,port)) filepath=os.path.join('.','bakt',filepath) f=open(filepath,'wb') data = True while data: data=s.recv(1024) if data: f.write(data) s.close() f.close() def put_file(host,port,filepath): s=socket.socket() s.connect((host,port)) f=open(filepath,'rb') while true: data=f.read(1024) if data: s.sendall(data) else: break s.close() f.close() class FtpClient: def __init__(self,host='localhost',port=21): self.host=host self.port=port self.cmds=('QUIT','USER','NOOP','TYPE','PASV','PORT','RETP','STOR') self.linesep='\n' self.data_port=None self.loged=False self.sock=None self.pasv_mode=None self.pasv_host=None self.pasv_port=None def cmd_connect(self): self.sock=socket.socket() self.sock.connect((self.host,self.port)) self.data_port=self.sock.getsockname()[0] def start(self): print('支持的命令:',self.cmds) self.cmd_connect() self.login() while True: cmd=input('請輸入FTP命令: ') if not cmd: print('FTP命令不能為空。') continue cmd,args=self.split_args(cmd) if not self.send_cmd(cmd,args): continue res = self.readline(self.sock) print(res) if cmd.stratswith('PASV') and res.startswith('227'): self.pasv_mode=True servinfo =res[res.index('(')+1:res.index(')')] self.pasv_host='.'.join(servinfo.split(',')[:4]) servinfo =servinfo.split(',')[-2:] self.pasv_port=256*int(servinfo[0])+int(servinfo[1]) if cmd.startswith('RETR'): if self.pasv_mode: threading.Thread(target=get_file,args=(self.pasv_host,self.pasv_port,args)).start() if cmd.startswith('STOR'): if self.pasv_mode: threading.Thread(target=put_file,args=(self.pasv_host,self.pasv_port,args)).start() if cmd.startswith('QUIT'): break self.sock.close() self.sock=None def login(self): if self.sock: self.send_cmd('USER') res=self.readline(self.sock) if res.startswith('230'): print('登錄成功!') self.loged def readline(self,sock): data = '' while not data.endswith(self.linesep): d=sock.recv(1) data +=d.decode('utf-8') return data def split_args(self,cmds): if ' ' in cmds: cmdlsts = cmds.split(' ') cmd = cmdlsts[0] args=' '.join(cmdlsts[1:]) else: cmd = cmds args = '' return cmd.upper(),args def send_cmd(self,cmd,args=''): if self.sock: if args: cmd = ' '.join((cmd,args)) if cmd.startswith('RETR') or cmd.startswith('STOR'): if self.pasv_mode is None: print('Please appoint port or stor mode.') return False if not args: return False if cmd.startswith('STOR'): if args: if not os.path.exists(args): print('File is not exists') return False cmd+=self.linesep self.sock.sendall(cmd.encode('utf-8')) return True if __name__=='__main__': fc=FtpClient() fc.start()
-
