zoukankan      html  css  js  c++  java
  • 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,)
    
    '''
    解决黏包场景:
        应用场景在实时通讯时,需要阅读此次发的消息是什么
    不需要黏包解决场景
        下载或者上传文件的时候,最后要把包都结合在一起,黏包无所谓
    '''
  • 相关阅读:
    【PAT甲级】1043 Is It a Binary Search Tree (25 分)(判断是否为BST的先序遍历并输出后序遍历)
    Educational Codeforces Round 73 (Rated for Div. 2)F(线段树,扫描线)
    【PAT甲级】1042 Shuffling Machine (20 分)
    【PAT甲级】1041 Be Unique (20 分)(多重集)
    【PAT甲级】1040 Longest Symmetric String (25 分)(cin.getline(s,1007))
    【PAT甲级】1039 Course List for Student (25 分)(vector嵌套于map,段错误原因未知)
    Codeforces Round #588 (Div. 2)E(DFS,思维,__gcd,树)
    2017-3-9 SQL server 数据库
    2017-3-8 学生信息展示习题
    2017-3-5 C#基础 函数--递归
  • 原文地址:https://www.cnblogs.com/weiweivip666/p/13062031.html
Copyright © 2011-2022 走看看