返回顶部
首页 > 资讯 > 后端开发 > Python >面向对象之套接字(socket)和黏包
  • 542
分享到

面向对象之套接字(socket)和黏包

面向对象socket 2023-01-30 22:01:18 542人浏览 泡泡鱼

Python 官方文档:入门教程 => 点击学习

摘要

  tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端   基于UDP协议的Socket   server端: import socket udp_sk = socket.socket(type=socket.SOCK

  tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端

  基于UDP协议的Socket

  server端:

import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM)   #创建一个服务器的套接字
udp_sk.bind(('127.0.0.1',9000))        #绑定服务器套接字
msg,addr = udp_sk.recvfrom(1024)
print(msg)
udp_sk.sendto(b'hi',addr)                 # 对话(接收与发送)
udp_sk.close()                         # 关闭服务器套接字

  client端: 

import socket
ip_port=('127.0.0.1',9000)
udp_sk=socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello',ip_port)
back_msg,addr=udp_sk.recvfrom(1024)
print(back_msg.decode('utf-8'),addr)

 

  在学习粘包之前我们先学几个新模块:

  struct模块:

    1、 struct.pack
      struct.pack用于将python的值根据格式符,转换为字符串(因为Python中没有字节(Byte)类型,可以把这里的字符串理解为字节流,或字节数组)。其函数原型为:struct.pack(fmt, v1, v2, ...),参数fmt是格式字符串,关于格式字符串的相关信息在下面有所介绍。v1, v2, ...表示要转换的Python值。

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

import struct

a = 20
b = 400
s = struct.pack('ii', a, b)
print(s, type(s))
#输出:b'\x14\x00\x00\x00\x90\x01\x00\x00' <class 'bytes'>
print('length: ', len(s))
#输出:length:  8
s2 = struct.unpack('ii', s)
print(s2)
#输出:(20, 400)

s2 = struct.unpack('ii', s)
#报错:unpack requires a buffer of 4 bytes
#==>解压需要一个4字节的缓冲区,也就是说'ii'表示8个字节的缓冲

 

  #格式符"i"表示转换为int,'ii'表示有两个int变量。

  #进行转换后的结果长度为8个字节(int类型占用4个字节,两个int为8个字节)

  可以使用python的内置函数repr来获取可识别的字符串,其中十六进制的0x00000014, 0x00001009分别表示20和400。

  subprocess模块:

    这个模块用的不多,只是用于执行复杂的系统命令而已..我们不必深究.

 让我们基于tcp先制作一个远程执行命令的程序(命令ls -l ; lllllll ; pwd)

res=subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)

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

且只能从管道里读一次结果

注意

 

  同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令的时候又接收到之前执行的另外一部分结果,这种显现就是粘包。

  那粘包的成因是什么呢?

当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。 
MTU是Maximum Transmission Unit的缩写。意思是网络上传送的最大数据包。MTU的单位是字节。 大部分网络设备的MTU都是1500。
如果本机的MTU比网关的MTU大,大的数据包就会被拆开来传送,这样会产生很多数据包碎片,增加丢包率,降低网络速度。

 

  那我们如何解决粘包呢?

  1.我们可以用time模块,为两个文件之间设置一定的时间间隔,让两个不会粘结在一起,这个方法可行,但是会降低程序运行效率,而且很蠢.

  2.我们上面讲过struct模块,我们可以用struct模块把报头的四个字节取出来,再解包获得文件的大小,通过解包出来的解包大小来读取数据.

 服务端:

import socket
import subprocess

server = socket.socket()

server.bind(('127.0.0.1',8008))

server.listen(5)

while True:
    print("server is working.....")
    conn,addr = server.accept()     #建立链接
    # 字节类型
    while True:
        # 针对window系统
        try:
            cmd = conn.recv(1024).decode("utf8") # 阻塞

            if cmd == b'exit':      #判断与客户端链接是否结束
                break

            res=subprocess.Popen(cmd,
                             shell=True,
                             stderr=subprocess.PIPE,
                             stdout=subprocess.PIPE,
                             )
            # print("stdout",res.stdout.read())
            # print("stderr",res.stderr.read().decode("gbk"))
            out=res.stdout.read()
            err=res.stderr.read()

            print("out响应长度",len(out))       #输出出来的长度
            print("err响应长度",len(err))       #错误的长度
            if err:
                 import struct
                 header_pack = struct.pack("i", len(err))   #压包
                 conn.send(header_pack)     #发送
                 conn.send(err)         #发送错误信息
            else:
                 #构建报头
                 import struct
                 header_pack=struct.pack("i",len(out))      #压包
                 print("header_pack",header_pack)           #压包信息
                 # # 发送报头
                 conn.send(str(len(out)).encode("utf8"))    #发送压包
                 # 发送数据
                 conn.send(out)     #发送输出出来的数据

        except Exception as e:      #错误信息处理
            break


    conn.close()
服务端

 客户端:

import socket
import struct
sk = socket.socket()

sk.connect(('127.0.0.1',8008))

while 1:
    cmd = input("请输入命令:")
    sk.send(cmd.encode('utf-8')) # 字节       #发送输出的指令
    if cmd=="":
        continue
    if cmd == 'exit':           #判断是否退出
        break

    data_length=int(sk.recv(1024).decode("utf8"))           #接收到的信息
    print("data_length",data_length)        #打印出来

    recv_data_length=0          #接收到的长度
    recv_data=b""

    while recv_data_length<data_length:     #如果接收到的长度小于设定的数据长度,则全输出出来,否则直接输出最大长度
        data=sk.recv(1024)          #阻塞  设定最大输出长度
        recv_data_length+=len(data)         #计算数据长度
        recv_data+=data

    print(recv_data.decode("gbk"))


sk.close()
客户端

  3.还可以通过客户端发一个数据,服务器回一个数据,表示已经收到,然后再发送数据,这样就不会导致粘包了.

 服务端:

import socket
import JSON

sock = socket.socket()
sock.bind(("192.168.13.226",8080))
sock.listen(5)

while 1:
    print("正在等待链接.....")
    conn,addr = sock.accept()   #建立链接
    while 1:
        data = conn.recv(1024).decode("utf8")   #接收数据,并解码
        file_info = json.loads(data)
        print("file_info",file_info) #查看反序列化出来的是什么

        #文件信息
        action = file_info.get("action")
        filename = file_info.get("filename")
        filesize = file_info.get("filesize")

        conn.send(b"200")   #告诉客户端,我已准备接收数据

        #接收文件数据
        with open("put/" + filename,"wb") as f:
            recv_data_length = 0
            while recv_data_length < filesize:
                data = conn.recv(1024)
                recv_data_length += len(data)
                f.write(data)
                print("总文件大小为%s,已接收%s"% (filesize,recv_data_length))
        print("接收成功! ")
服务端

 

 客户端:

import socket
import os
import json

sock = socket.socket()
sock.connect(("192.168.13.226",8080))

while 1:
    cmd = input("请输入命令:")  #put 111.jpg

    action,filename = cmd.strip().split(" ")
    filesize = os.path.getsize(filename)

    file_info = {
        "action":action,        #命令
        "filename":filename,    #文件名字
        "filesize":filesize     #文件大小
    }

    file_info_json = json.dumps(file_info).encode("utf8")   #将字典序列化
    sock.send(file_info_json)   #发送序列化后的字典


    #确认服务端接收到了文件信息
    code = sock.recv(1024).decode("utf8")
    if code == "200":   #服务端已准备接收文件
        #发送文件数据
        with open(filename,"rb")as f:
            for line in f:          #把文件一行一行的发过去
                sock.send(line)
    else:
        print("服务器异常! ")
客户端

  关于hashlib补充

import hashlib

md5=hashlib.md5()
md5.update(b"hello")
md5.update(b"yuan")

print(md5.hexdigest())
print(len(md5.hexdigest()))

#helloyuan:   d843cc930aa76f7799bba1780f578439
#             d843cc930aa76f7799bba1780f578439

 

 

  综合例题:上传文件

服务端

import struct
import socket
import json
import hashlib

sock = socket.socket()
sock.bind(("127.0.0.1",8080))
sock.listen(5)

while 1:
    print("正在等待连接.....")
    conn,addr = sock.accept()
    while 1:

        #接收json的打包长度
        file_info_length_pack = conn.recv(4)
        file_info_length = struct.unpack("i",file_info_length_pack)[0]  #解包

        #接收json字符串
        file_info_json = conn.recv(file_info_length).decode("utf8")
        file_info = json.loads(file_info_json)  #反序列化

        action = file_info.get("action")
        filename = file_info.get("filename")
        filesize = file_info.get("filesize")

        #循环接收文件
        md5 = hashlib.md5()
        with open("put/"+ filename,"wb") as f:
            recv_data_length = 0
            while recv_data_length < filesize:
                data = conn.recv(1024)
                recv_data_length += len(data)
                f.write(data)
                #MD5摘要
                md5.update(data)    #写入要加密的的东西
                print("文件总大小:%s,已成功接收%s"% (filesize,recv_data_length))

        print("接收成功!")
        conn.send(b"OK")
        print(md5.hexdigest())
        md5_val = md5.hexdigest()   #解密
        client_md5 = conn.recv(1024).decode('utf8')
        if md5_val == client_md5:
            conn.send(b"203")
        else:
            conn.send(b"204")
服务端

 

客户端

import socket
import os
import json
import struct
import hashlib

sock = socket.socket()
sock.connect(("127.0.0.1",8080))

while 1:
    cmd = input("请输入命令:")  #put 111.jpg

    action,filename = cmd.strip().split(" ")
    filesize = os.path.getsize(filename)    #获取文件大小

    file_info = {
        "action":action,    #命令
        "filename":filename,#名字
        "filesize":filesize,#文件大小
    }

    #将file_info字典先序列化,再编码赋值给file_info_json
    file_info_json = json.dumps(file_info).encode("utf8")

    #打包
    ret = struct.pack("i",len(file_info_json))
    #发送file_info_json的打包长度
    sock.send(ret)
    #发送 file_info_json字节串
    sock.send(file_info_json)
    #发送 文件数据
    md5 = hashlib.md5()
    with open(filename,"rb") as f:
        for line in f:
            sock.send(line)
            md5.update(line)    #写入要加密的东西

    data = sock.recv(1024)
    print(md5.hexdigest())
    md5_val = md5.hexdigest()   #获取密文
    sock.send(md5_val.encode("utf8"))
    is_valid = sock.recv(1024).decode("utf8")
    if is_valid == "203":
        print("文件完整!")
    else:
        print("文件上传失败!")
客户端

 

--结束END--

本文标题: 面向对象之套接字(socket)和黏包

本文链接: https://lsjlt.com/news/179454.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 面向对象之套接字(socket)和黏包
      tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端   基于UDP协议的socket   server端: import socket udp_sk = socket.socket(type=socket.SOCK...
    99+
    2023-01-30
    面向对象 socket
  • Python面向对象之模块和包
    模块 模块的概念 模块是Python程序架构的一个核心概念 所有以.py结尾的源文件都是一个模块; 模块名也是标识符,需要遵循标识符的命名规则; 在模块中定义的全局变量,类,函数,都是直接给外界使用的工具; 模块就好比一个工具包,而...
    99+
    2023-01-31
    面向对象 模块 Python
  • 面向对象之类的成员,嵌套
    类的成员可分为三大类:字段丶方法和属性   字段包括:普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同.   普通字段属于对象   静态字段属于类 class Foo: #类变量(静态字段)...
    99+
    2023-01-30
    嵌套 面向对象 成员
  • Python面向对象之类和对象
    目录类定义类定义类和属性类中方法对象方法(普通方法)类方法静态方法魔术方法对象创建对象对象属性总结 类 定义类 所有类名首字母要求大写,多个单词时遵循驼峰命名法 所...
    99+
    2024-04-02
  • Python面向对象之入门类和对象
    目录什么是面向对象编程?定义类,从具体代码来感受吧!多个类和对象的观察补充一下类的属性(数据部分)总结什么是面向对象编程? 我们是不是听过面向过程,拿来放在一起对比就比较好理解了。 ...
    99+
    2024-04-02
  • Java 面向对象 之 关键字instanceof
    转载于 : http://www.verejava.com/id=16992811364048 public class TestInstanceof {public static...
    99+
    2023-06-02
  • Java面向对象基础知识之抽象类和接口
    抽象类(abstract): 抽象类不能创建实例,它只能作为父类被继承。抽象类是从多个具体类中抽象出来的父类,它具有更高层次的抽象。从多个具有相同特征的类中抽象出一个抽象类,以这个抽...
    99+
    2024-04-02
  • python 面向对象之class和封装
    # 封装 # Python并没有真正的私有化支持,但可用下划线得到伪私有 访问私有变量:实例._类名__变量名 访问私有方法:实例._类名__方法名() class Wife02(...
    99+
    2024-04-02
  • Python入门之面向对象和类
    目录一、两大编程思想二、类与对象三、定义Python中的类四、对象创建五、类属性、类方法、静态方法六、动态绑定属性和方法七、面向对象的三大特征八、方法重写总结一、两大编程思想 二、...
    99+
    2024-04-02
  • Java 深入浅出解析面向对象之抽象类和接口
    目录抽象类声明抽象类声明抽象方法案例使用规则接口声明接口案例接口特性抽象类和接口的区别抽象类 java语言,声明类时 abstract class Db{} 说明Db类为抽象类。 j...
    99+
    2024-04-02
  • Java面向对象之抽象类,接口的那些事
    目录一、抽象类1.抽象类概述1.1 为什么要有抽象类?(抽象类的作用)1.2 抽象类的定义2. 抽象类特点3.抽象类成员特点4.抽象类案例二、接口1.接口概述2.接口特点3.接口成员...
    99+
    2024-04-02
  • Java面向对象之super关键字怎么用
    这篇文章将为大家详细讲解有关Java面向对象之super关键字怎么用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。public class Test1 {public&nbs...
    99+
    2023-06-02
  • Java面向对象之final关键字怎么用
    这篇文章主要介绍了Java面向对象之final关键字怎么用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。public class FinalKeyword...
    99+
    2023-06-02
  • python基础之函数和面向对象详解
    目录函数函数参数变量作用域内嵌函数和闭包lambda 表达式面向对象三大特性类、类对象 和 实例对象类属性 和 对象属性私有魔法方法基本的魔法方法算术运算符属性访问 描述符...
    99+
    2024-04-02
  • python面向对象之反射和内置方法
    一、静态方法(staticmethod)和类方法(classmethod) 类方法:有个默认参数cls,并且可以直接用类名去调用,可以与类属性交互(也就是可以使用类属性) 静态方法:让类里的方法直接被类调用,就像正常调用函数一样 类方法和...
    99+
    2023-01-31
    反射 面向对象 方法
  • Java面向对象基础知识之委托和lambda
    委托定义类型,类型指定特定方法签名。可将满足此签名的方法(静态或实例)分配给该类型的变量,然后(使用适当参数)直接调用该方法,或将其作为参数本身传递给另一方法再进行调用。以下示例演示...
    99+
    2024-04-02
  • Java面向对象之包装类的用途与实际使用
    目录一、什么是包装类二、包装类的用途三、包装类的实际使用(以int和integer为例)1.int和integer类之间的转换2、Integer类内部的常用方法四、常见的面试题1.J...
    99+
    2024-04-02
  • C#面向对象设计原则之接口隔离原则
    接口隔离原则(ISP) 定义:使用多个专门的接口比使用单一的总接口要好。即不要把鸡蛋都放到一个篮子里。好处:比较灵活、方便,不想实现的或不用实现的可以不实现。解释说明:大部分人都喜欢...
    99+
    2024-04-02
  • [转载]Java面向对象程序设计之接口应用
    Java语言提供了一种接口(interface)机制。这种接口机制使Java的面向对象编程变得更加灵活。我们可以用接口来定义一个类的表现形式,但接口不能包含任何实现。在《Thinking in Java》一书中,作者对接口有这样的描述:“接...
    99+
    2023-06-03
  • Java面向对象基础知识之数组和链表
    数组的优点: 随机访问性强 查找速度快 数组要求是一块连续的内存空间来存储,这就要求在物理上这一片空间是连续的,每个元素都有指定的索引in...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作