• socket及黏包现象及解决黏包---day28


    1.四次挥手(补充)

        客户端向服务端发送一个请求消息,断开连接(代表客户端没有数据传输了)
        服务端接收请求,发出响应
        等到服务端所有数据收发完毕之后
        服务端向客户端发送断开连接的请求
        客户端接收请求后,发出响应
        等到2msl,最大报文生存时间之后
        客户端与服务端彻底断开连接

    2.socket

    socket的意义:通络通信过程中,信息拼接的工具(中文:套接字)
    # 开发中,一个端口只对一个程序生效,在测试时,允许端口重复捆绑 (开发时删掉)
    # 在bind方法之前加上这句话,可以让一个端口重复使用
    sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

    3.tcp基本语法

    ####客户端
    import socket
    
    #1.创建一个socket对象
    sk = socket.socket()
    
    #2.创建连接
    sk.connect(('127.0.0.1',9000))
    
    #3.发送数据(二进制字节流)
    sk.send("今天天气真好".encode('utf-8'))
    
    #4.关闭连接
    sk.close()
    ####服务端
    '''
    客户端和服务端在收发数据时
    一发一收是一对,否则会导致数据异常
    send 发送  recv接收
    '''
    import socket
    
    #1.创建socket对象
    sk = socket.socket()
    
    #2.绑定对应的ip和端口(注册网络,让其他主机能够找到)
    '''127.0.0.1 代表本地ip'''
    sk.bind(('127.0.0.1',9000))
    
    #3.开启监听
    sk.listen()
    
    #4.创建三次握手
    conn,addr = sk.accept()
    print(conn,addr)
    """
    conn = <socket.socket fd=456, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9000), raddr=('127.0.0.1', 61176)>
    addr = ('127.0.0.1', 61176)
    """
    
    #5.收发数据(recv里面的参数单位是字节,代表一次最多接受多少数据)
    res = conn.recv(1024)
    print(res.decode('utf-8'))
    
    #6.四次挥手
    conn.close()
    
    #7.退还端口
    sk.close()

    4.tcp循环发消息

    ####客户端
    # ### tcp客户端
    import socket
    
    #1.创建socket对象
    sk = socket.socket()
    
    #2.创建连接
    sk.connect(('127.0.0.1',9000))
    
    #3.发送数据
    '''
    res = sk.recv(1024) #一次接收最大字节1024
    print(res)
    '''
    while True:
        strvar = input('请输入内容')
        sk.send(strvar.encode('utf-8'))
        
        res = sk.recv(1024)
        if res == b'q':
            break
        print(res.decode('utf-8'))
    
    
    #4.关闭连接
    sk.close()
    ####服务端
    # ### 服务端
    import socket
    
    #1.创建socket对象
    sk = socket.socket()
    
    #2.注册主机绑定ip及端口
    sk.bind(('127.0.0.1',9000))
    
    #3.监听
    sk.listen()
    
    #4.三次握手
    # conn,addr = sk.accept()
    
    #5.接发收数据
    '''
    数据类型:二进制的字节流
    b修饰的字符串 => 代表的是二进制的字节流
    里面的字符必须是ascii编码中的字符,不能是中文,否则报错
    '''
    while True:
        conn,addr = sk.accept()
        while True:
            res = conn.recv(1024)
            print(res.decode('utf-8'))
            
            strvar = input('请输入内容')
            conn.send(strvar.encode('utf-8'))
            #退出
            if strvar == 'q':
                break
    
    #6.四次挥手
    conn.close()
    
    #7.退还端口
    sk.close()

    5.udp基本语法

    #### 客户端
    
    import socket
    #type = socket.SOCK_DGRAM => 返回udp协议对象
    
    #1.创建udp对象
    sk = socket.socket(type=socket.SOCK_DGRAM)
    
    
    #2.发送数据
    msg = '大妹纸,你好啊'
    #sendto (二进制字节流,(ip,端口))
    sk.sendto(msg.encode('utf-8'),('127.0.0.1',9000))
    
    #客户端接收服务端发过来的数据
    msg,ser_addr = sk.recvfrom(1024)
    print(msg.decode())
    print(ser_addr)
    
    
    #3.关闭连接
    sk.close()
    ####服务端
    
    import socket
    #type = socket.SOCK_DGRAM => 返回udp协议对象
    
    #1.创建对象
    sk = socket.socket(type=socket.SOCK_DGRAM)
    
    #2.绑定地址端口号
    sk.bind(('127.0.0.1',9000))
    
    #3.接收消息(udp作为服务端的时候,第一次一定是  接收消息)
    msg,cli_addr = sk.recvfrom(1024)
    print(msg.decode())
    print(cli_addr) # ('127.0.0.1', 56184)
    
    #服务端给客户端发消息
    msg = '我是老爷们,我不好'
    sk.sendto(msg.encode(),cli_addr)
    
    
    #.关闭连接
    sk.close()

    6.黏包

    # tcp协议在发送数据时,会出现黏包现象.    
        (1)数据粘包是因为在客户端/服务器端都会有一个数据缓冲区,
        缓冲区用来临时保存数据,为了保证能够完整的接收到数据,因此缓冲区都会设置的比较大。
        (2)在收发数据频繁时,由于tcp传输消息的无边界,不清楚应该截取多少长度
        导致客户端/服务器端,都有可能把多条数据当成是一条数据进行截取,造成黏包

    7.黏包出现的两种情况

    #黏包现象一:
        在发送端,由于两个数据短,发送的时间隔较短,所以在发送端形成黏包
    #黏包现象二:
        在接收端,由于两个数据几乎同时被发送到对方的缓存中,所有在接收端形成了黏包
    #总结:
        发送端,包之间时间间隔短 或者 接收端,接受不及时, 就会黏包
        核心是因为tcp对数据无边界截取,不会按照发送的顺序判断

    8.黏包对比:tcp和udp

    #tcp协议:
    缺点:接收时数据之间无边界,有可能粘合几条数据成一条数据,造成黏包 
    优点:不限制数据包的大小,稳定传输不丢包
    
    #udp协议:
    优点:接收时候数据之间有边界,传输速度快,不黏包
    缺点:限制数据包的大小(受带宽路由器等因素影响),传输不稳定,可能丢包
    
    #tcp和udp对于数据包来说都可以进行拆包和解包,理论上来讲,无论多大都能分次发送
    但是tcp一旦发送失败,对方无响应(对方无回执),tcp可以选择再发,直到对应响应完毕为止
    而udp一旦发送失败,是不会询问对方是否有响应的,如果数据量过大,易丢包

    9.解决黏包的几种办法

    # ### 客户端
    # ### 针对于tcp协议,会出现黏包现象
    
    import socket
    import time
    
    #1.创建对象
    sk = socket.socket()
    #2.创建连接
    sk.connect(('127.0.0.1',9000))
    time.sleep(0.1)
    
    #处理收发数据逻辑
    '''
    res1 = sk.recv(1024)
    res2 = sk.recv(1024)
    print(res1)
    print(res2)
    '''
    #先接收数据的总大小
    res = sk.recv(1)
    num = int(res.decode('utf-8'))
    #接收num这么多的字节数
    res1 = sk.recv(num)  #第一次
    res2 = sk.recv(1024)  #第二次
    print(res1)
    print(res2)
    
    #4.关闭连接
    sk.close()
    ####服务端
    
    import socket
    
    #1.创建对象
    sk = socket.socket()
    # 把这句话写在bind之前,让一个端口绑定多个程序,可以重复使用(仅用在测试环节)
    #sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    
    #2.注册主机绑定ip和端口
    sk.bind(('127.0.0.1',9000))
    
    #3.监听
    sk.listen()
    
    #4.三次握手
    conn,addr = sk.accept()
    
    #在发送真实数据之前,提前告诉接收端数据的大小
    conn.send('5'.encode())
    
    #处理收发数据逻辑
    conn.send('hello'.encode())  #第一次发送
    #time.sleep(0.1)
    conn.send(',world'.encode())  #第二次发送
    
    #6.四次挥手
    conn.close()
    
    #7.退还端口
    sk.close()
    ####客户端
    # ### 针对于tcp协议,会出现黏包现象
    
    import socket
    import time
    
    #1.创建对象
    sk = socket.socket()
    #2.创建连接
    sk.connect(('127.0.0.1',9000))
    time.sleep(0.1)
    
    # 处理收发数据逻辑
    '''
    黏包的现象
    res1 = sk.recv(1024)
    res2 = sk.recv(1024)
    print(res1)
    print(res2)
    '''
    #先接收数据的总大小
    res = sk.recv(8)
    num = int(res.decode('utf-8'))
    #接收num这么多的字节数
    res1 = sk.recv(num)
    res2 = sk.recv(1024)
    print(res1)
    print(res2)
    
    #关闭连接
    sk.close()
    # ### 服务端
     import socket
     
     #1.创建对象
     sk = socket.socket()
     
     #2.注册主机绑定ip和端口
     sk.bind(('127.0.0.1',9000))
     
     #3.监听
     sk.listen
     
     #4.三次握手
     conn,addr = sk.accept()
     
     #5.接发收数据处理逻辑
     conn.send('00000100'.encode())  #8字节
     
     #处理收发数据逻辑
     msg = 'hello' * 20
     conn.send(msg.encode())
     #time.sleep(0.1)
     conn.send(',world'.encode())
     
     #6.四次挥手
     conn.close()
     
     #7.退还端口
     sk.close()
    # ### 客户端   用struct解决
    # ### 针对于tcp协议,会出现黏包现象
    
    import socket
    import time
    import struct
    
    #1.创建对象
    sk = socket.socket()
    
    #2.创建连接
    sk.connect(('127.0.0.1',9000))
    
    #处理收发数据逻辑
    #接受第一次发送过来的数据(数据的大小)
    n = sk.recv(4) #都会压缩成4个字节来接收
    tup = struct.unpack("i",n) #unpack解包,'i'表示整型int,返回元组
    print(tup)  #(24,)
    n = tup[0]
    print(n)
    
    #第二次接收的真实的数据
    res1 = sk.recv(n)
    print(res1.decode())
    
    #第三次接收的真实的数据
    res2 = sk.recv(1024)
    print(res2.decode())
    
    #关闭连接
    sk.close()
    # ### 服务端
    import socket
    import struct
    
    #1.创建对象
    sk = socket.socket()
    
    #2.注册主机绑定ip和端口
    sk.bind(('127.0.0.1',9000))
    
    #3.监听
    sk.listen()
    
    #4三次握手
    conn,addr = sk.accept()
    
    #5.处理收发数据逻辑
    strvar = input('请输入')
    msg = strvar.encode()
    length = len(msg)
    
    
    #第一次把长度先发送过去
    res = struct.pack('i',length)
    conn.send(res)
    
    #第二次在发送真实数据
    conn.send(msg)
    
    #第三次在发送一个数据
    conn.send(',world'.encode())
    
    #6.四次挥手
    conn.close()
    
    #7.退还端口
    sk.close()

    10.struct用法(解决黏包)

    import struct
    '''
    pack:
        把任意长度的数字转化成具有4个字节的固定长度的字节流
    
    unpack:
        把4个字节值恢复成原本的数字,返回是元组
    '''
    
    #i => int  要转化的当前数据是整型
    '''pack的数值范围不是无限的,上限大概在21个亿左右,不要超过这个值'''
    res = struct.pack('i',999999999)
    res = struct.pack("i",1234343433)
    res = struct.pack("i",2100000011)
    print(res)
    print(len(res))
    
    #i =>  把对应的数据转换成int,最后返回元组
    tup = struct.unpack('i',res)
    print(tup[0]) ## (2100000011,)
    
    '''
    解决黏包场景:
        应用场景在实时通讯时,需要阅读此次发的消息是什么
    不需要黏包解决场景
        下载或者上传文件的时候,最后要把包都结合在一起,黏包无所谓
    '''
  • 相关阅读:
    VC++ 在Watch窗口显示GetLastError值以及详细信息
    VC++ Debug内存值
    VC++ 给你的代码强制加一个硬断点
    wchat_t与char互转
    使用forever运行nodejs应用
    C++ 检查Windows服务运行状态
    【转】Android横竖屏重力自适应
    可配置多功能门 SN74LVC1G57, 1G58, 1G97, 1G98, 1G99
    VPW协议解析
    STM32的TAMPER-RTC管脚作为Tamper使用
  • 原文地址:https://www.cnblogs.com/weiweivip666/p/13062031.html
Copyright © 2020-2023  润新知