• 粘包现象


    一、基于udp的套接字

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

    udp服务端:

    ss = socket()   #创建一个服务器的套接字
    ss.bind()       #绑定服务器套接字
    while True :       #服务器无限循环
        cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)
    ss.close()                         # 关闭服务器套接字

    udp客户端:

    cs = socket()   # 创建客户套接字
    while True :      # 通讯循环
        cs.sendto()/cs.recvfrom()   # 对话(发送/接收)
    cs.close()                      # 关闭客户套接字

    1、udp套接字简单实例

    服务端:

    from socket import *
    
    udp_ss=socket(AF_INET,SOCK_DGRAM)
    udp_ss.bind(('127.0.0.1',8080))
    
    while True:
        msg,addr=udp_ss.recvfrom(1024)
        print(msg,addr)
        udp_ss.sendto(msg.upper(),addr)

    客户端:

    from socket import *
    
    udp_cs=socket(AF_INET,SOCK_DGRAM)
    
    while True:
        msg=input('>>: ').strip()
        if not msg:continue
        udp_cs.sendto(msg.encode('utf-8'),('127.0.0.1',8080))
        msg,addr=udp_cs.recvfrom(1024)
        print(msg.decode('utf-8'),addr)

    2、模拟聊天(由于udp无连接,所以可以同时多个客户端去跟服务端通信)

    服务端:

    from socket import *
    
    udp_ss=socket(AF_INET,SOCK_DGRAM)
    udp_ss.bind(('127.0.0.1',8081))
    
    while True:
        msg,addr=udp_ss.recvfrom(1024)
        print('来自[%s]的一条消息:%s' %(addr,msg.decode('utf-8')))
        msg_b=input('回复消息: ').strip()
        udp_ss.sendto(msg_b.encode('utf-8'),addr)

    客户端1:

    from socket import *
    
    udp_cs = socket(AF_INET,SOCK_DGRAM)
    
    while True :
        msg = input('请输入消息,回车发送: ').strip()
        if msg == 'quit' : break
        if not msg : continue
        udp_cs.sendto(msg.encode('utf-8'),('127.0.0.1',8081))
    
        back_msg,addr = udp_cs.recvfrom(1024)
        print('来自[%s]的一条消息:%s' %(addr,back_msg.decode('utf-8')))
    
    udp_cs.close()

    客户端2:

    from socket import *
    
    udp_cs = socket(AF_INET,SOCK_DGRAM)
    
    while True :
        msg = input('请输入消息,回车发送: ').strip()
        if msg == 'quit' : break
        if not msg : continue
        udp_cs.sendto(msg.encode('utf-8'),('127.0.0.1',8081))
    
        back_msg,addr = udp_cs.recvfrom(1024)
        print('来自[%s]的一条消息:%s' %(addr,back_msg.decode('utf-8')))
    
    udp_cs.close()

    二、粘包现象

    先做粘包现象:

    服务端:

    from socket import *
    phone=socket(AF_INET,SOCK_STREAM)
    phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    phone.bind(('127.0.0.1',8080))
    phone.listen(5)
    conn,client_addr=phone.accept()
    
    data1=conn.recv(1024)
    print('data1: ',data1)
    data2=conn.recv(1024)
    print('data2:',data2)

    客户端:

    from socket import *
    phone=socket(AF_INET,SOCK_STREAM)
    phone.connect(('127.0.0.1',8080))
    
    phone.send('hello'.encode('utf-8'))
    phone.send('world'.encode('utf-8'))

    我们再将上个随笔里的ssh例子拿出来(先执行 ipconfig /all 再执行 dir 看结果)

    客户端:

    from socket import *
    import subprocess
    cs=socket(AF_INET,SOCK_STREAM)
    cs.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    cs.bind(('127.0.0.1',8082))
    cs.listen(5)
    
    print('starting...')
    while True:
        conn,addr=cs.accept()
        print('-------->',conn,addr)
    
        while True:
            try:
                cmd=conn.recv(1024)
                res = subprocess.Popen(cmd.decode('utf-8'), shell=True,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE)
                stdout=res.stdout.read()
                stderr=res.stderr.read()
    
                #发送命令的结果
                conn.send(stdout+stderr)
            except Exception:
                break
        conn.close() #挂电话
    cs.close() #关机

    服务端:

    from socket import *
    ss=socket(AF_INET,SOCK_STREAM) #买手机
    ss.connect(('127.0.0.1',8082)) #绑定手机卡
    
    #发,收消息
    while True:
        cmd=input('>>: ').strip()
        if not cmd:continue
        ss.send(cmd.encode('utf-8'))
        cmd_res=ss.recv(1024)
        print(cmd_res.decode('gbk'))
    ss.close()

    注意:

    subprocess模块的结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码


    三、粘包

    注意:只有TCP有粘包现象,UDP永远不会粘包,首先需要掌握一个socket收发消息的原理

    应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。

    例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束

    所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

    此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

    两种情况会粘包:

    1、发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)

    2、接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包) 

    拆包的发生情况:

    当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。

    四、解决粘包方法

    粘包现象中第一个现象解决:

    解决一:(需要知道每次发过来的数据大小 不现实)

    from socket import *
    phone=socket(AF_INET,SOCK_STREAM)
    phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    phone.bind(('127.0.0.1',8080))
    phone.listen(5)
    conn,client_addr=phone.accept()
    
    data1=conn.recv(10)
    print('data1: ',data1)
    data2=conn.recv(4)
    print('data2:',data2)
    服务端
    from socket import *
    phone=socket(AF_INET,SOCK_STREAM)
    phone.connect(('127.0.0.1',8080))
    
    phone.send('helloworld'.encode('utf-8'))
    phone.send('egon'.encode('utf-8'))
    客户端

    解决二:

     服务端
     客户端

    ssh例子问题解决:

    为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据

    from socket import *
    phone=socket(AF_INET,SOCK_STREAM)
    phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    phone.bind(('127.0.0.1',8080))
    phone.listen(5)
    conn,client_addr=phone.accept()
    
    data1=conn.recv(1024)
    print('data1: ',data1)
    data2=conn.recv(1024)
    print('data2:',data2)
    服务端
    from socket import *
    import time
    phone=socket(AF_INET,SOCK_STREAM)
    phone.connect(('127.0.0.1',8080))
    
    phone.send('hello'.encode('utf-8'))
    time.sleep(5)
    phone.send('world'.encode('utf-8'))
    客户端

    五、struct模块(了解)

    该模块可以把一个类型,如数字,转成固定长度的bytes

    struct.pack('i',11111111)
    #struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围

    struct.pack用于将Python的值根据格式符,转换为字符串(因为Python中没有字节(Byte)类型)。它的函数原型为:struct.unpack(fmt, string)。

    struct.unpack做的工作刚好与struct.pack相反,用于将字节流转换成python数据类型。它的函数原型为:struct.unpack(fmt, string),该函数返回一个元组

  • 相关阅读:
    thinkphp分页样式css代码
    thinkphp两表,多表联合查询及分页的连贯操作写法
    ThinkPHP3.2.3新特性之:数据库设置
    ThinkPHP的主从数据库配置
    CentOS7 64位下MySQL5.7安装与配置(YUM)
    sessionid如何产生?由谁产生?保存在哪里?
    GIT使用
    Windows下安装composer
    树莓派debian配置lamp[解决Apache不显示php网页]
    【Linux】Debian 下安装 Apache,MySQL,PHP
  • 原文地址:https://www.cnblogs.com/moning/p/7506302.html
Copyright © 2020-2023  润新知