socket 编程基础
1.客户端服务器架构
2.osi 七层
3.socket 就是封装底层的 TCP 和UDP、IP 等协议的库,
我们直接调用 socket 为我们封装的方法就可以利用 TCP 和 UDP 进行通信
4.套接字,也就是 socket 的中文名,ip address + port,
TCP用主机的IP地址加上主机上的端口号作为TCP连接的端点,这种端点就叫做套接字(socket)或插口
5.工作流程,看代码吧,TCP/IP 协议通信
socket TCP 的 recv 自己这端的缓冲区为空时,阻塞
而在socket UDP 的recvfrom 自己这端的缓冲区为空时,就会收一个空
TCP socket 连接例子,服务器端
# 服务器端
import socket
socket1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) // 基于网络,tcp/ip通信
socket1.bind(('127.0.0.1',8088)) #绑定到固定端口,等待客户端来访问
socket1.listen(5) #最多有5个等待连接
conn,addr = socket1.accept() #没有连接时,会在这句一直等待
print('conn = ',conn)
print('addr = ',addr) #IP地址和端口 ('127.0.0.1', 1941)
data = conn.recv(1024)
print('data = ', data.decode('utf-8'))
conn.send('你好啊'.encode('utf-8')) # 发送只能是二进制编码
conn.close()
socket1.close()
输出结果:
conn =
addr = ('127.0.0.1', 1941)
data = 天天向上
在客户端只需要负责连接
# 客户端
import socket
socket1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket1.connect(('127.0.0.1', 8088)) # 发起 TCP 三次握手连接
socket1.send('天天向上'.encode('utf-8'))
data = socket1.recv(1024)
print(data.decode('utf-8'))
socket1.close()
输出结果:
你好啊
升级版服务器端,server 可以捕捉异常并继续运行。
# server
import socket
ip_port = ('127.0.0.1', 8088)
backlog = 5
buffersize = 1024
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 减少地址复用的时间
socket_server.bind(ip_port)
socket_server.listen(backlog)
while True:
print('wait to connect')
conn,addr = socket_server.accept()
print('conn = ', conn)
print('addr = ', addr)
try:
while True:
data = conn.recv(buffersize)
print('>>>', data.decode('utf-8'))
if data.decode('utf-8') == 'bye':
print('prepare to close connect')
break
send = input('>>>').strip()
conn.send(send.encode('utf-8'))
if not send or send == 'bye':
print('prepare to close connect')
conn.send('bye'.encode('utf-8'))
break
except Exception as e:
print(e)
conn.close()
print('close')
socket_server.close()
输出结果:
wait to connect
conn =
addr = ('127.0.0.1', 6802)
>>>
>>>d
[WinError 10053] 你的主机中的软件中止了一个已建立的连接。
close
wait to connect
conn =
addr = ('127.0.0.1', 6803)
>>> 413
升级版客户端
# client
import socket
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_client.connect(ip_port)
while(True):
send = input('>>>').strip()
socket_client.send(send.encode('utf-8'))
if not send or send == 'bye':
print('prepare to close connect')
socket_client.send('bye'.encode('utf-8'))
break
data = socket_client.recv(buffersize)
print('>>>', data.decode('utf-8'))
if data.decode('utf-8') == 'bye':
print('prepare to close connect')
break
socket_client.close()
print('close')
输出结果:
>>>413
Traceback (most recent call last):
File "C:/Users/libai/PycharmProjects/begin/test.py", line 14, in
data = socket_client.recv(buffersize)
KeyboardInterrupt #手动终止客户端,服务端自动断开连接
基于UDP的套接字
#udp_server
import socket
import time
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
udp_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
udp_server.bind(ip_port)
print('udp is working')
while True:
data,addr = udp_server.recvfrom(buffersize)
print('addr = ', addr)
print("data = ",data.decode('utf-8'))
udp_server.sendto(time.strftime('%Y-%m-%d %X').encode('utf-8'), addr)
输出结果:
udp is working
addr = ('127.0.0.1', 50122)
data = 123
addr = ('127.0.0.1', 50122)
data = 88
# udp_client
import socket
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
udp_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
data = input('>>>').strip()
udp_client.sendto(data.encode('utf-8'),ip_port)
ntp_time, addr = udp_client.recvfrom(buffersize)
print('ntp返回的时间:',ntp_time.decode('utf-8'))
print(addr)
输出结果:
>>>123
ntp返回的时间: 2018-04-21 11:04:03
('127.0.0.1', 8088)
>>>88
ntp返回的时间: 2018-04-21 11:04:08
('127.0.0.1', 8088)
>>>
远程执行系统命令,调用 subprocess 模块,十分有意思
# server_tcp
import socket
import subprocess
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
backlog = 5
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
socket_server.bind(ip_port)
socket_server.listen(backlog)
while True:
print('watiing for connect')
conn, addr = socket_server.accept()
print('conn = ', conn)
print('addr = ', addr)
while True:
data = conn.recv(buffersize)
print('输入>>>', data.decode('utf-8'))
if not data or data.decode('utf-8')=='bye':
break
response = subprocess.Popen(data.decode('utf-8'),shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
error = response.stderr.read()
if error:
cmd_res = error
else:
cmd_res = response.stdout.read()
conn.send(cmd_res)
conn.close()
输出结果:
watiing for connect
conn =
addr = ('127.0.0.1', 27419)
输入>>> dir
输入>>> 156
输入>>> bye
watiing for connect
# client_tcp
import socket
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_client.connect(ip_port)
while True:
data = input('>>>')
socket_client.send(data.encode('utf-8'))
if not data: continue
if data == 'bye':
break
response = socket_client.recv(buffersize)
print(response.decode('gbk'))
print('connect close')
输出结果:
2018/04/21 15:25 .
2018/04/21 15:25 ..
2018/04/21 15:28 .idea
2018/04/20 17:23 a
2018/04/18 16:03 25 a.txt
2018/04/21 15:25 1,028 begin
2018/04/21 15:00 19 process.py
2018/04/21 15:23 392 test.py
4 个文件 1,464 字节
4 个目录 68,050,141,184 可用字节
>>>156
'156' 不是内部或外部命令,也不是可运行的程序
或批处理文件。
>>>bye
connect close
TCP 的粘包现象,UDP不会出现粘包现象
socket TCP 会出现粘包现象,也就是recv命令收取的buffersize没有全部收完,就会等到下次再继续收
例如:先运行 ipconfig 命令,没有收完缓冲区使得打印信息不全,再运行别的命令,
下次的命令会打印运行ipconfig命令剩余的信息。
解决粘包 第一个版本
# server
import socket
import subprocess
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
backlog = 5
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
socket_server.bind(ip_port)
socket_server.listen(backlog)
while True:
print('watiing for connect')
conn, addr = socket_server.accept()
print('conn = ', conn)
print('addr = ', addr)
while True:
data = conn.recv(buffersize)
print('输入>>>', data.decode('utf-8'))
if not data or data.decode('utf-8')=='bye':
break
response = subprocess.Popen(data.decode('utf-8'),shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
error = response.stderr.read()
if error:
cmd_res = error
else:
cmd_res = response.stdout.read()
if not cmd_res:
cmd_res = 'execution succeed'.encode('gbk')
length = len(cmd_res)
conn.send(str(length).encode('gbk'))
ready = conn.recv(buffersize)
if ready.decode('gbk') == 'ready':
conn.send(cmd_res)
conn.close()
#client
import socket
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_client.connect(ip_port)
try:
while True:
data = input('>>>')
socket_client.send(data.encode('utf-8'))
if not data: continue
if data == 'bye':
break
length = socket_client.recv(buffersize)
length = int(length.decode('gbk'))
socket_client.send('ready'.encode("gbk"))
response = b''
while len(response) < length:
response += socket_client.recv(buffersize)
print(response.decode('gbk'))
except Exception as e:
print(e)
print('connect close')
socket_client.close()
解决粘包 第二个版本
# server
import socket
import subprocess
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
backlog = 5
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
socket_server.bind(ip_port)
socket_server.listen(backlog)
while True:
print('watiing for connect')
conn, addr = socket_server.accept()
print('conn = ', conn)
print('addr = ', addr)
while True:
data = conn.recv(buffersize)
print('输入>>>', data.decode('utf-8'))
if not data or data.decode('utf-8')=='bye':
break
response = subprocess.Popen(data.decode('utf-8'),shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
error = response.stderr.read()
if error:
#response.stderr.read() 返回的值为系统编码的bytes类型,需要解码
cmd_res = error.decode('gbk')
else:
cmd_res = response.stdout.read().decode('gbk')
if not cmd_res:
cmd_res = 'execution succeed'
length = len(cmd_res)
send_data = str(length)+'!o!'+cmd_res
# 字符长度,'!o!'符号作为分隔符,再加上返回的数据一起发送
conn.send(send_data.encode('gbk'))
conn.close()
输出结果:
watiing for connect
conn =
addr = ('127.0.0.1', 28581)
输入>>> ipconfig
输入>>> bye
watiing for connect
import socket
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_client.connect(ip_port)
try:
while True:
data = input('>>>')
socket_client.send(data.encode('utf-8'))
if not data: continue
if data == 'bye':
break
send_data = socket_client.recv(buffersize)
# 不知道发送的字符长度,从收取的第一个data中截取出来,没读完再继续读取
send_data = send_data.decode('gbk')
key_line = send_data.find('!o!')# 找到分隔符,返回的是第一个!的位置
length = int(send_data[0:key_line]) # 切割出返回字符长度
response = send_data[key_line+3:].encode('gbk') # 切割出返回字符,再次编码以便和未发送完的字符串拼接
while len(response) < length:
response += socket_client.recv(buffersize)
print(response.decode('gbk'))
print('返回%s个字节' %length)
except Exception as e:
print(e)
print('connect close')
socket_client.close()
输出结果:#只复制了一部分
以太网适配器 VMware Network Adapter VMnet8:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::8dae:48ae:e19c:207d%9
IPv4 地址 . . . . . . . . . . . . : 192.168.1.1
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . :
无线局域网适配器 本地连接* 12:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::7ca0:36c4:83dd:d3ac%22
IPv4 地址 . . . . . . . . . . . . : 192.168.23.1
子网掩码 . . . . . . . . . . . . : 255.255.255.0
IPv4 地址 . . . . . . . . . . . . : 192.168.137.1
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . :
返回1631个字节
>>>bye
解决粘包 第三版
#server
import socket
import subprocess
import struct
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
backlog = 5
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
socket_server.bind(ip_port)
socket_server.listen(backlog)
while True:
print('waiting for connect')
conn, addr = socket_server.accept()
print('conn = ', conn)
print('addr = ', addr)
while True:
data = conn.recv(buffersize)
print('输入>>>', data.decode('utf-8'))
if not data or data.decode('utf-8')=='bye':
break
response = subprocess.Popen(data.decode('utf-8'),shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
error = response.stderr.read()
if error:
#response.stderr.read() 返回的值为系统编码的bytes类型,需要解码
cmd_res = error.decode('gbk')
else:
cmd_res = response.stdout.read().decode('gbk')
if not cmd_res:
cmd_res = 'execution succeed'
length = len(cmd_res)
length_pack = struct.pack('i', length) #struct.pack 编码出来的length_pack 为四个字节
conn.send(length_pack)
conn.send(cmd_res.encode('gbk'))
conn.close()
#client
import socket
import struct
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_client.connect(ip_port)
try:
while True:
data = input('>>>')
socket_client.send(data.encode('utf-8'))
if not data: continue
if data == 'bye':
break
length_pack = socket_client.recv(4) #取出四字节的编码字符数据长度
length = int(struct.unpack('i',length_pack)[0])
data = b''
while len(data) < length:
data += socket_client.recv(buffersize)
print(data.decode('gbk'))
print('返回%s个字节' %length)
except Exception as e:
print(e)
print('connect close')
socket_client.close()
下面是一个ftp服务器和客户端
有着上传下载功能,添加了一些常用的命令,还有断点续传的功能,但存在bug,当练练手了,这个东西搞了好几天
输入启动命令:python C:\PycharmProjects\begin\ftp_client\ftp_client.py -P 8889 -S 127.0.0.1
#ftp_client
# python C:\PycharmProjects\begin\ftp_client\ftp_client.py -P 8889 -S 127.0.0.1
import optparse # 解析获得的命令行参数
import socket
import json #传输
import os
import sys
import hashlib #对上传下载文件的 md5 校验
# 用数字代表一些特定字符,可以减少发送的字符,不过我只用到了几个
STATUS_CODE = {
250 : "Invalid cmd format, e.g: {'action':'get','filename':'test.py','size':344}",
251 : "Invalid cmd ",
252 : "Invalid auth data",
253 : "Wrong username or password",
254 : "Passed authentication",
255 : "Filename doesn't provided",
256 : "File doesn't exist on server",
257 : "ready to send file",
258 : "md5 verification",
800 : "the file exist,but not enough ,is continue? ",
801 : "the file exist !",
802 : " ready to receive datas",
900 : "md5 valdate success"
}
#输入启动命令:python C:\PycharmProjects\begin\ftp_client\ftp_client.py -P 8889 -S 127.0.0.1
#定义一个类,实例化在最下面
class ClientHandler:
def __init__(self): # 解析参数
self.op = optparse.OptionParser()
self.op.add_option('-S', '--server', dest='server')
self.op.add_option('-P', '--port', dest='port')
self.op.add_option('-U', '--username', dest='username')
self.op.add_option('-p', '--password', dest='password')
self.options, self.args = self.op.parse_args()
# print(self.options)
# print(self.args)
self.verify_args() # 检测输入是否合法
self.make_connect() # 建立连接
self.mainPath = os.path.dirname(os.path.abspath(__file__)) #获得文件的执行路径,上传下载时需要用到
# 检测输入的是否合法,我这里只检测了端口值
def verify_args(self):
if int(self.options.port) >0 and int(self.options.port)<65535:
return True
else:
exit('port is error')
# 建立连接,传入元组形式的 ip 地址和端口值,(要先启动服务器)
def make_connect(self):
self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.s.connect((self.options.server, int(self.options.port)))
print('connect successful')
def get_response(self):
data= self.s.recv(1024)
data = json.loads(data.decode('utf-8'))
return data
# 通信开始,用的也是反射
def interactive(self):
if self.authenticate():
print('pass')
while True:
cmd_info = input('[%s]' %self.current_dir).strip()
if len(cmd_info) == 0:continue # 如果输入为空,重新输入一遍,TCP协议不能发送空
cmd_list = cmd_info.split()
if hasattr(self, cmd_list[0]):
func = getattr(self, cmd_list[0])
func(*cmd_list)
else:
print('please input again')
def ls(self, *cmd_list):
data = {
'action' : 'ls'
}
self.s.sendall(json.dumps(data).encode('utf-8')) #把 data 转成json字符串发送给服务器
msg = self.s.recv(1024).decode('utf-8')
if msg != 'empty': # 用 empty 代替发送为空
print(msg)
def cd(self, *cmd_list):
cmd_list = list(cmd_list)
if len(cmd_list) == 1: #如果命令中只有一个 cd 时,返回最上级目录,也就是 home 下的家目录
cmd_list.append('top')
if len(cmd_list) != 2: # cmd_list 的长度不等于2,就是输入出错了
print('please input again')
return
data = {
'action' : 'cd',
'dirname' : cmd_list[1]
}
self.s.sendall(json.dumps(data).encode('utf-8'))
msg = self.s.recv(1024).decode('utf-8')
if msg != 'no':
self.current_dir = msg[msg.find('libai', 10):] #
#libai 是我定义的账号名称,在home 下会有一个libai的文件作为家目录
# print(self.current_dir)
else:
print('no this dir')
def put(self, *cmd_list):
print('put')
# put 12.jpg image 命令形式
s = hashlib.md5() #实例一个 md5,检查文件是否传输无误
action,local_path,target_path = cmd_list
local_path = os.path.join(self.mainPath, local_path)
#把 self.mainPath 和 image 合成一个完整目录
file_name = os.path.basename(local_path) #这句好像是多余的,额
file_size = os.stat(local_path).st_size
data = {
'action' : 'put',
'file_name' : file_name,
'file_size' : file_size,
'target_path' : target_path
}
self.s.send(json.dumps(data).encode('utf-8'))
###############################################
has_send = 0
is_exit = self.s.recv(1024).decode('utf-8')
if is_exit == '800':
#文件不完整
choice = input('the file is exist, but no enough, is continue? [Y/N ]').strip()
if choice.upper() == 'Y':#是否续传
self.s.sendall('Y'.encode('utf-8'))
has_send = int(self.s.recv(1024).decode('utf-8')) #获得已经传输的文件大小
else:
self.s.sendall('N'.encode('utf-8'))
elif is_exit == '801':
#文件存在,就直接返回了
print('the file is exist')
return
#如果文件已经传输了一部分,需要续传,重新计算md5 的值,
print('send filename: %s, filesize: %s' %(file_name, file_size))
f = open(local_path, 'r+b')
has_send_b = has_send
if has_send > 0:
while True:
if has_send - 1024 > 0:
s.update(f.read(1024)) # 更新md5
has_send = has_send - 1024
else:
s.update(f.read(has_send))
break
f.seek(has_send_b) #这句好像也是多余的额,
print('has_send_b = ', has_send_b)
while has_send_b < file_size:
data = f.read(1024)
s.update(data) # 更新md5
self.s.sendall(data)
has_send_b += len(data)
self.show_progress(has_send_b, file_size) #这是一个进度条函数
f.close()
self.s.sendall(str(s.hexdigest()).encode('utf-8')) #收发client和server的md5
server_md5 = self.s.recv(1024).decode('utf-8')
if server_md5 == s.hexdigest():
print('\nMd5 checksum succeeded, Uploaded successfully')
else:
print('\nMd5 check failed ,upload failed')
#time.sleep(20)
# 下载和上传也是差不多了,我不写了
def downloads(self, *cmd_list):
# downloads 12.jpg
s = hashlib.md5()
if len(cmd_list) != 2:
print('please input again')
return
data = {
'action' : 'downloads',
'file_name' : cmd_list[1],
}
self.s.sendall(json.dumps(data).encode('utf-8'))
msg = self.s.recv(1024).decode('utf-8')
print(msg)
path = os.path.dirname(os.path.abspath(__file__))
path = os.path.join(path, cmd_list[1]).replace('\\', '/')
has_send = 0
if msg == 'exist this file':
if os.path.exists(path):
print('you want to continue transmission?')
choice = input('[Y/N]').upper()
if choice == 'Y':
has_send = os.stat(path).st_size
f = open(path, 'r+b') #keep going
else:
f = open(path, 'r+b') #downloading
else:
f = open(path, 'wb')
else:
print(msg)#bu cun zai wen jian
return
file_size = int(self.s.recv(1024).decode('utf-8'))
self.s.sendall(str(has_send).encode('utf-8'))
print('downloading')
has_send_b = has_send
f.seek(0)
if has_send > 0:
while True:
if has_send - 1024 > 0:
mess = f.read(1024)
s.update(mess)
has_send = has_send - 1024
else:
s.update(f.read(has_send))
break
f.seek(has_send_b)
print('has_send = ', has_send)
while has_send_b < file_size:
message = self.s.recv(1024)
f.write(message)
s.update(message)
has_send_b += len(message)
self.show_progress(has_send_b, file_size)
f.close()
self.s.sendall(str(s.hexdigest()).encode('utf-8'))
server_md5 = self.s.recv(1024).decode('utf-8')
if server_md5 == s.hexdigest():
print('\nMd5 checksum succeeded, Uploaded successfully')
else:
print('\nMd5 check failed ,upload failed')
# print('downloads data = ', data)
# self.s.sendall(json.dumps(data).encode('utf-8'))
# msg = self.s.recv(1024)
# print(msg)
#创建文件夹
def mkdir(self, *cmd_list):
if len(cmd_list) != 2:
print('please input again')
return
data = {
'action' : 'mkdir',
'dirname' : cmd_list[1]
}
self.s.sendall(json.dumps(data).encode('utf-8'))
msg = self.s.recv(1024).decode('utf-8')
print(msg)
#删除文件夹或文件
def rm(self, *cmd_list):
if len(cmd_list) != 2:
print('please input again')
return
data = {
'action' : 'rm',
'dirname' : cmd_list[1]
}
self.s.sendall(json.dumps(data).encode('utf-8'))
msg = self.s.recv(1024).decode('utf-8')
print(msg)
# 这个是进度条函数
def show_progress(self, has_send, file_size):
procentage = int(float(has_send)/file_size*100)
sys.stdout.write('%s%% %s\r' %(procentage, '*'*procentage))
#这个是检测你是否输入了用户名和密码,如果没有,会提示你输入的
def authenticate(self):
if self.options.username == None or self.options.password == None:
username = input('username : ')
password = input('password : ')
return self.get_auth_result(username, password)
return self.get_auth_result(self,self.options.username,self.options.password)
#校验你的用户名和密码对不对
def get_auth_result(self, username, password):
data = {
'action' : 'auth',
'username' : username,
'password' : password
}
self.s.send(json.dumps(data).encode('utf-8'))
response = self.get_response()
print(response)
print('status_code : ', response['status_code'])
if response['status_code'] == 254:
self.username = username
self.current_dir = username
print(STATUS_CODE[254])
return True
else:
print(STATUS_CODE[response['status_code']])
ch = ClientHandler() # 实例化的同时,连接上服务器
ch.interactive() # 通信开始
ftp_server 我分为几个模块来写,这是一个启动文件,整个 ftp_server 的入口 ,启动命令: C:/PycharmProjects/begin/ftp_server/bin/ftp_server.py srart
,当然每个人的文件存放路径都不相同了
# ftp_server 启动
import sys,os
base_path = os.path.dirname(os.path.dirname(__file__))# 获得本文件的上上级目录,也就是ftp_server目录
sys.path.append(base_path) # 将 base_path 添加到临时的环境变量
print(base_path)
from core import main # 在环境变量中就可以找到 core 文件夹了
main.test() # 用文件名加类名运行
setting 配置文件
#配置文件
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
IP = '127.0.0.1'
PORT = 8889
ACCOUNT_PATH = os.path.join(BASE_DIR, 'conf', 'accounts.cfg')
accounts.cfg 账号密码文件
[DEFAULT]
[libai]
password = 123456
quotation = 100
[root]
password = root
quotation = 100
main.py 文件
import optparse #解析出start
import socketserver
# 在启动文件中已经加入了环境变量,不需要再添加了
from conf import setting
from core import server
# 启动 ftp_server 的命令:python C:\PycharmProjects\begin\ftp_server\bin\ftp_server.py start
# 运行 test 会首先运行 test 的构造方法 __init__,解析命令行参数,
# 用反射,可以十分方便的增添函数,
class test:
def __init__(self):
self.op = optparse.OptionParser()
options, args = self.op.parse_args()
print(options)
print(args)
if hasattr(self, args[0]): # 传入的参数是 start ,用 hasattr 检测类是否有 start 方法,有返回True
func = getattr(self,args[0]) # 存在 start 方法,用 getattr 获取,加括号运行
func()
def start(self):
print('waiting for connect') # 连接
# 用 socketserver 内置的类实例化 s ,并传入自定义的 server 文件中的 ServerHandler 类
s = socketserver.ThreadingTCPServer((setting.IP, setting.PORT), server.ServerHandler)
# 启动 实例化的 s 对象
s.serve_forever()
最核心的文件,server.py
import socketserver
import json #作为传输
import configparser #解析存储的账号密码
from conf import setting
import os
import shutil #删除文件夹的库
import hashlib
STATUS_CODE = {
250 : "Invalid cmd format, e.g: {'action':'get','filename':'test.py','size':344}",
251 : "Invalid cmd ",
252 : "Invalid auth data",
253 : "Wrong username or password",
254 : "Passed authentication",
255 : "Filename doesn't provided",
256 : "File doesn't exist on server",
257 : "ready to send file",
258 : "md5 verification",
800 : "the file exist,but not enough ,is continue? ",
801 : "the file exist !",
802 : " ready to receive datas",
900 : "md5 valdate success"
}
#定义一个继承 socketserver.BaseRequestHandler 的类,实例化时自动运行 handle 方法
class ServerHandler(socketserver.BaseRequestHandler):
def handle(self):
print(self.request) # self.request 就是类同与 socket 定义服务器时返回的实例对象
print(self.client_address) #
while True:
data = self.request.recv(1024)
data = json.loads(data.decode('utf-8'))
if data.get('action'):
if hasattr(self, data.get('action')):
func = getattr(self, data.get('action'))
func(**data)
else:
print('invaild')
else:
print('invaild')
def send_response(self, status_code):
response = {'status_code': status_code}
self.request.sendall(json.dumps(response).encode('utf-8'))
def auth(self, **data):
username = data['username']
password = data['password']
username = self.authenticate(username,password)
if username:
self.send_response(254) #正常返回
else:
self.send_response(253) #用户名或密码错误返回
def authenticate(self, username, password): #验证用户名了密码,我放在了conf/accounts.cfg
cfg = configparser.ConfigParser()
cfg.read(setting.ACCOUNT_PATH)
if username in cfg.sections():
if password == cfg[username]['Password']:
print('login successful')
self.username= username
self.mainPath = os.path.join(setting.BASE_DIR,'home',self.username).replace('\\','/')
return username
def put(self, **data):
s = hashlib.md5()
print('put')
print('data : ', data)
print(type(data))
file_name = data['file_name']
file_size = data['file_size']
target_path = data['target_path']
print('file_name = ', file_name)
print('file_size = ', file_size)
print('target_path = ', target_path)
abs_path = os.path.join(self.mainPath,target_path,file_name)
print('self.mainPath = ', self.mainPath)
print('target_path = ', target_path)
print('file_name = ', file_name)
print(abs_path)
################################################
has_received = 0
#断点续传十分重要的就是调整文件指针的位置
if os.path.exists(abs_path):
file_has_size = os.stat(abs_path).st_size
if file_has_size < file_size:
#断点续传
self.request.sendall('800'.encode('utf-8'))
choice = self.request.recv(1024).decode('utf-8')
if choice == "Y":
self.request.sendall(str(file_has_size).encode('utf-8'))
has_received = file_has_size
f = open(abs_path, 'r+b') #
else:
f = open(abs_path, 'r+b')
else:
# 文件存在
self.request.sendall('801'.encode('utf-8'))
return
else:
self.request.sendall('802'.encode('utf-8'))
f = open(abs_path, 'wb')
print('putting : %s' %file_name)
has_received_b = has_received
if has_received > 0:
f.seek(0)
while True:
if has_received - 1024 > 0:
s.update(f.read(1024))
has_received = has_received - 1024
else:
s.update(f.read(has_received))
break
f.seek(has_received_b)
print('has_received_b = ',has_received_b)
while has_received_b < file_size:
msg = self.request.recv(1024)
f.write(msg)
s.update(msg)
has_received_b += len(msg)
f.close()
client_md5 = self.request.recv(1024).decode('utf-8')
self.request.sendall(str(s.hexdigest()).encode('utf-8'))
if client_md5 == s.hexdigest():
print('Md5 checksum succeeded, Uploaded successfully')
else:
print('Md5 check failed ,upload failed')
def ls(self, **data): #接收到传来的数据 data
print('ls')
file_list = os.listdir(self.mainPath) #列出在当前目录下的所有文件
file_str = '\n'.join(file_list) #获得的为列表,转成字符串
if not len(file_list):
file_str = 'empty'
self.request.sendall(file_str.encode('utf-8')) #将结果发送回去
def cd(self, **data):
top_directory = 'C:PycharmProjects/begin/ftp_server/home/libai'#需要用的话要这里要改一下路径啊
print('cd')
dirname = data.get('dirname')
if dirname == 'top': #返回顶级目录
self.mainPath = top_directory
elif dirname =='..': # 返回上一层目录
if self.mainPath != top_directory:
self.mainPath = os.path.dirname(self.mainPath)
else: #else 就是进入某某目录了
back = self.mainPath
self.mainPath = os.path.join(self.mainPath, dirname).replace('\\','/')
if not os.path.isdir(self.mainPath):
self.mainPath = back
self.request.sendall('no'.encode('utf-8'))
return
self.request.sendall(self.mainPath.encode('utf-8'))
def mkdir(self, **data):#创建文件夹,嵌套或者单个文件夹
print('mkdir')
dirname = data.get('dirname')
path = os.path.join(self.mainPath, dirname).replace('\\','/')
if not os.path.exists(path):
if '/' in dirname:
os.makedirs(path)
else:
os.mkdir(path)
self.request.sendall('Created successfully'.encode('utf-8'))
else:
self.request.sendall('Directory already exists'.encode('utf-8'))
def rm(self, **data): #删除命令,嵌套或者单个文件
print('rm')
dirname = data.get('dirname')
path = os.path.join(self.mainPath, dirname).replace('\\', '/')
if not os.path.exists(path):
print('wen jian bu cun zai')
self.request.sendall('no'.encode('utf-8'))
else:
try:
shutil.rmtree(path) #这个是嵌套删除文件
except:
os.remove(path) #删除单个文件
print('delete')
self.request.sendall('remove'.encode('utf-8'))
#下载和上传是一样的了,
def downloads(self, **data):
# downloads 12.jpg
print('downloads')
s = hashlib.md5()
print('data : ', data)
file_name = data['file_name']
abs_path = os.path.join(self.mainPath,file_name).replace('\\', '/')
print('self.mainPath = ', self.mainPath)
print('abs_path = ', abs_path)
if os.path.exists(abs_path):
print('have this abs_path')
self.request.sendall('exist this file'.encode('utf-8'))
else:
self.request.sendall('no this file'.encode('utf-8'))
return
###############################################
file_size = os.stat(abs_path).st_size
print('file_size = ', file_size)
self.request.sendall(str(file_size).encode('utf-8'))
has_send = int(self.request.recv(1024).decode('utf-8'))
f = open(abs_path, 'rb')
has_send_b = has_send
if has_send > 0:
while True:
if has_send - 1024 > 0:
s.update(f.read(1024))
has_send = has_send - 1024
else:
s.update(f.read(has_send))
break
f.seek(has_send_b)
print('has_send = ', has_send)
while has_send_b < file_size:
message = f.read(1024)
s.update(message)
self.request.sendall(message)
has_send_b += len(message)
f.close()
client_md5 = self.request.recv(1024).decode('utf-8')
self.request.sendall(str(s.hexdigest()).encode('utf-8'))
if client_md5 == s.hexdigest():
print('Md5 checksum succeeded, Uploaded successfully')
else:
print('Md5 check failed ,upload failed')
#我自我觉得功能还是不错的,有些bug,修改了一下,发现bug还挺多,就算了,
#当练练手了,
对了,我的家目录设置在 ftp_server 下的home/libai 和root,上传默认在ftp_clent.py 目录下寻找文件上传,用 put 12.jpg image 这样的形式,要不然报错啊,会提示列表出界
下载啊,在你所在的家目录下的所在的路径下载,下载到ftp_client 所在的目录
大概就是这样了
总结: 断点续传主要就是要调整文件指针所在的位置,而文件指针的位置可以用已存在的文件大小来得出,
追加模式的文件指针是默认在文件末尾的
好了,就记录到这里了,
分享一下文件:链接:https://pan.baidu.com/s/1CCaIpw5D48Lf0cUFUud2zw 密码:lz2r
(不知道啥时候会和谐掉呢)