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

2018-09-05 07:58:10来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

 一丶套接字(socket)

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

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

  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模块:

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

  

  基于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)

 

二丶粘包

  让我们基于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()
客户端

  本示例通过客户端输入一段系统命令,把所输入的系统命令编码然后发给服务器端,服务器解码后再调用subprocess模块,把输出的值打包后,回传给客户端,然后客户端再解包,通过解包获得的文件内容信息大小打印出来.

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:Python基础入门笔记

下一篇:vim中使用pydiction对python代码进行补全