socket 编程

在基于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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值