# -*- coding:utf-8 -*-
import os
import random
import socket
import struct
import _thread
import sys
# 长度限制常量
FILEPATH_MAX_LEN = 256 # 文件路径最大长度
BLOCK_MAX_SIZE = 1464 # 数据报中所能包含的最大分片数据长度
DGRAM_MAX_SIZE = 1472 # 数据报载荷的最大长度
DGRAM_MIN_SIZE = 5 # 数据报载荷的最小长度
# 超时和重传常量
SHACKHANDS_TIMEOUT = 5 # 握手阶段超时时间
DATA_TRAN_TIMEOUT = 2 # 数据传输阶段超时时间
DATA_TRAN_RESEND = 5 # 数据传输阶段超时重传次数
SERVER_TIMEOUT = 30 # 服务端超时时间
# 数据报类型
SHACKHANDS_MSG = 0x00
SHACKHANDS_ACK_MSG = 0x01
DATA_TRAN_MSG = 0x02
DATA_TRAN_ACK_MSG = 0x03
# 客户端状态
SHAKEHANDS_STATUS = 0
DATA_TRAN_STATUS = 1
# 服务端监听握手请求的端口
SERVER_PORT = 44444
# 隐写类型
TYPE1 = 0x00 # 文件不包含隐写信息
TYPE2 = 0x01
TYPE3 = 0x02
TYPE4 = 0x03
TYPE5 = 0x04
TYPE6 = 0x05
TYPE7 = 0x06
TYPE8 = 0x07
TYPE9 = 0x08
# 客户端
class Client(object):
def __init__(self):
self.dgram_flag = random.randint(0, 10000) # 数据报标识,初始化时选择随机值
self.status = SHAKEHANDS_STATUS # 客户端状态
self.server_ip = '127.0.0.1' # 服务端ip,默认为本地环回ip
self.server_port = SERVER_PORT # 服务端端口
self.server_addr = None # 服务端地址信息
self.sock = None # 客户端套接字
self.filepath = None # 要传输的文件路径
# 服务端
class Server(object):
class ClientInfo(object): # 客户端信息
def __init__(self):
self.address = None
self.dgram_flag = None
self.sock = None
self.file = None
def __init__(self):
self.status = SHAKEHANDS_STATUS # 当前所处状态
self.local_ip = sys.argv[1] # 服务端绑定的ip
self.local_port = SERVER_PORT # 服务端绑定的监听握手请求的端口
self.local_addr = None # 服务端绑定的地址信息
self.sock = None # 服务端监听握手请求的套接字
# 每次调用从文件中依次读取一个block的数据
def get_file_block(filepath):
with open(filepath, 'rb') as f:
while True:
block = f.read(BLOCK_MAX_SIZE)
if block: yield block
else: yield None
# 构造数据报中的数据载荷
def create_dgram(
dgram_flag, dgram_type,
filename = None,
recv_port = None,
split_flag = None, split_index = None, data = None, data_len = None):
if dgram_type == SHACKHANDS_MSG:
dgram = struct.pack('!HB256s', dgram_flag, SHACKHANDS_MSG, filename.encode('UTF-8'))
elif dgram_type == SHACKHANDS_ACK_MSG:
dgram = struct.pack('!HBH', dgram_flag, SHACKHANDS_ACK_MSG, recv_port)
elif dgram_type == DATA_TRAN_MSG:
dgram = struct.pack('!HBBI' + str(data_len) + 's', dgram_flag, DATA_TRAN_MSG, split_flag, split_index, data)
elif dgram_type == DATA_TRAN_ACK_MSG:
dgram = struct.pack('!HBI', dgram_flag, DATA_TRAN_ACK_MSG, split_index)
else:
dgram = ''
return dgram
# 解析数据报中的数据载荷
def resolv_dgram(dgram, dgram_flag = None):
# 载荷长度不足DGRAM_MIN_SIZE视为错误数据
dgram_len = len(dgram)
if dgram_len < DGRAM_MIN_SIZE: return None
# 载荷的数据报标识和参数中的不一致,视为错误数据,参数为None表示忽略该判断
recv_dgram_flag, recv_dgram_type = struct.unpack('!HB', dgram[0:3])
if dgram_flag != None and recv_dgram_flag != dgram_flag: return None
# 根据载荷中的数据报类型不同作出相应的解析
if recv_dgram_type == SHACKHANDS_MSG: # 服务端提取数据报标识,文件名称
filename, = struct.unpack('!256s', dgram[3:])
return SHACKHANDS_MSG, recv_dgram_flag, filename
elif recv_dgram_type == SHACKHANDS_ACK_MSG: # 客户端提取数据传输阶段的服务器端口
recv_port, = struct.unpack('!H', dgram[3:])
return SHACKHANDS_ACK_MSG, recv_port
elif recv_dgram_type == DATA_TRAN_MSG: # 服务端提取分片标志,分片编号和数据内容
content_len = dgram_len - 8
split_flag, split_index, content = struct.unpack('!BI' + str(content_len) + 's', dgram[3:])
return DATA_TRAN_MSG, split_flag, split_index, content
elif recv_dgram_type == DATA_TRAN_ACK_MSG: # 客户端提取收到的分片编号
split_index, = struct.unpack('!I', dgram[3:])
return DATA_TRAN_ACK_MSG, split_index
else: return None
# 客户端线程
def t_client():
client = Client()
while True:
if client.status == SHAKEHANDS_STATUS:
client_shakehands(client)
elif client.status == DATA_TRAN_STATUS:
client_data_tran(client)
else: pass
# 客户端握手阶段
def client_shakehands(client):
# 初始化client
client.server_ip = input('please input ip address: ')
client.filepath = input('please input filepath: ')
client.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.server_port = SERVER_PORT
client.server_addr = (client.server_ip, client.server_port)
# 发送握手消息
dgram = create_dgram(
client.dgram_flag,
SHACKHANDS_MSG,
filename = os.path.basename(client.filepath))
ret = client.sock.sendto(dgram, client.server_addr)
print('client: send shakehand packet length: ' + str(ret) + '\n')
# 接收握手确认消息
client.sock.settimeout(SHACKHANDS_TIMEOUT)
try:
while True: # 循环接收数据
data, _ = client.sock.recvfrom(DGRAM_MAX_SIZE)
result = resolv_dgram(data, client.dgram_flag)
# 收到的是握手确认消息,更新服务端端口,进入数据传输阶段
if result != None and result[0] == SHACKHANDS_ACK_MSG:
print('client: recv shakehand ack port:' + str(result[1]) + '\n')
client.server_port = result[1]
client.server_addr = (client.server_ip, client.server_port)
break
except socket.timeout: # 超时则停止握手,放弃本次文件传输
print('client: shakehand connect timeout\n')
client.sock.close()
client.dgram_flag += 1
return
#except socket.error: # 捕获其他错误,主要是服务端未上线时导致的icmp不可达错误,因为会导致后续的接收逻辑出现问题
# print 'client: shakehand socket error\n'
# client.sock.close()
# client.dgram_flag += 1
# return
# 进入数据传输阶段,更新客户端状态,并更改超时时间
client.status = DATA_TRAN_STATUS
client.sock.settimeout(DATA_TRAN_TIMEOUT)
# 客户端数据传输阶段
def client_data_tran(client):
block_getter = get_file_block(client.filepath) # 获取读取文件内容的生成器
split_index = 0 # 初始化分片编号
# 循环发送分片数据
# 采取停等方式:发送编号为index的数据,等收到相应的回复后才发送编号为index+1的数据
while True:
# 读取文件数据并构造数据报载荷
block = next(block_getter) # 读取下一个block
split_flag = 1
if block == None: # 表示文件已读取完毕
block = ''.encode('UTF-8')
split_flag = 0
dgram = create_dgram(
client.dgram_flag,
DATA_TRAN_MSG,
split_flag = split_flag,
split_index = split_index,
data = block,
data_len = len(block))
# 发�
评论2