在基于tcp的通信中,有可能出现黏包现象,那么什么是黏包现象呢,什么原因导致了黏包现象的发现?
1.连续发送数据时,本该分开接收的数据累积到一次接收了,分不清发送的是哪些消息
2.连续接收数据时,前面接收的数据大小小于它本应该接收的数据大小,那么紧接着它的下一个接收会接收到上次没有接收完的消息
这就导致多个消息混在一起
3.大发送多个小数据时,如果在网络延迟内,tcp的内部优化算法就会将多个消息合并在一起发送,这也就导致了黏包现象
黏包现象的本质原因是发送接收双方不知道对方应该处理的实际消息的大小,因此可以在发送消息前沟通好要发送数据
的大小,这样接收方就知道应该接收多少数据了。
基于TCP协议,实现一个简单的文件上传与下载:
客户端代码:
import socket
import os
'''
网盘具有上传与下载功能
上传: put source dest
下载: get source dest
退出:exit
ackpath: 路径检测
pathok: 路径正确
pathno: 路径错误
在接到输入的指令时,需要对输入的指令进行解析,看是否输入正确
'''
def ackPath(path, sk):
'''检测远程路径存在与否'''
ackPacket = 'ackpath-%s' % (path)
sk.send(ackPacket.encode('utf8'))
ackResult = bytes.decode(sk.recv(1024), 'utf8')
if ackResult == 'pathok':
return True
else:
return False
def parse(cmd, sk):
'''解析命令,如果正确就返回True, 否则返回False'''
cmds = cmd.split(' ')
'''检测命令是否有效'''
if len(cmds) != 3:
return False
if cmds[0] != 'put' and cmds[0] != 'get':
return False
'''路径检测'''
source = cmds[1]
dest = cmds[2]
sourceResult, destResult = False, False
if cmds[0] == 'put':
sourceResult = os.path.exists(source)
destResult = ackPath(dest, sk)
elif cmds[0] == 'get':
sourceResult = ackPath(source, sk)
destResult = os.path.exists(dest)
return sourceResult and destResult
def putFile(path1, path2, sk):
'''将本地文件上传至服务器
path1: 源路径
path2: 目的目录
sk: socket
我在开始传输前,需要先将文件名与文件大小传过程
'''
with open(path1, 'rb') as f1:
buffSize = 2048
fileLen = os.path.getsize(path1)
# 构建远程文件路径
remotePath = '%s/%s' % (path2, os.path.basename(path1))
# 传输一个文件头,方便服务识别与重组文件
fileHead = 'putfile-%s-%d' % (remotePath, fileLen)
sk.send(fileHead.encode('utf8'))
responseAck = bytes.decode(sk.recv(1024), 'utf8')
if responseAck == 'putok':
while f1.tell() != fileLen:
sk.send(f1.read(buffSize))
else:
print('文件发送失败!')
pass
pass
def getFile(path1, path2, sk):
'''从服务器下载文件
path1: 本地目录
path2: 要下载的文件
sk: socket
'''
buffSize = 2048
fileHead = 'getfile-%s' % (path1)
sk.send(fileHead.encode('utf8'))
fileLen = int(bytes.decode(sk.recv(1024), 'utf8'))
recvSize = 0
# 构建本地文件路径
localPath = '%s%s' % (path2, os.path.basename(path1))
f1 = open(localPath, 'wb')
sk.send('getstart'.encode('utf8'))
while recvSize < fileLen:
buff = sk.recv(buffSize)
f1.write(buff)
recvSize += len(buff)
f1.close()
pass
def execute(cmd, sk):
'''执行命令'''
cmds = cmd.split(' ')
command = cmds[0]
path1 = cmds[1]
path2 = cmds[2]
if command == 'put':
putFile(path1, path2, sk)
elif command == 'get':
getFile(path1, path2, sk)
# 后面把这里改为通过配置文件配置
info = '欢迎使用网盘'
print(info)
# 建立连接
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# 改为配置文件
addrPort = ('127.0.0.1', 8888)
sk.connect(addrPort)
propInfo = 'put sourcepath destpath/ get sourcepath destpath/ exit'
ackInfo = 'com'
sk.send(ackInfo.encode('utf8'))
data = bytes.decode(sk.recv(1024), 'utf8')
if data == ackInfo * 2:
print('服务器连接成功!')
while True:
cmd = input('请输入要执行的指令(%s): ' % propInfo)
if cmd == 'exit':
sk.send('exit'.encode('utf8'))
break
else:
if parse(cmd, sk):
execute(cmd, sk)
pass
else:
print('输入命令错误, %s' % propInfo)
continue
pass
else:
print('连接失败,请检查网络配置!')
sk.close()
服务端代码:
import socket
import os
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
addPort = ('127.0.0.1', 8888)
# 绑定地址
sk.bind(addPort)
sk.listen(5)
def putFile(path, size, conn):
'''接收文件'''
buffSize = 2048
with open(path, 'wb') as f1:
conn.send('putok'.encode('utf8'))
recvSize = 0
while recvSize < size:
buff = conn.recv(buffSize)
recvSize += len(buff)
f1.write(buff)
pass
def getFile(path, conn):
'''发送文件'''
buffSize = 2048
fileSize = os.path.getsize(path)
conn.send(str(fileSize).encode('utf8'))
response = bytes.decode(conn.recv(1024), 'utf8')
if response == 'getstart':
with open(path, 'rb') as f1:
while f1.tell() != fileSize:
conn.send(f1.read(buffSize))
pass
def ackPath(path):
return os.path.exists(path)
while True:
print('网盘服务中...')
# 等待连接
conn, addr = sk.accept()
print('来自 %s:%s 的连接' % (addr[0], addr[1]))
while True:
cmds = bytes.decode(conn.recv(1024), 'utf8').split('-')
cmd = cmds[0]
if cmd == 'ackpath':
result = ackPath(cmds[1])
if result == True:
conn.send('pathok'.encode('utf8'))
else:
conn.send('pathno'.encode('utf8'))
pass
elif cmd == 'putfile':
filePath = cmds[1]
fileSize = int(cmds[2])
putFile(filePath, fileSize, conn)
pass
elif cmd == 'getfile':
filePath = cmds[1]
getFile(filePath, conn)
pass
elif cmd == 'com':
conn.send('comcom'.encode('utf8'))
elif cmd == 'exit':
print('%s:%s 断开连接' % (addr[0], addr[1]))
conn.close()
break