Python--socket和threading编程
网络编程基础(一)
- 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服务器端方法
- 基本步骤
- 创建套接字并绑定地址,开始监听连接,接收连接并收发数据,关闭套接字
#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服务器端方法
- 基本步骤
- 创建套接字并绑定地址,开始监听连接,收发数据,关闭套接字
#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实现客户端
- 基本步骤
- 创建套接字,用套接字连接服务器;收发数据;关闭套接字
#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实现客户端
- 基本步骤
- 创建套接字,收发数据,关闭套接字
#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的地址和端口号
- 指定保存备份文件的目录
- 关闭服务器
- 以多线程的方式同时为多个客户端提供备份服务
#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()
- 最简备份服务器端建立
- 同一时间只能连接一个客户并且为其备份
- 备份客户端的一个目录及其子目录的所有文件
- 与客户端交互
- 客户端发送即将要发送文件信息的大小
- 服务器端接收客户端通知的文件信息的大小
- 客户端发送文件信息(包括文件大小。文件名)
- 服务器端依照文件信息的大小接收文件信息
- 客户端逐个发送文件数据,每发送完一个文件数据,接收该文件的备份结果
- 服务器端接收文件数据并保存备份至文件系统,每接收完一个文件就返回备份结果
#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地址和端口号
- 输入备份目录,备份其中的所有文件
- 显示服务器端发来的备份结果
- 选择备份时启用压缩备份
- 功能
- 客户端与服务器最终版
#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()- 通过多线程实现备份服务器端
- 单线程服务端问题
- 启动服务,界面停止响应
- 一个客户端正在备份,其他客户端不能连接
- 建立多线程服务器
- 解决点击启动服务,页面停止响应的问题,实现多个客户端进行交互
- 退出服务器:将服务线程配置为后台线程(可能使文件丢失);应用线程同步的手段退出服务(这个方法好)
- 可压缩备份服务
- 客户端发送已压缩文件
- 与客户端交互基本流程
- 单线程服务端问题
- 通过多线程实现备份客户端
- 最终版
#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的子类,可以直接初始化
- setup方法:准备请求处理器
- handle方法:完成请求具体操作(一般只用这个)
- finish方法:清理setup期间的相关资源
- StreamRequestHandler(上面那个类的子类)使用TCP协议
- 定制请求处理器时可以只覆盖handle()
- 实例属性request代表和客户端连接的socket可以用它实现TCP数据的接收
#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)
#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协议
- 略
#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()
BaseRequestHandle
- BasesSrver(通过继承定制服务器类)
- 使用socketserver重新编写备份服务器端
#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最终代码
-
#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(''):
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(''):
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()
-
Python--socket和threading编程的更多相关文章
- Python Socket,How to Create Socket Server? - 网络编程实例
文章出自:Python socket – network programming tutorial by Silver Moon 原创译文,如有版权问题请联系删除. Network programin ...
- python socket 编程简单入门
想讲讲套接字的概念 套接字,即英文socket的中文意译,起源于20世纪70年代,是加利福利亚大学的伯克利版本UNIX(称为BSD UNIX)的一部分.目的是实现主机上运行的一个程序与另一个运行的程序 ...
- [Python_7] Python Socket 编程
0. 说明 Python Socket 编程 1. TCP 协议 [TCP Server] 通过 netstat -ano 查看端口是否开启 # -*-coding:utf-8-*- "&q ...
- Python Socket编程基础篇
Socket网络编程 socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求. ...
- Python Socket套接字编程
Python 的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为ABC语言的一种继承.Py ...
- 第九章:Python高级编程-Python socket编程
第九章:Python高级编程-Python socket编程 Python3高级核心技术97讲 笔记 9.1 弄懂HTTP.Socket.TCP这几个概念 Socket为我们封装好了协议 9.2 cl ...
- Python Socket 编程——聊天室示例程序
上一篇 我们学习了简单的 Python TCP Socket 编程,通过分别写服务端和客户端的代码了解基本的 Python Socket 编程模型.本文再通过一个例子来加强一下对 Socket 编程的 ...
- Python Socket 网络编程
Socket 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的,例如我们每天浏览网页.QQ ...
- Python:使用threading模块实现多线程编程
转:http://blog.csdn.net/bravezhe/article/details/8585437 Python:使用threading模块实现多线程编程一[综述] Python这门解释性 ...
- Python Socket,How to Create Socket Cilent? - 网络编程实例
文章出自:Python socket – network programming tutorial by Silver Moon 原创译文,如有版权问题请联系删除. Network programin ...
随机推荐
- CodeForces - 896D :Nephren Runs a Cinema(卡特兰数&组合数学---比较综合的一道题)
Lakhesh loves to make movies, so Nephren helps her run a cinema. We may call it No. 68 Cinema. Howev ...
- 1151 LCA in a Binary Tree(30 分)
The lowest common ancestor (LCA) of two nodes U and V in a tree is the deepest node that has both U ...
- java 实现树形结构
package tree; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Font; import java ...
- 使用window.print()后,未关闭打印页面,原网页不能操作
使用window.print()后,未关闭打印页面,原网页不能操作,此时可以试着用window.location.reload()重新加载页面解决问题.
- UDP打洞原理及代码
来源:http://www.fenbi360.net/Content.aspx?id=1021&t=jc UDP"打洞"原理 1. NAT分类 根据Stun协议 ...
- Vue 内联样式的数据绑定
Vue 内联样式的数据绑定 之前学的是数据绑定 class,现在可以将数据绑定到 style 中. <div id="app"> <div v-bind:styl ...
- WebForm中创建树节点TreeNode
Tree: namespace ECTECH.NorthSJ.Web.SysData { public partial class testTree : BasePage { ; protected ...
- 机器学习:多项式回归(scikit-learn中的多项式回归和 Pipeline)
一.scikit-learn 中的多项式回归 1)实例过程 模拟数据 import numpy as np import matplotlib.pyplot as plt x = np.random. ...
- Cassandra 学习三 数据模型
Cassandra如何存储数据的概述. 集群(Cluster) ·Cassandra数据库分布在几个一起操作的机器上.最外层容器被称为集群.对于故障处理,每个节点包含一个副本,如果发生故障,副本 ...
- MySQL组合索引最左匹配原则
几个重要的概念 1.对于mysql来说,一条sql中,一个表无论其蕴含的索引有多少,但是有且只用一条. 2.对于多列索引来说(a,b,c)其相当于3个索引(a),(a,b),(a,b,c)3个索引,又 ...