import
subprocess
res
= subprocess.Popen(
'
dir
'
,shell=True,stdout=subprocess.PIPE,stderr=
subprocess.PIPE)
print
(
'
Stdout:
'
,res.stdout.read().decode(
'
gbk
'
))
print
(
'
Stderr:
'
,res.stderr.read().decode(
'
gbk
'
))
PIPE把输出的东西装到一个'水管'里,如果在windows中的编码格式是gbk,执行结果:
Stdout: 驱动器 C 中的卷是 系统
卷的序列号是 85C0
-
669A
C:\Users\Administrator\PycharmProjects\Internet_program 的目录
2019/09/16 13:48
.
2019/09/16 13:48
..
2019/09/16 13:47
.idea
2019/09/16 13:46 21
Client1.py
2019/09/16 13:42
0 Client2.py
2019/09/16 13:48 207
Sever1.py
2019/09/16 01:41 70
time_test.py
2019/09/14 23:51
venv
4 个文件 298
字节
4 个目录 45,863,636,992
可用字节
Stderr:
在这里也可以使用os.popen()但是它会不管正确和错误的结果都放在一起,而用subprocess能够分别拿到正确和错误的信息
基于TCP实现的黏包
Sever:
import
socket
sk
=
socket.socket()
sk.bind((
'
127.0.0.1
'
,8092
))
sk.listen()
conn,addr
=
sk.accept()
while
True:
cmd
= input(
'
<<<
'
)
conn.send(cmd.encode(
'
gbk
'
))
ret
= conn.recv(1024).decode(
'
gbk
'
)
print
(ret)
conn.close()
sk.close()
Client:
import
socket
import
subprocess
sk
=
socket.socket()
sk.connect((
'
127.0.0.1
'
,8092
))
while
True:
cmd
= sk.recv(1024).decode(
'
gbk
'
)
ret
= subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=
subprocess.PIPE)
std_out
=
'
stdout:
'
+ (ret.stdout.read()).decode(
'
gbk
'
)
std_err
=
'
stderr:
'
+ (ret.stderr.read()).decode(
'
gbk
'
)
print
(std_out)
print
(std_err)
sk.send(std_out.encode(
'
gbk
'
))
sk.send(std_err.encode(
'
gbk
'
))
sk.close()
执行结果:
Sever:
<<<
dir;ls
stdout: 驱动器 C 中的卷是 系统
卷的序列号是 85C0
-
669A
C:\Users\Administrator\PycharmProjects\Internet_program 的目录
<<<
ipconfig
stderr:找不到文件
<<<
Client:
stdout: 驱动器 C 中的卷是 系统
卷的序列号是 85C0
-
669A
C:\Users\Administrator\PycharmProjects\Internet_program 的目录
stderr:找不到文件
stdout:
Windows IP 配置
以太网适配器 Bluetooth 网络连接
2
:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
以太网适配器 本地连接:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
无线局域网适配器 无线网络连接:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::8c6:36a9:6fa6:
8018%14
IPv4 地址 . . . . . . . . . . . . :
192.168.43.216
子网掩码 . . . . . . . . . . . . :
255.255.255.0
默认网关. . . . . . . . . . . . . :
192.168.43.1
隧道适配器 本地连接
* 3
:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
stderr:
当我们在sever端输入dir;ls命令时,只有stdout的结果跑出来,而当我们输入ipconfig这个命令时,系统将上一次dir;ls未执行完的stderr的结果给跑出来。像这样没有接受完全或者接受多了的就是黏包现象。
TCP会有黏包现象但是它不丢包。
基于UDP实现的黏包
Sever:
import
socket
sk
= socket.socket(type=
socket.SOCK_DGRAM)
sk.bind((
'
127.0.0.1
'
,8092
))
msg,addr
= sk.recvfrom(10240
)
while
1
:
cmd
= input(
'
<<<
'
)
if
cmd ==
'
q
'
:
break
sk.sendto(cmd.encode(
'
gbk
'
),addr)
msg,addr
= sk.recvfrom(10240
)
print
(msg.decode(
'
gbk
'
))
sk.close()
Client:
import
socket
import
subprocess
sk
= socket.socket(type=
socket.SOCK_DGRAM)
addr
= (
'
127.0.0.1
'
,8092
)
sk.sendto(
'
Start
'
.encode(
'
utf-8
'
),addr)
while
1
:
cmd,addr
= sk.recvfrom(10240
)
ret
= subprocess.Popen(cmd.decode(
'
gbk
'
),shell=True,stderr=subprocess.PIPE,stdout=
subprocess.PIPE)
std_out
=
'
Stdout:
'
+ (ret.stdout.read()).decode(
'
gbk
'
)
std_err
=
'
Stderr:
'
+ (ret.stderr.read()).decode(
'
gbk
'
)
print
(std_out)
print
(std_err)
sk.sendto(std_out.encode(
'
gbk
'
),addr)
sk.sendto(std_err.encode(
'
gbk
'
),addr)
sk.close()
执行结果:
Sever:
<<<
dir;ls
Stdout: 驱动器 C 中的卷是 系统
卷的序列号是 85C0
-
669A
C:\Users\Administrator\PycharmProjects\Internet_program 的目录
<<<
dir
Stderr:找不到文件
<<<
ipconfig
Stdout: 驱动器 C 中的卷是 系统
卷的序列号是 85C0
-
669A
C:\Users\Administrator\PycharmProjects\Internet_program 的目录
2019/09/16 14:43
.
2019/09/16 14:43
..
2019/09/16 14:37
.idea
2019/09/16 14:43 553
Client1.py
2019/09/16 13:42
0 Client2.py
2019/09/16 14:43 306
Sever1.py
2019/09/16 01:41 70
time_test.py
2019/09/14 23:51
venv
4 个文件 929
字节
4 个目录 45,855,449,088
可用字节
<<<
pwd
Stderr:
<<<
ip
Stdout:
Windows IP 配置
以太网适配器 Bluetooth 网络连接
2
:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
以太网适配器 本地连接:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
无线局域网适配器 无线网络连接:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::8c6:36a9:6fa6:
8018%14
IPv4 地址 . . . . . . . . . . . . :
192.168.43.216
子网掩码 . . . . . . . . . . . . :
255.255.255.0
默认网关. . . . . . . . . . . . . :
192.168.43.1
隧道适配器 本地连接
* 3
:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
<<<
Client:
Stdout: 驱动器 C 中的卷是 系统
卷的序列号是 85C0
-
669A
C:\Users\Administrator\PycharmProjects\Internet_program 的目录
Stderr:找不到文件
Stdout: 驱动器 C 中的卷是 系统
卷的序列号是 85C0
-
669A
C:\Users\Administrator\PycharmProjects\Internet_program 的目录
2019/09/16 14:43
.
2019/09/16 14:43
..
2019/09/16 14:37
.idea
2019/09/16 14:43 553
Client1.py
2019/09/16 13:42
0 Client2.py
2019/09/16 14:43 306
Sever1.py
2019/09/16 01:41 70
time_test.py
2019/09/14 23:51
venv
4 个文件 929
字节
4 个目录 45,855,449,088
可用字节
Stderr:
Stdout:
Windows IP 配置
以太网适配器 Bluetooth 网络连接
2
:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
以太网适配器 本地连接:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
无线局域网适配器 无线网络连接:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::8c6:36a9:6fa6:
8018%14
IPv4 地址 . . . . . . . . . . . . :
192.168.43.216
子网掩码 . . . . . . . . . . . . :
255.255.255.0
默认网关. . . . . . . . . . . . . :
192.168.43.1
隧道适配器 本地连接
* 3
:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
Stderr:
Stdout:
Stderr:
'
pwd
'
不是内部或外部命令,也不是可运行的程序
或批处理文件。
Stdout:
Stderr:
'
ip
'
不是内部或外部命令,也不是可运行的程序
或批处理文件。
可以看出UDP不会有黏包现象,会产生丢包现象,没发完就不发了,不完整也不可靠。
黏包成因
TCP协议的数据传送
拆包机制
当发送端缓冲区的长度大于网卡的MTU时,TCP会将这次发送的数据拆成几个数据包发送出去。MTU是Maximum Transmission Unit的缩写,意思是网络上传送最大数据包,MTU是字节单位,大部分网络设备的MTU都是1500. 如果本机的MTU比网关的MTU大,大的数据包就会被拆开来传送,这样会产生很多数据包碎片,增加丢包率,降低网络速度。
在正常情况下它的拆包可理解为:
面向流的通信特点和Nagle算法
TCP(transport control protocol,传输控制协议),是面向连接的,面向流的,提供高可靠性的服务。收发两端(客户端和服务端)都要有一一成对的socket,因此发送端为了将多个发往接收端的包,更有效地发往对方,使用了优化算法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样接收端就难于分辨出来了,必须提供科学的拆包机制。即面向流的通信是无消息保护边界的。
对于空消息:TCP是基于数据流的,于是收发消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而UDP协议是基于数据报的,即便是你输入的是空内容(直接回车),也可以被发送,UDP协议会帮你封装上消息然后发出去。
可靠黏包的TCP协议:TCP协议数据不会丢,没有收完包,就会下次接收,会继续上次继续接受。
基于tcp协议特点的黏包现象成因
当我们在socket服务端发送值1、2,然后根据优化算法,它会把1先放到这个缓存当中等一等,然后再把2一起封装起来,然后再发出去,因此我们看到的就是黏包现象
这种现象的表面现象是两个send太近且发送的消息太短
发送端可以使1K1K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据。也就是说,应用程序所看到的数据是一个整体,或者说是一个流(stream),一条消息有多少字节对应程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现黏包问题的原因。而UDP协议是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。
解决黏包的方法
解决方案一:
Sever:
import
socket
sk
=
socket.socket()
sk.bind((
'
127.0.0.1
'
,8080
))
sk.listen()
conn,addr
=
sk.accept()
while
True:
cmd
= input(
'
<<<
'
)
if
cmd ==
'
q
'
:
conn.send(b
'
q
'
)
break
conn.send(cmd.encode(
'
gbk
'
))
num
= conn.recv(1024).decode(
'
utf-8
'
)
conn.send(b
'
ok
'
)
res
= conn.recv(int(num)).decode(
'
gbk
'
)
print
(res)
conn.close()
sk.close()
Client:
import
socket
import
subprocess
sk
=
socket.socket()
sk.connect((
'
127.0.0.1
'
,8080
))
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()
sk.send(str(len(std_out)
+ len(std_err)).encode(
'
utf-8
'
))
sk.recv(
1024
)
print
(
'
Stdout:
'
+ std_out.decode(
'
gbk
'
))
print
(
'
Stderr:
'
+ std_err.decode(
'
gbk
'
))
sk.send(std_out)
sk.send(std_err)
sk.close()
执行结果:
Sever:
<<<
dir
驱动器 C 中的卷是 系统
卷的序列号是 85C0
-
669A
C:\Users\Administrator\PycharmProjects\Internet_program 的目录
2019/09/17 15:33
.
2019/09/17 15:33
..
2019/09/17 15:31
.idea
2019/09/17 15:33 623
Client1.py
2019/09/16 13:42
0 Client2.py
2019/09/17 15:21 389
Sever1.py
2019/09/16 01:41 70
time_test.py
2019/09/14 23:51
venv
4 个文件 1,082
字节
4 个目录 45,031,833,600
可用字节
<<<
ls
'
ls
'
不是内部或外部命令,也不是可运行的程序
或批处理文件。
<<<
ipconfig
Windows IP 配置
以太网适配器 Bluetooth 网络连接
2
:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
以太网适配器 本地连接:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
无线局域网适配器 无线网络连接:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::8c6:36a9:6fa6:
8018%14
IPv4 地址 . . . . . . . . . . . . :
192.168.43.216
子网掩码 . . . . . . . . . . . . :
255.255.255.0
默认网关. . . . . . . . . . . . . :
192.168.43.1
隧道适配器 本地连接
* 3
:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
<<<
Client:
Stdout: 驱动器 C 中的卷是 系统
卷的序列号是 85C0
-
669A
C:\Users\Administrator\PycharmProjects\Internet_program 的目录
2019/09/17 15:33
.
2019/09/17 15:33
..
2019/09/17 15:31
.idea
2019/09/17 15:33 623
Client1.py
2019/09/16 13:42
0 Client2.py
2019/09/17 15:21 389
Sever1.py
2019/09/16 01:41 70
time_test.py
2019/09/14 23:51
venv
4 个文件 1,082
字节
4 个目录 45,031,833,600
可用字节
Stderr:
Stdout:
Stderr:
'
ls
'
不是内部或外部命令,也不是可运行的程序
或批处理文件。
Stdout:
Windows IP 配置
以太网适配器 Bluetooth 网络连接
2
:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
以太网适配器 本地连接:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
无线局域网适配器 无线网络连接:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::8c6:36a9:6fa6:
8018%14
IPv4 地址 . . . . . . . . . . . . :
192.168.43.216
子网掩码 . . . . . . . . . . . . :
255.255.255.0
默认网关. . . . . . . . . . . . . :
192.168.43.1
隧道适配器 本地连接
* 3
:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
Stderr:
这种写法的好处就是能确定Sever到底要接受多少数据,不好的地方就是多了一次交互
解决方案二: 使用struct模块
用struct模块我们能把一个数据类型转换成固定长度的bytes
这里以数字类型举例,'i'代表int类型:
import
struct
print
(struct.pack(
'
i
'
,2048),len(struct.pack(
'
i
'
,2048)))
#
b'\x00\x08\x00\x00' 4
print
(struct.pack(
'
i
'
,204800),len(struct.pack(
'
i
'
,204800)))
#
b'\x00 \x03\x00' 4
print
(struct.pack(
'
i
'
,2048000),len(struct.pack(
'
i
'
,2048000)))
#
b'\x00@\x1f\x00' 4
当后面的数值戳过一定范围的时候程序就会报错
Sever:
import
socket
import
struct
sk
=
socket.socket()
sk.bind((
'
127.0.0.1
'
,8080
))
sk.listen()
conn,addr
=
sk.accept()
while
True:
cmd
= input(
'
<<<
'
)
if
cmd ==
'
q
'
:
conn.send(b
'
q
'
)
break
conn.send(cmd.encode(
'
gbk
'
))
num
= conn.recv(4
)
num
= struct.unpack(
'
i
'
,num)[0]
res
= conn.recv(int(num)).decode(
'
gbk
'
)
print
(res)
conn.close()
sk.close()
Client:
import
socket
import
subprocess
import
struct
sk
=
socket.socket()
sk.connect((
'
127.0.0.1
'
,8080
))
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
(
'
Stdout:
'
+ std_out.decode(
'
gbk
'
))
print
(
'
Stderr:
'
+ std_err.decode(
'
gbk
'
))
sk.send(num_by)
sk.send(std_out)
sk.send(std_err)
sk.close()
执行结果:
<<<
dir
驱动器 C 中的卷是 系统
卷的序列号是 85C0
-
669A
C:\Users\Administrator\PycharmProjects\Internet_program 的目录
2019/09/17 16:25
.
2019/09/17 16:25
..
2019/09/17 16:23
.idea
2019/09/17 16:25 659
Client1.py
2019/09/16 13:42
0 Client2.py
2019/09/17 16:22 400
Sever1.py
2019/09/17 16:08 288
time_test.py
2019/09/14 23:51
venv
4 个文件 1,347
字节
4 个目录 45,025,951,744
可用字节
<<<
configip
'
configip
'
不是内部或外部命令,也不是可运行的程序
或批处理文件。
<<<
ipconfig
Windows IP 配置
以太网适配器 Bluetooth 网络连接
2
:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
以太网适配器 本地连接:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
无线局域网适配器 无线网络连接:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::8c6:36a9:6fa6:
8018%14
IPv4 地址 . . . . . . . . . . . . :
192.168.43.216
子网掩码 . . . . . . . . . . . . :
255.255.255.0
默认网关. . . . . . . . . . . . . :
192.168.43.1
隧道适配器 本地连接
* 3
:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
<<<
Stdout: 驱动器 C 中的卷是 系统
卷的序列号是 85C0
-
669A
C:\Users\Administrator\PycharmProjects\Internet_program 的目录
2019/09/17 16:25
.
2019/09/17 16:25
..
2019/09/17 16:23
.idea
2019/09/17 16:25 659
Client1.py
2019/09/16 13:42
0 Client2.py
2019/09/17 16:22 400
Sever1.py
2019/09/17 16:08 288
time_test.py
2019/09/14 23:51
venv
4 个文件 1,347
字节
4 个目录 45,025,951,744
可用字节
Stderr:
Stdout:
Stderr:
'
configip
'
不是内部或外部命令,也不是可运行的程序
或批处理文件。
Stdout:
Windows IP 配置
以太网适配器 Bluetooth 网络连接
2
:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
以太网适配器 本地连接:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
无线局域网适配器 无线网络连接:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::8c6:36a9:6fa6:
8018%14
IPv4 地址 . . . . . . . . . . . . :
192.168.43.216
子网掩码 . . . . . . . . . . . . :
255.255.255.0
默认网关. . . . . . . . . . . . . :
192.168.43.1
隧道适配器 本地连接
* 3
:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
Stderr:
实现一个大文件的传输和下载
当我们在网络上传输所有数据时,这些数据都叫数据包,数据包里的所有数据都叫报文,报文里不止有你的数据还有IP地址、MAC地址、端口号等,所有的报文都有报头,这是由协议规定的。所有在网络上传播数据包的协议里都有一个报头。什么时候需要自己定制报文?比如说复杂的应用上就会应用到、传输文件的时候(文件名、文件大小、文件类型、存储路径等)...
其实在网络传输的过程当中处处有协议,协议就是一堆报头和报文(都由字节组成)。
Sever:
import
socket
import
struct
import
json
buffer
= 1024
sk
=
socket.socket()
sk.bind((
'
127.0.0.1
'
,8080
))
sk.listen()
conn,addr
=
sk.accept()
head_len
= conn.recv(4
)
struct.unpack(
'
i
'
,head_len)[0]
json_head
= conn.recv(head_len).decode(
'
utf-8
'
)
head
=
json.loads(json_head)
file_size
= head[
'
fileSize
'
]
with open(r
'
dir\%s
'
%head[
'
fileName
'
],
'
wb
'
) as f:
while
file_size:
if
file_size >=
buffer:
content
=
conn.recv(buffer)
f.write(content)
file_size
-=
buffer
else
:
content
=
conn.recv(buffer)
f.write(content)
break
conn.close()
sk.close()
Client:
import
socket
import
struct
import
os
import
json
sk
=
socket.socket()
sk.connect((
'
127.0.0.1
'
,8080
))
buffer
= 1024
head
= {
'
filePath
'
: r
'
C:\Users\Administrator\Desktop\专题\海報資料夾\专题海报
'
,
'
fileName
'
: r
'
专题海报
'
,
'
fileSize
'
: None}
file_path
= os.path.join(head[
'
filePath
'
],head[
'
fileName
'
])
file_size
=
os.path.getsize(file_path)
head[
'
fileSize
'
] =
file_size
json_head
= json.dumps(head)
#
字典转成字典
bytes_head = json_head.encode(
'
utf-8
'
)
#
字符串转bytes
head_len
= len(bytes_head)
#
报头的长度
pack_len = struct.pack(
'
i
'
,head_len)
sk.send(pack_len)
#
先发报头的长度
sk.send(bytes_head)
#
再发bytes类型的报头
with open(r
'
dir\%s
'
%file_path,
'
rb
'
) as f:
while
file_size:
if
file_size >=
buffer:
content
= f.read(buffer)
#
每次文件读出的内容
sk.send(content)
file_size
-=
buffer
else
:
content
=
f.read(file_size)
sk.send(content)
break
sk.close()

