socket 网络编程


tcp协议 和 udp协议

TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。

UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。

socket的基本使用

TCP 的socket — 基础对话版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# server端
import socket

sk = socket.socket() # 创建套接字
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 避免重用ip和端口
sk.bind(('127.0.0.1',8090)) # 绑定端口
sk.listen() # 监听链接

conn,addr = sk.accept() # 接收客户端链接
ret = conn.recv(1024) # 接收客户端消息
print(ret) # 打印客户端消息
conn.send(b'hello') # 向客户端发送消息 在网络上传输的只有二进制1010,所以必须是bytes类型

ret = conn.recv(1024).decode('utf-8')
print(ret)
conn.send('吃面条吧'.encode('utf-8'))

conn.close() # 关闭客户端链接
sk.close() # 关闭服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# client端
import socket
sk = socket.socket() # 创建客户端套接字
sk.connect(('127.0.0.1',8090)) # 链接服务端

# 接收|发送消息
sk.send(b'hey') # 发送消息
ret = sk.recv(1024) # 接收消息
print(ret)

sk.send('中午吃什么'.encode('utf-8'))
ret = sk.recv(1024).decode('utf-8')
print(ret)

sk.close()

循环对话版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# server端
import socket
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(('127.0.0.1',8090))
sk.listen()
print('服务端开始监听...')

conn,addr = sk.accept() # 获取到一个客户端的链接,已经完成了三次握手建立了一个连接
# 阻塞
print('有新的链接进入{}'.format(addr))

# 对话
while 1:
ret = conn.recv(1024).decode('utf-8') # 阻塞,直到收到客户端发来的消息
print(ret)
if ret == 'bye':
conn.send('bye'.encode('utf-8'))
break

msg = input('server:>>>')
if msg == 'bye':
conn.send('bye'.encode('utf-8'))
break
conn.send(msg.encode('utf-8'))

conn.close()
sk.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# client端
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',8090))

while True:
msg = input('client:>>>')
if msg == 'bye':
sk.send('bye'.encode('utf-8'))
break
sk.send(msg.encode('utf-8'))
ret = sk.recv(1024).decode('utf-8')
if ret == 'bye':
break
print(ret)

sk.close()

时间戳转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
ip_port = ('127.0.0.1',8090)
# server端
# server 接收时间戳时间,转化成格式化时间
# client 每10秒time.time() 吧时间戳时间发给server
import sys
import os
sys.path.insert(0,os.path.dirname(os.getcwd()))
import socket
import time
from conf import settings

sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(settings.ip_port)
sk.listen()
print('服务端开始监听...')

conn,addr = sk.accept()
print('有新的链接进入{}'.format(addr))

# 收发消息
while 1:
res = conn.recv(1024).decode('utf-8')
if res == '':
break
print('client:%s' %res)
msg = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(float(res)))
print(type(msg),msg)
conn.send(('server:%s' %msg).encode('utf-8'))

conn.close()
sk.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# client端
import time
import socket

# msg = time.time()
# print(type(msg),msg) # float类型

sk = socket.socket()
sk.connect(('127.0.0.1',8090))
count = 1
while 1:
if count > 3:
break
now_time = str(time.time())
print(now_time)
sk.send(now_time.encode('utf-8'))
ret = sk.recv(1024).decode('utf-8')
print(ret)
time.sleep(3)
count += 1

sk.close()

UDP 的socket — 基础对话版

1
2
3
4
5
6
7
8
9
10
11
# server 
import socket
sk = socket.socket(type=socket.SOCK_DGRAM) # 创建一个服务器的套接字
sk.bind(('127.0.0.1',8090)) # 绑定服务器套接字

# 对话(接收与发送)
msg,addr = sk.recvfrom(1024)
print(msg.decode('utf-8'))
sk.sendto(b'bye',addr) # 发送消息要带着地址

sk.close() # 关闭服务器套接字
1
2
3
4
5
6
7
8
9
10
11
# client
import socket
ip_port = ('127.0.0.1',8090)
sk = socket.socket(type=socket.SOCK_DGRAM)

# 对话(接收与发送)
sk.sendto(b'hello',ip_port) # 发送消息要带着地址
ret,addr = sk.recvfrom(1024)
print(ret.decode('utf-8'))

sk.close()

在udp的消息通信的时候:

  1. 不需要进行监听 (listen)
  2. 不需要建立链接 (accept)
  3. 在启动服务之后,只能被动的等到客户端发送消息过来
  4. 客户端发送消息的同时,还会带着地址信息过来
  5. 服务端进行消息服务的时候,不仅需要发送消息,还需要带着对方的地址回去

UDP 实现简易QQ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# server 
import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1',9999)
udp_sk.bind(ip_port)
print('Bind UDP on 9999...')

while True:
# 接收数据:
data,addr = udp_sk.recvfrom(1024)
data = data.decode('utf-8')
print(('Received from %s:%s') %(addr,data))

msg = input('server:>>>')
udp_sk.sendto(msg.encode('utf-8'),addr)

udp_sk.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# client 
import socket
ip_port = ('127.0.0.1',9999)
udp_sk = socket.socket(type=socket.SOCK_DGRAM)

while True:
msg = input('client:>>>')
msg = '\033[32mform client1:%s\033[0m' %msg
udp_sk.sendto(msg.encode('utf-8'),ip_port)

data,addr = udp_sk.recvfrom(1024)
data = data.decode('utf-8')
print(('Received from %s:%s') %(addr,data))

udp_sk.close()

UDP 实现简易时间同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# server 
# server端提供时间同步服务
# 接收信息 时间的格式
# 将server端时间 转换成 接收到的格式
# 返回给client

import socket
import time
sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1',9999)
sk.bind(ip_port)
print('Bind UDP on 9999...')

while True:
data,addr = sk.recvfrom(1024)
data = data.decode('utf-8')
# print(type(data),data)
print('form %s:%s' %(addr,data))
# time_str = time.strftime(data,time.localtime(time.time()))
time_str = time.strftime(data)
print(type(time_str),time_str)
sk.sendto(str(time_str).encode('utf-8'),addr)

sk.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# client 
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1',9999)


time_format = '%Y-%m-%d %H:%M:%S'
sk.sendto(time_format.encode('utf-8'),ip_port)
data,addr = sk.recvfrom(1024)
data = data.decode('utf-8')
print(data)

sk.close()

# 操作方式:
# 1、操作系统定时任务 + python代码
# 2、while True + time.sleep

黏包现象

subprocess 远程执行命令

1
2
3
4
5
6
7
8
import subprocess
res = subprocess.Popen('dir',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
print('stdout:' + res.stdout.read().decode('gbk')) # windows默认控制台输出 gbk
print('stderr:' + res.stderr.read().decode('gbk')) # windows默认控制台输出 gbk

# stdout=subprocess.PIPE 标准输出放入管道
# stderr=subprocess.PIPE 错误输出放入管道
# 结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码

TCP的黏包现象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 在server端下发命令
import socket
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(('127.0.0.1',8090))
sk.listen()
print('服务器端口8090开始监听')

conn,addr = sk.accept()
print('有新的链接请求{}'.format(addr))

while 1:
cmd = input('cmd:>>>')
conn.send(cmd.encode('utf-8'))
ret = conn.recv(1024).decode('utf-8') # windows控制台是GBK
print(ret)

conn.close()
sk.close()

# 执行的命令
# 1. dir;ls
# 2. ipconfig
# 发生的问题:
# 象数据没有接收完全 或者 接收多了 这种现象就是“黏包”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 在client端接收命令并执行返回
import socket
import subprocess
sk = socket.socket()
sk.connect(('127.0.0.1',8090))

while True:
cmd = sk.recv(1024).decode('gbk')
res = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
# print('stdout:' + res.stdout.read().decode('gbk')) # windows默认控制台输出 gbk
# print('stderr:' + res.stderr.read().decode('gbk')) # windows默认控制台输出 gbk

# 这里的res得到的是gbk格式,需要解码整体转换成str
std_out = 'stdout: ' + (res.stdout.read()).decode('gbk')
std_err = 'stderr: ' + (res.stderr.read()).decode('gbk')
print(type(std_out),std_out)
print(type(std_err),std_err)

sk.send(std_out.encode('utf-8'))
sk.send(std_err.encode('utf-8'))

sk.close()

UDP的黏包现象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# server
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1',9999)
sk.bind(ip_port)
print('Bind UDP on 9999...')

data,addr = sk.recvfrom(1024)

while 1:
cmd = input('cmd:>>>')
if cmd == 'q':
break
sk.sendto(cmd.encode('utf-8'),addr)
data,addr = sk.recvfrom(10240)
print(data.decode('utf-8'))

sk.close()

# 操作:
# 1. ipconfig
# 2. dir

# udp
# udp不会黏包
# udp会丢包

# tcp
# tcp会黏包
# tcp不会丢包
# 内部优化算法 让整个程序发送数据和接收数据没有边界
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# client
import socket
import subprocess
sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1',9999)

sk.sendto('吃了吗?'.encode('utf-8'),ip_port)

while 1:
cmd,addr = sk.recvfrom(1024) # bytes
if cmd == 'q':
break
res = subprocess.Popen(cmd.decode('gbk'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
std_out = 'std_out:' + res.stdout.read().decode('gbk')
std_err = 'std_err:' + res.stderr.read().decode('gbk')
print(std_out)
print(std_err)

sk.sendto(std_out.encode('utf-8'),addr)
sk.sendto(std_err.encode('utf-8'),addr)

sk.close()

触发黏包

会发生黏包的两种情况

  1. 发送方的缓存机制
    发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据很小,会合到一起,产生粘包)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# server
# 1. server 先把数据给操作,操作系统再传给对面的操作系统
# 2. client 操作系统把接收到的消息 给client程序
# 3. 如果发送了10个数据 由于第一次服务端接收2个,还剩下8个
# 4. tcp协议在接收端有缓存机制,直到下次接收再给
# 5. 第一次没有全部接收,后面就全发给接收方
import socket
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(('127.0.0.1',8090))
sk.listen()
print('服务端8090端口开始监听...')

conn,addr = sk.accept()
ret = conn.recv(2)
ret2 = conn.recv(10)
print(ret) # b'he'
print(ret2) # b'llo,egg'
1
2
3
4
5
6
7
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',8090))

sk.send(b'hello,egg')

sk.close()
  1. 接收方的缓存机制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# server 
# 1. 优化算法,连续的小数据包会被合并
# 2. windows系统上 客户端在结束的时候会发送一个空消息 低版本会报错
# 3. 多个send 小的数据连在一起,会发生黏包现象,是Tcp协议内部的优化算法造成的
# 4. 连续使用了send引起的
import socket
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(('127.0.0.1',8090))
sk.listen()
print('服务端8090端口开始监听...')

conn,addr = sk.accept()
ret = conn.recv(12)
print(ret) # b'helloegg'
ret2 = conn.recv(12)
print(ret2)
1
2
3
4
5
6
7
8
9
10
11
# clietn 
import socket
import time
sk = socket.socket()
sk.connect(('127.0.0.1',8090))

sk.send(b'hello')
# time.sleep(3)
sk.send(b'egg')

sk.close()

总结黏包现象

黏包现象只发生在tcp协议中:

  1. 从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。
  2. 实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

解决黏包

发送消息长度

  1. 黏包问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个循环接收完所有数据。
  2. 存在的问题:
    程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# server
# 黏包的本质问题 — 你不知道到底要接收多大的数据
# 解决:
# 首先发送,这个数据到底有多大
# 再按照数据的长度,接收数据

import socket
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(('127.0.0.1',8090))
sk.listen()
print('服务端8090端口开始监听...')
conn,addr = sk.accept()
print('有新的客户端链接{}'.format(addr))

while True:
cmd = input('server: ').encode('gbk')
if cmd == 'q':
break
conn.send(cmd)
num = conn.recv(4) # 接收消息的长度
# print('接收消息长度:%d' %(int(num)))
print(num)
conn.send(b'ok') # 发送消息应答
ret = conn.recv(int(num)).decode('gbk') # 1024修改成要接收的数据长度
print(ret)
conn.close()
sk.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# client
import socket
import subprocess
sk = socket.socket()
sk.connect(('127.0.0.1',8090))
while True:
cmd = sk.recv(1024).decode('gbk')
res = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)

std_out = res.stdout.read()
std_err = res.stderr.read()

# print(type(std_out)) # <class 'bytes'>
# print(type(std_err))

print(str(len(std_out)+len(std_err)).encode('utf-8')) # 得到消息长度 b'455'
sk.send(str(len(std_out)+len(std_err)).encode('utf-8')) # 发送消息长度
# 发送一次消息的长度
sk.recv(4096) # 接收应答
sk.send(std_out)
sk.send(std_err)

sk.close()

进阶方法—使用struct模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# struct模块作用:把一个类型,如数字,转成固定长度的bytes
# 1、什么是固定长度的bytes
import struct
ret = struct.pack('i',20491) # i 代表int,即将要把一个数字转换成固定长度的bytes类型
print(len(ret),ret) # 4 b'\x01\x08\x00\x00' 太长的数据不够位数会有字母和符号代替,超过长度会报错

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

num = struct.unpack('i',ret)
print(type(num),num) # <class 'tuple'> (2049,)
print(num[0]) # 2049

# 2、为什么要转成固定长度的
# 发送数据的时候
# 客户端先发送长度,服务端先接收长度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# server
import socket
import struct

sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(('127.0.0.1',8090))
sk.listen()
print('服务端8090端口开始监听...')

conn,addr = sk.accept()
print('有新的客户端链接{}'.format(addr))

while True:
cmd = input('server:>>>').encode('gbk')
if cmd == 'q':
conn.send(b'bye')
break
conn.send(cmd)

# 接收消息长度 客户端使用struct固定传值4个bytes
num = conn.recv(4) # 4
num = struct.unpack('i',num)[0] # 消息长度大小
print(num)

ret = conn.recv(int(num)).decode('gbk')
print(ret)

conn.close()
sk.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# client
import socket
import subprocess
import struct

sk = socket.socket()
sk.connect(('127.0.0.1',8090))

while True:
cmd = sk.recv(1024).decode('gbk')
if cmd == 'q':
break
res = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)

std_out = res.stdout.read()
std_err = res.stderr.read()

len_num = len(std_out)+len(std_err)
num_by = struct.pack('i',len_num)
# print(type(num_by),num_by) # <class 'bytes'> b'\xc8\x01\x00\x00'

sk.send(num_by) # 发送固定4个字节,消息长度
sk.send(std_out)
sk.send(std_err)

sk.close()

使用struct解决黏包

借助struct模块,我们知道长度数字可以被转换成一个标准大小的4字节数字。因此可以利用这个特点来预先发送数据长度。

我们还可以把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个自己足够用了)

实现大文件上传和下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# server 接收端
import socket
import json
import struct

sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(('127.0.0.1',8090))
sk.listen()
print('服务端8090端口开始监听...')

buffer = 1024 # 按照服务器性能调整
conn,addr = sk.accept()
print('有新的客户端链接{}'.format(addr))

# 1. 接收报头长度
pack_head = conn.recv(4) # 接收报头
len_head = struct.unpack('i',pack_head)[0] # 使用struct.unpack得到报头长度
# print(len_head)

# 2. 接收数据报头
js_head = conn.recv(len_head).decode('utf-8') # 接收bytes类型的数据报文
js_head = json.loads(js_head) # json报文转字典
# print(js_head)

# 3. 获取文件长度和文件名称
file_size = js_head['file_size']
file_name = js_head['file_name']

# 4. 打开文件,接收数据
with open(file_name,mode='wb') as f:
while file_size:
print(file_size)
if file_size >= buffer:
context = conn.recv(buffer)
f.write(context)
file_size -= buffer
else:
context = conn.recv(buffer)
f.write(context)
break

conn.close()
sk.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# client 发送端
import socket
import os
import struct
import json

sk = socket.socket()
sk.connect(('127.0.0.1',8090))

# 发送文件 定制报头
# 发送流程:
# 1. 先发报头长度
# 2. 再发by_head报头
# 3. 发送报文

# 1. 组织数据字典
head = {
'file_name': '01 软件简介 软件分类.avi',
'file_path': 'D:\\',
'file_size': None
}

file_path = os.path.join(head['file_path'],head['file_name']) # 获取文件路径
# print(file_path)
file_size = os.path.getsize(file_path) # 获取文件大小
head['file_size'] = file_size
head['all_file_path'] = file_size
buffer = 4096

# 2. 数据报头长度
js_head = json.dumps(head,ensure_ascii=False)
# print(js_head)
by_head = js_head.encode('utf-8')
len_head = len(by_head)
pack_head = struct.pack('i',len_head)

# 3. 发送报头长度
sk.send(pack_head)

# 4. 发送数据报头
sk.send(by_head)

# 5. 打开文件发送文件
with open(file_path,mode='rb') as f:
while file_size:
print(file_size)
if file_size >= buffer:
context = f.read(buffer)
sk.send(context)
file_size -= buffer
else:
context = f.read(buffer)
sk.send(context)
break
sk.close()

自定制报头 发送数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# server
import socket
import struct
import json

sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(('127.0.0.1',8090))
sk.listen()
print('服务端8090端口开始监听...')

conn,adrr = sk.accept()
print('有新的客户端链接{}'.format(adrr))

while True:
cmd = input('server:>>>').encode('gbk')
conn.send(cmd)
if cmd == 'q':
break
len_head = conn.recv(4)
len_head = struct.unpack('i',len_head)[0]
print(int(len_head))

# 接收报头
by_head = conn.recv(int(len_head)).decode('utf-8')
head = json.loads(by_head)
print(head,type(head))

# 通过报头长度接收消息
res = conn.recv(head['info_size']).decode('gbk')
print(res)

conn.close()
sk.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# client
import socket
import struct
import json
import subprocess

# 组织报头
head = {
'info_size':None
}

sk = socket.socket()
sk.connect(('127.0.0.1',8090))

while True:
cmd = sk.recv(1024).decode('gbk')
if cmd == 'q':
break
res = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
std_out = res.stdout.read()
std_err = res.stderr.read()

# 先发报头长度
# 1. 将字典转成by类型
head['info_size'] = len(std_out)+len(std_err)
js_head = json.dumps(head,ensure_ascii=False)
by_head = js_head.encode('utf-8')

# 2. 使用struct将报头长度转成固定字节
len_head = len(by_head)
print(len_head)
pack_len = struct.pack('i',len_head)
# 3. 发送报头长度
sk.send(pack_len)
# 4. 发送报头
sk.send(by_head)
# 5. 发送信息
sk.send(std_out)
sk.send(std_err)

sk.close()