socket粘包问题解决

1. 高大上版解决粘包方式(自定制包头)

整体的流程解释
整个流程的大致解释:
我们可以把报头做成字典,字典里包含将要发送的真实数据的描述信息(大小啊之类的),然后json序列化,然后用struck将序列化后的数据长度打包成4个字节。
我们在网络上传输的所有数据 都叫做数据包,数据包里的所有数据都叫做报文,报文里面不止有你的数据,还有ip地址、mac地址、端口号等等,其实所有的报文都有报头,这个报头是协议规定的,看一下

发送时:
先发报头长度
再编码报头内容然后发送
最后发真实内容

接收时:
先手报头长度,用struct取出来
根据取出的长度收取报头内容,然后解码,反序列化
从反序列化的结果中取出待取数据的描述信息,然后去取真实的数据内容

server端

import socket
import subprocess
import struct
import json
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

phone.bind(('127.0.0.1', 8080))

phone.listen(5)

while 1:
    conn, client_addr = phone.accept()
    print(client_addr)

    while 1:
        try:
            cmd = conn.recv(1024)
            ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            correct_msg = ret.stdout.read()
            error_msg = ret.stderr.read()

            # 1 制作固定报头
            total_size = len(correct_msg) + len(error_msg)

            header_dict = {
                'md5': 'fdsaf2143254f',
                'file_name': 'f1.txt',
                'total_size':total_size,
            }

            header_dict_json = json.dumps(header_dict) # str
            bytes_headers = header_dict_json.encode('utf-8')

            header_size = len(bytes_headers)

            header = struct.pack('i', header_size)

            # 2 发送报头长度
            conn.send(header)

            # 3 发送报头
            conn.send(bytes_headers)

            # 4 发送真实数据:
            conn.send(correct_msg)
            conn.send(error_msg)
        except ConnectionResetError:
            break

conn.close()
phone.close()

client端

import socket
import struct
import json
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

phone.connect(('127.0.0.1',8080))

while 1:
    cmd = input('>>>').strip()
    if not cmd: continue
    phone.send(cmd.encode('utf-8'))

    # 1,接收固定报头
    header_size = struct.unpack('i', phone.recv(4))[0]

    # 2,解析报头长度
    header_bytes = phone.recv(header_size)

    header_dict = json.loads(header_bytes.decode('utf-8'))

    # 3,收取报头
    total_size = header_dict['total_size']

    # 3,根据报头信息,接收真实数据
    recv_size = 0
    res = b''

    while recv_size < total_size:

        recv_data = phone.recv(1024)
        res += recv_data
        recv_size += len(recv_data)

    print(res.decode('gbk'))

phone.close()

网络编程作业

加粗的是必须要做的,倾斜的是比较有难度的,大家别放松呀。

1. 多用户同时登陆
    2. 用户登陆,加密认证
    3. 上传/下载文件,保证文件一致性
    4. 传输过程中现实进度条
    5. 不同用户家目录不同,且只能访问自己的家目录
    6. 对用户进行磁盘配额、不同用户配额可不同
    7. 用户登陆server后,可在家目录权限下切换子目录
    8. 查看当前目录下文件,新建文件夹
    9. 删除文件和空文件夹
    10. 充分使用面向对象知识
    11. 支持断点续传

  简单分析一下实现方式:

  1.字符串操作以及打印 —— 实现上传下载的进度条功能

​ 2.socketserver —— 实现ftp server端和client端的交互

  3.struct模块 —— 自定制报头解决文件上传下载过程中的粘包问题

  4.hashlib或者hmac模块 —— 实现文件的一致性校验和用户密文登录

  5.os模块 —— 实现目录的切换及查看文件文件夹等功能

  6.文件操作 —— 完成上传下载文件及断点续传等功能

  看一下流程图:

2. 基于UDP协议的socket通信

UDP协议下的socket

udp是无链接的,先启动哪一端都不会报错

UDP下的socket通讯流程

  先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),recvform接收消息,这个消息有两项,消息内容和对方客户端的地址,然后回复消息时也要带着你收到的这个客户端的地址,发送回去,最后关闭连接,一次交互结束

服务端
import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM)   #创建一个服务器的套接字
udp_sk.bind(('127.0.0.1',8080))        #绑定服务器套接字
msg,addr = udp_sk.recvfrom(1024)
print(msg)
udp_sk.sendto(b'hi',addr)                 # 对话(接收与发送)
udp_sk.close()                         # 关闭服务器套接字
客户端
import socket
ip_port=('127.0.0.1',8080)
udp_sk=socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello',ip_port)
back_msg,addr=udp_sk.recvfrom(1024)
print(back_msg.decode('utf-8'),addr)

初版qq聊天

服务端
#_*_coding:utf-8_*_
import socket
ip_port=('127.0.0.1',8081)
udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #DGRAM:datagram 数据报文的意思,象征着UDP协议的通信方式
udp_server_sock.bind(ip_port)#你对外提供服务的端口就是这一个,所有的客户端都是通过这个端口和你进行通信的

while True:
    qq_msg,addr=udp_server_sock.recvfrom(1024)# 阻塞状态,等待接收消息
    print('来自[%s:%s]的一条消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))
    back_msg=input('回复消息: ').strip()

    udp_server_sock.sendto(back_msg.encode('utf-8'),addr)
客户端
import socket
BUFSIZE=1024
udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

qq_name_dic={
    'taibai':('127.0.0.1',8081),
    'Jedan':('127.0.0.1',8081),
    'Jack':('127.0.0.1',8081),
    'John':('127.0.0.1',8081),
}

while True:
    qq_name=input('请选择聊天对象: ').strip()
    while True:
        msg=input('请输入消息,回车发送,输入q结束和他的聊天: ').strip()
        if msg == 'q':break
        if not msg or not qq_name or qq_name not in qq_name_dic:continue
        udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])# 必须带着自己的地址,这就是UDP不一样的地方,不需要建立连接,但是要带着自己的地址给服务端,否则服务端无法判断是谁给我发的消息,并且不知道该把消息回复到什么地方,因为我们之间没有建立连接通道

        back_msg,addr=udp_client_socket.recvfrom(BUFSIZE)# 同样也是阻塞状态,等待接收消息
        print('来自[%s:%s]的一条消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))

udp_client_socket.close()

自制时间服务器的代码示例:

服务端
from socket import *
from time import strftime
import time
ip_port = ('127.0.0.1', 9000)
bufsize = 1024

udp_server = socket(AF_INET, SOCK_DGRAM)
udp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
udp_server.bind(ip_port)

while True:
    msg, addr = udp_server.recvfrom(bufsize)
    print('===>', msg)
    stru_time = time.localtime()  #当前的结构化时间
    if not msg:
        time_fmt = '%Y-%m-%d %X'
    else:
        time_fmt = msg.decode('utf-8')
    back_msg = strftime(time_fmt,stru_time)
    print(back_msg,type(back_msg))
    udp_server.sendto(back_msg.encode('utf-8'), addr)

tcp_server.close()
客户端
from socket import *
ip_port=('127.0.0.1',9000)
bufsize=1024

udp_client=socket(AF_INET,SOCK_DGRAM)

while True:
    msg=input('请输入时间格式(例%Y %m %d)>>: ').strip()
    udp_client.sendto(msg.encode('utf-8'),ip_port)

    data=udp_client.recv(bufsize)
    print('当前日期:',str(data,encoding='utf-8'))

百万年薪python之路 -- socket粘包问题解决的更多相关文章

  1. 百万年薪python之路 -- socket()模块的用法

    socket()模块的用法: import socket socket.socket(socket_family,socket_type,protocal=0) socket_family 可以是 A ...

  2. 百万年薪python之路 -- Socket

    Socket 1. 为什么学习socket 你自己现在完全可以写一些小程序了,但是前面的学习和练习,我们写的代码都是在自己的电脑上运行的,虽然我们学过了模块引入,文件引入import等等,我可以在程序 ...

  3. socket粘包问题解决

    粘包client.send(data1)client.send(data2)这两次send紧挨在一起,处理的时候会放在一起发过去在Linux里每次都粘包,Windows里面某次会出现粘包在两次send ...

  4. python之路--subprocess,粘包现象与解决办法,缓冲区

    一. subprocess 的简单用法 import subprocess sub_obj = subprocess.Popen( 'dir', #系统指令 shell=True, #固定方法 std ...

  5. python网络编程-socket“粘包”(小数据发送问题)

    一:什么是粘包 “粘包”, 即服务器端你调用时send 2次,但你send调用时,数据其实并没有立刻被发送给客户端,而是放到了系统的socket发送缓冲区里,等缓冲区满了.或者数据等待超时了,数据才会 ...

  6. 百万年薪python之路 -- 包

    包 使用import 和from xx import xx 现有如下结构 bake ├── __init__.py ├── api ├── __init__.py ├── policy.py └── ...

  7. 百万年薪python之路 -- 数据库初始

    一. 数据库初始 1. 为什么要有数据库? ​ 先来一个场景: ​ 假设现在你已经是某大型互联网公司的高级程序员,让你写一个火车票购票系统,来hold住十一期间全国的购票需求,你怎么写? 由于在同一时 ...

  8. 百万年薪python之路 -- 并发编程之 协程

    协程 一. 协程的引入 本节的主题是基于单线程来实现并发,即只用一个主线程(很明显可利用的cpu只有一个)情况下实现并发,为此我们需要先回顾下并发的本质:切换+保存状态 cpu正在运行一个任务,会在两 ...

  9. 百万年薪python之路 -- MySQL数据库之 Navicat工具和pymysql模块

    一. IDE工具介绍(Navicat) 生产环境还是推荐使用mysql命令行,但为了方便我们测试,可以使用IDE工具,我们使用Navicat工具,这个工具本质上就是一个socket客户端,可视化的连接 ...

随机推荐

  1. 【linux】【gitlab】gitlab安装、备份、恢复、升级、内存消耗问题

    前言 GitLab:GitLab 是一个用于仓库管理系统的开源项目,使用Git作为代码管理工具,并在此基础上搭建起来的web服务.功能:Gitlab 是一个提供代码托管.提交审核和问题跟踪的代码管理平 ...

  2. 浅谈HDFS(一)

    产生背景及定义 HDFS:分布式文件系统,用于存储文件,主要特点在于其分布式,即有很多服务器联合起来实现其功能,集群中的服务器各有各的角色 随着数据量越来越大,一个操作系统存不下所有的数据,那么就分配 ...

  3. WordPress 文章目录插件 Easy Table of Contents 配置教程

    今天介绍一款目录插件,本站已经使用一段时间,感觉不错,实现效果可以看文章页右侧边栏有目录,点击目录内容会快速定位.推荐给大家. 一.安装Easy Table of Contents 插件主页搜索Eas ...

  4. java-ztest测试报告的搭建,python-BeautifulReport

    今天用testng的时候感觉测试报告贼丑又慢,以下图片是对比.了解到ztest,搭建的时候发现网上没有教程,对java真是太不友好了,所以步骤记录下吧.有疑问的可进群:231733032 使用ztes ...

  5. Spring MVC-从零开始-@RequestMapping 注解value属性

    1.@RequestMapping 注解可以在控制器类的级别和/或其中的方法的级别上使用. 2.直接在方法上使用@RequestMapping package com.jt; import org.s ...

  6. 移动端适配(手机端rem布局详解)

    1. 问题的引出 如果html5要适应各种分辨率的移动设备,应该使用rem这样的尺寸单位,同时给出了一段针对各个分辨率范围在html上设置font-size的代码: html{font-size:10 ...

  7. mysql基础操作 增删改查

    如何使用终端操作数据库 如何登录数据库 mysql -u用户名 -p密码 比如: mysql -uroot -p123456 如何查询数据库服务器中所有的数据库 show databases; 如何选 ...

  8. 斐波那契数列n项的值。(递归和非递归算法Golang实现)

    递归实现: func f(num int) int { if num == 1 || num == 2 { return 1 } return f(num-1) + f(num-2) } 非递归实现: ...

  9. ElasticSearch Cardinality Aggregation聚合计算的误差

    使用ES不久,今天发现生产环境数据异常,其使用的ES版本是2.1.2,其它版本也类似.通过使用ES的HTTP API进行查询,发现得到的数据跟javaClient API 查询得到的数据不一致,于是对 ...

  10. python编程基础之十

    分支条件: 单一分支条件:if 条件 : 条件真运行... 双分支条件:if 条件 : 条件真运行else: 条件假运行... 多分支条件:if 条件1 : 条件1真运行elif 条件2 : 条件1假 ...