zoukankan      html  css  js  c++  java
  • socket基于TCP(粘包现象和处理)

    6socket套接字

    1565756321381

    socket网络套接字

    什么是socket

    • 每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。
    • ​ sock是处于应用层和传输层之间的抽象层,1他是一组操作起来非常简单的接口(接收数据)此接口接收数据之后,交由操作系统
    • 为什么存在socket抽象层
    • 如果直接与操作系统数据交互非常麻烦,繁琐,socket对这些繁琐的操作高度的封装和简化
    • socket在python中就是一个模块。

    7基于TCP协议的socket简单的网络通信

    import socket
    server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    

    一对一

    简单的socket抽象层 定义一些简单的接口 留下一个接口

    1.socket处于应用层和传输层之间,提供了一些简单的接口,避免与操作系统直接对接,省去了相当繁琐复杂的过程

    2.socket 在python中就是一个模块

    不考虑缓存区的大小 发送是没有限制的虽然不限制但是不会一次性发送过多

    有收必有发,send发--recv接收

    AF_UNIX

    基于套接字家族的名字:

    unix一切皆文件,基于文件的套接字(dicheng1wen),运行同一机器,通过访问同一个文件系统完成通信

    AF_INET(应用最广泛的一个)

    (AF_INET6 ipv6)

    encode转码 类字节 base()转码类字节

    执行的时候端口被占有 长连接

    如果进入需要等待

    收发是成对的 有发必有收,

    遇到一个recv 就阻塞 写两个就卡住

    base类型:

    ​ ASCII字符:在字符串前面b' '

    非ASCII字符:encode 转化成 bytes类型

    报错类型

    ConnectionRefusedError #服务端没开 报错
    
    ConnectionResetError #服务端崩溃
    
    ConnectionAbortedError#客户端退出
    

    单一

    #server端
    import socket#调用socket
    sk=socket.socket()#使用socket里面的socket
    #相当于买手机
    sk.bind(('127.0.0.1',8080))#bind(里面放入一个元组('ip',端口号))绑定手机卡
    sk.listen(5)#监听 等着有人给我打电话#阻塞 5是允许5个人 链接我,剩下的链接也可以链接,等待 
    #最大等待链接数5个
    #允许5个进入半链接池
    #()是和电脑性能有关 #
    
    conn,addr=sk.accept()#接收到别人的电话 connection 连接管道 address地址 来电显示 #阻塞
    ret=conn.recv(1024)#收听别人说话的长度#1024个字节#等消息
    print(ret)
    ret=conn.recv(1024)#等消息
    print(ret.decode('utf-8'))#要解码
    conn.send(b'1')#和别人说话,必须传一个bytes类型
    conn.close()#挂电话#关闭链接
    sk.close()#关手机#关闭socket对象
    
    ##重用ip地址
    #client端
    import socket
    sk=socket.socket()#买手机
    “”“
    sk.setsockopt(socket.SQL_SOCKET,socket.SO_REUSEADDR,1)#允许socket重用避免服务重启的时候 address already in use
    ”“”“
    sk.connect(('127.0.0.1',8080))#拨别人的号
    
    sk.send(b'hello')#发送消息
    sk.send('是我的'.encode('utf-8'))#中文要转码成为bit
    ret=sk.recv(1024)
    print(ret)
    conn.close()#挂电话
    sk.close()#关手机
    

    链接+循环通信

    #client
    import socket
    
    
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#使用网络传输 tcp
    try:
        phone.connect(('127.0.0.1', 8848))
    except ConnectionRefusedError:
        exit('服务端没开')
    
    while 1:
        try:
            data=input('强输入')
            phone.send(data.encode('gbk'))
            if not data:
                print('不能为空')
            elif data.upper() == 'Q':
                print('您退出了')
                break
            else:
                from_server_data=phone.recv(1024)
    
                print(f'来自服务的消息:{from_server_data.decode("gbk")}')
        except ConnectionResetError:
            print('服务端崩溃了连不上')
            break
        except ConnectionAbortedError:
            pass
    
    
    
    phone.close()
    
    #server
    import socket
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
    phone.bind(('127.0.0.1',8848))
    
    #开机监听
    while 1:#循环通信
        phone.listen()
        print('开启通信')
    
        conn,addr=phone.accept()#阻塞
        # s = input('请输入')
        # conn.send(s.encode('utf-8'))
        print(conn,addr)
    
    #from_client_data.upper()b
    
        while 1:
            try:
                from_client_data = conn.recv(1024)  # 至多接受1024个字节 阻塞
                if from_client_data.upper()==b'Q':
                    print('客户正常退出了')
                print(f'来自可无端的消息:{from_client_data.decode("utf-8")}')
                from_server_data=input('》》').strip().encode('utf-8')
                conn.send(from_server_data)
            except ConnectionResetError:
                print('客户端强行断开了')
                break
    
    
    

    远程命令

    传输的就是cmd是 gbk类型的base

    半连接池 listen

    9.tcp 实例:远程执行命令

    subprocesss#远程执行命令

    # shell: 命令解释器,相当于调用cmd 执行指定的命令。
    # stdout:正确结果丢到管道中。
    # stderr:错了丢到另一个管道中。
    # windows操作系统的默认编码是gbk编码。
    #server端
    import socket#引用socket模块
    import subprocess#引用远程执行模块
    import struct
    server_config=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#执行tcp默认不写
    server_config.bind(('127.0.0.1',8848))#写一个地址
    while 1:
        server_config.listen(5)
        print('开始通信')
        conn,addr=server_config.accept()
        while 1:
            try:
        		form_client_data=conn.recv(1024)
                if form_client_data.upper()==b'Q':
                    #设置一个退出的按钮
                    print('客户端退出了')
                else:
                	obj=subprocess.Popen(form_client_data.encode('utf-8'),
                                        shell=True,
                                        stdout=subprocess.PIPE,
                                        stderr=subprocess.PIPE,
                                        )
                    result=obj.stdout.read()+obj.stderr.subprocess.read()
                    #拼接到一起进行输出 如果输入的是错的正确的命令为空,如果输入的是正确的 错误的信息为空
                    total_size=len(result)
                    head_bytes=struct.pack('i',total_size)
                    conn.send(head_bytes)
                    conn.send(result)
              except ConnectionAbortedError:
                print(f'客户端{addr}断开')
                break
       conn.close()
    server_config.close()
    #client #客户端
    import socket
    import struct
    client_cofig=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    #AF_INET#SOCK_STREAM
    client_cofig.connect(('127.0.0.1',8848))
    while 1:
        s=input('1212')
        client_cofig.send(s.encode('utf-8'))
        head.client=client_cogif.recv(4)
        total.size=struct.unpack('i',head.client)
        total.ppt=b''
        while len(total.ppt)<total.size:
            total.ppt+=client.recv(1024)
        print(total.ppt.decode('gbk'))
    client_cofig.close()
    

    10.粘包现象

    11.操作系统的缓存区

    1.为什么出现粘包

    缓冲区

    缓冲区优点:

    1.暂时存储一些数据

    缓冲区

    缓冲区存在 如果你的网络波动,保证数据的收发稳定,匀速

    缓冲区一般大小是8k

    缺点:造成了粘包现象之一

    防止数据丢失

    不会一次性发很多 缓冲区满了就先不发

    没收完的东西存储在了缓冲区里 等下次取直接取 因为取的时间比发的时间快

    send完直接recv从缓冲区取 取出剩余

    12.什么情况下出现粘包

    1.出现粘包的情况

    粘包只会出现在tcp中

    2.收发的本质

    不一定要一收一发

    ​ 1.连续短暂的send多次(数据量很小),数据会统一发送出去(因为会等缓冲区满了之后封包才发送出去s)

    会有一个停留 nigle算法 连续send多次

    #client
    # import socket
    #
    # phone = socket.socket()
    #
    # phone.connect(('127.0.0.1',8848))
    #
    #
    # phone.send(b'he')
    # phone.send(b'll')
    # phone.send(b'o')
    #
    #
    # phone.close()
    # Nigle算法
    
    #server
    # import socket
    #
    # phone = socket.socket()
    #
    # phone.bind(('127.0.0.1',8848))
    #
    # phone.listen(5)
    #
    #
    # conn,addr = phone.accept()  # 等待客户端链接我,阻塞状态中
    #
    # from_client_data = conn.recv(1024)  # 最多接受1024字节
    # print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}')
    # conn.close()
    # phone.close()
    
    
    

    2.send数量过大 send超过限制对方recv接受的数量时 会暂存缓存区。

    第二次recv时会将上一次没有recv完的剩余数据

    收发现象

    发多次收一次

    发一次收多次

    13.low解决粘包现象

    如何解决 服务端发一次数据 10000字节,

    客户端接受数据时,循环接受,1每次(至多)接收1024个字节直至将所有的字节全部接收完毕,将接收的数据拼接在一起,最后解码

    遇到的问题:recv次数无法确定

    ​ 你发送总数据之前,先给我发一个总数据的长度:len()

    然后在发送总数据

    客户端:先接收一个长度接收字节因为有粘包,50000个字节

    encode转字符串 int转出数字

    struct模块 加个类似tcp 固定head头 是固定长度头 之后解包

    然后再循环recv 控制循环条件是反解出来的在服务端制作的长度

    
    

    14.recv工作原理

    '''
    源码解释:
    Receive up to buffersize bytes from the socket.
    接收来自socket缓冲区的字节数据,
    For the optional flags argument, see the Unix manual.
    对于这些设置的参数,可以查看Unix手册。
    When no data is available, block untilat least one byte is available or until the remote end is closed.
    当缓冲区没有数据可取时,recv会一直处于阻塞状态,直到缓冲区至少有一个字节数据可取,或者远程端关闭。
    When the remote end is closed and all data is read, return the empty string.
    关闭远程端并读取所有数据后,返回空字符串。
    '''
    ----------服务端------------:
    # 1,验证服务端缓冲区数据没有取完,又执行了recv执行,recv会继续取值。
    
    import socket
    
    phone =socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
    phone.bind(('127.0.0.1',8080))
    
    phone.listen(5)
    
    conn, client_addr = phone.accept()
    from_client_data1 = conn.recv(2)
    print(from_client_data1)
    from_client_data2 = conn.recv(2)
    print(from_client_data2)
    from_client_data3 = conn.recv(1)
    print(from_client_data3)
    conn.close()
    phone.close()
    
    # 2,验证服务端缓冲区取完了,又执行了recv执行,此时客户端20秒内不关闭的前提下,recv处于阻塞状态。
    
    import socket
    
    phone =socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
    phone.bind(('127.0.0.1',8080))
    
    phone.listen(5)
    
    conn, client_addr = phone.accept()
    from_client_data = conn.recv(1024)
    print(from_client_data)
    print(111)
    conn.recv(1024) # 此时程序阻塞20秒左右,因为缓冲区的数据取完了,并且20秒内,客户端没有关闭。
    print(222)
    
    conn.close()
    phone.close()
    
    
    # 3 验证服务端缓冲区取完了,又执行了recv执行,此时客户端处于关闭状态,则recv会取到空字符串。
    
    import socket
    
    phone =socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
    phone.bind(('127.0.0.1',8080))
    
    phone.listen(5)
    
    conn, client_addr = phone.accept()
    from_client_data1 = conn.recv(1024)
    print(from_client_data1)
    from_client_data2 = conn.recv(1024)
    print(from_client_data2)
    from_client_data3 = conn.recv(1024)
    print(from_client_data3)
    conn.close()
    phone.close()
    ------------客户端------------
    # 1,验证服务端缓冲区数据没有取完,又执行了recv执行,recv会继续取值。
    import socket
    import time
    phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    phone.connect(('127.0.0.1',8080))
    phone.send('hello'.encode('utf-8'))
    time.sleep(20)
    
    phone.close()
    
    
    
    # 2,验证服务端缓冲区取完了,又执行了recv执行,此时客户端20秒内不关闭的前提下,recv处于阻塞状态。
    import socket
    import time
    phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    phone.connect(('127.0.0.1',8080))
    phone.send('hello'.encode('utf-8'))
    time.sleep(20)
    
    phone.close()
    
    # 3,验证服务端缓冲区取完了,又执行了recv执行,此时客户端处于关闭状态,则recv会取到空字符串。
    import socket
    import time
    phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    phone.connect(('127.0.0.1',8080))
    phone.send('hello'.encode('utf-8'))
    phone.close()
    

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

    服务端

    import socket
    import subprocess
    import json
    import struct
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
    phone.bind(('192.168.14.97',8848))
    
    #开机监听
    while 1:
        phone.listen(2)
        print('开启通信')
    
        conn,addr=phone.accept()#阻塞
        # s = input('请输入')
        # conn.send(s.encode('utf-8'))
        print(conn,addr)
    
    #from_client_data.upper()b
    
        while 1:
            try:
                from_client_data = conn.recv(1024)  # 至多接受1024个字节 阻塞
                if from_client_data.upper()==b'Q':
                    print('客户正常退出了')
                    break
                else:
                    # print(f'来自可无端的消息:{from_client_data.decode("utf-8")}')
                    # from_server_data=input('》》').strip().encode('utf-8')
                    #
                    obj = subprocess.Popen(from_client_data.decode('utf-8'),
                                           shell=True,
                                           stdout=subprocess.PIPE,#正确命令
                                           stderr=subprocess.PIPE,# 错误命令
    
                                           )
    
                    # s= # 正确命令
                    # s2=obj.stderr.read().decode('gbk')  # 错误命令
                    total_base=obj.stdout.read()+obj.stderr.read()#原本就是gbk格式的
                    total_size=len(total_base)
                    #1.制作自定义报头
                    head_dic={'file_name':'text1',
                    'md5':54645898788,
                    'total_size':total_size}
                    #2.json形式的报头
                    head_dic_json=json.dumps(head_dic)
                    #3.bytes形式包头(把json形式字符串改成字节)
                    head_dic_json_bytes = json.dumps(head_dic).encode('utf-8')
                    #4获取bytes的总字节数(int类型)
                    len_head_dic_json_bytes=len(head_dic_json_bytes)
                    #5将不固定的int总字节变成固定长度的4个字节
                    four_head_bytes=struct.pack('i',len_head_dic_json_bytes)
                    #6 发送固定的4个字节
                    conn.send(four_head_bytes)
                    #7发送包头数据
                    conn.send( head_dic_json_bytes)
                    #8发送总数据
                    conn.send(total_base)
            except ConnectionResetError:
                print('客户端强行断开了')
                break
        conn.close()
    phone.close()
    

    客户端

    import socket
    import struct
    import json
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    try:
        phone.connect(('192.168.14.97', 8848))
    except ConnectionRefusedError:
        exit('服务端没开')
    
    while 1:
        try:
            data=input('强输入')
            if not data:
                print('不能为空')
            elif data.upper() == 'Q':
                print('您退出了')
                break
            else:
                phone.send(data.encode('utf-8'))
                #获取的包头固定数据字节
                from_server_data = phone.recv(4)
                #转换获取的字节(变成int类型的长度)
                len_totals=struct.unpack('i',from_server_data)[0]
                #获得bytes类型字典的总字节数
                len_totals_bytes=phone.recv(len_totals)
                #获取betes的dic数据(变成json字符串类型)1
                len_totals_bytes_json=len_totals_bytes.decode('utf-8')
                #解开json字符串获取原类型
                len_totals_json_dic=json.loads(len_totals_bytes_json)
                #通过自定义包头的里面的长度获取数据
                total_ppt=b''
                while len(total_ppt)< len_totals_json_dic['total_size']:
                    total_ppt+=phone.recv(1024)
                print(f'来自服务的消息:{total_ppt.decode("gbk")}')
                print(f'来自服务的消息:{len(total_ppt)}')
        except ConnectionResetError:
            print('服务端崩溃了连不上')
            break
        except ConnectionAbortedError:
            pass
    
    
    
    phone.close()
    
  • 相关阅读:
    C语言第五次作业
    c语言第4次作业
    第12次作业
    C语言第9次作业
    C语言第8次作业2
    C语言第8次作业
    C语言第七次作业---要死了----
    C语言第七次作业
    物联网工程实践第二次作业
    物联网工程实践第一次作业
  • 原文地址:https://www.cnblogs.com/strawberry-1/p/11377124.html
Copyright © 2011-2022 走看看