• 第69天python学习TCP粘包代码


    tcp协议在发送数据时,会出现黏包现象.

        (1)数据粘包是因为在客户端/服务器端都会有一个数据缓冲区,

        缓冲区用来临时保存数据,为了保证能够完整的接收到数据,因此缓冲区都会设置的比较大。

        (2)在收发数据频繁时,由于tcp传输消息的无边界,不清楚应该截取多少长度

    导致客户端/服务器端,都有可能把多条数据当成是一条数据进行截取,造成黏包

     

     黏包出现的两种情况

    黏包现象一:

    在发送端,由于两个数据短,发送的时间隔较短,所以在发送端形成黏包

    黏包现象二:

    在接收端,由于两个数据几乎同时被发送到对方的缓存中,所有在接收端形成了黏包

    总结:

        发送端,包之间时间间隔短 或者 接收端,接受不及时, 就会黏包

        核心是因为tcp对数据无边界截取,不会按照发送的顺序判断

     

    黏包对比:tcp和udp

    tcp协议:

    优点:接收时数据之间无边界,有可能粘合几条数据成一条数据,造成黏包

    缺点:不限制数据包的大小,稳定传输不丢包

     

    udp协议:

    优点:接收时候数据之间有边界,传输速度快,不黏包

    缺点:限制数据包的大小(受带宽路由器等因素影响),传输不稳定,可能丢包

     

    tcp和udp对于数据包来说都可以进行拆包和解包,理论上来讲,无论多大都能分次发送

    但是tcp一旦发送失败,对方无响应(对方无回执),tcp可以选择再发,直到对应响应完毕为止

    而udp一旦发送失败,是不会询问对方是否有响应的,如果数据量过大,易丢包

     

    解决黏包问题

    解决黏包场景:

    应用场景在实时通讯时,需要阅读此次发的消息是什么

    不需要解决黏包场景:

    下载或者上传文件的时候,最后要把包都结合在一起,黏包无所谓.

    1.黏包现象

    黏包现象一:

    服务端代码:

    import socket

    tcp_sever = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    # 在bind方法之前加上这句话,可以让一个端口重复使用
    tcp_sever.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    # 绑定地址端口(在网络上注册主机)
    tcp_sever.bind( ("127.0.0.1",9001) )
    tcp_sever.listen(5) #可以挂起5个请求

    conn,addr = tcp_sever.accept()
    conn.send("6".encode("utf-8"))
    message = "hello,my "
    conn.send(message.encode("utf-8"))
    conn.send("world".encode("utf-8"))

    # 四次挥手
    conn.close()
    # 退还端口
    tcp_sever.close()

    服务端
    import socket

    tcp_clint = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    tcp_clint.connect(("127.0.0.1", 9001))

    res0 = int(tcp_clint.recv(1).decode("utf-8")) #res0 "6"
    print(res0)

    res1 = tcp_clint.recv(res0)
    print(res1)

    #print(res1.decode("utf-8"))
    res2 = tcp_clint.recv(20)
    print(res2)
    tcp_clint.close()

     服务端向客户端发送两次消息,客户端接收三次,其中第三次出现黏包现象,因为客户端设置只接收6个字节,而服务端第二次发送了8个字节数,所有将剩下2个字节与第三次发送的数据黏包一起发送过来了,现象如截图:

    服务器端:
    #发生粘包场景2
    import socket
    import time

    tcp_sever = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    #在bind方法之前加上这句话,可以让一个端口重复使用
    tcp_sever.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

    #绑定地址端口(在网络上注册主机)
    tcp_sever.bind(("127.0.0.1", 9001))
    tcp_sever.listen(5)

    conn,addr = tcp_sever.accept()
    conn.send("000120".encode("utf-8"))
    message = 'hello' * 20
    conn.send(message.encode("utf-8"))

    # time.sleep(1)
    conn.send("world".encode("utf-8"))

    #四次挥手
    conn.close()

    #退还端口
    tcp_sever.close()

    客户端:

    #发生粘包场景2
    import socket
    import time

    tcp_clint = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    tcp_clint.connect(("127.0.0.1", 9001))

    time.sleep(0.2) #当发送端时间很短是出现粘包
    res0 = int(tcp_clint.recv(6).decode("utf-8")) #res0 "6"
    print(res0)

    res1 = tcp_clint.recv(res0)
    print(res1)

    #print(res1.decode("utf-8"))
    res2 = tcp_clint.recv(10)
    print(res2)
    tcp_clint.close()
    这个因为在接收端,由于两个数据几乎同时被发送到对方的缓存中,所有在接收端形成了黏包,以至于第三次接收到的内容为空,因为已经黏包到第二次数据上了。如下截图:

    这种情况只要将第二次发送和第三次发送数据隔开一点时间,比如sleep1秒即可,即将注释#time.sleep(1)去掉后运行结果截图如下:

     

  • 相关阅读:
    对宏的另外一些认识 及 assert.h的实现细节
    不要想太多
    线段树
    SQL基础 利用SELECT检索数据
    hidden表单值无法重置的缺陷
    oracle 数据库登陆
    基于ejb3,对JDBC进行封装,让使用JDBC时能像hibernate使用annotation注解一样简便,而且更加轻巧
    GoJS的一些使用技巧
    GoJS的学习使用
    灵活使用trim方法
  • 原文地址:https://www.cnblogs.com/jianchixuexu/p/11878670.html
Copyright © 2020-2023  润新知