Python3学习之路~8.2 socket简单实例 实现ssh 发送大量数据

时间:2022-04-27 18:01:03

实例1:

利用socket模拟客户端和服务器端各自收发一次数据:

#Author:Zheng Na

# 客户端

import socket

# 声明socket类型,同时生成socket连接对象
client = socket.socket() # 默认参数family=AF_INET(表示地址簇为IPV4),type=SOCK_STREAM(表示socket类型为TCP) client.connect(('localhost',6969)) client.send(b"hello world") #注意:Python 2.x中可以发送str类型和bytes类型数据,但是Python3.x中只能发送bytes类型数据 data = client.recv(1024) # 接收1024字节
print("recv from server: ",data) client.close()

socket_client.py

#Author:Zheng Na

# 服务器端

import socket

server = socket.socket() # 声明socket类型,同时生成socket连接对象

server.bind(('localhost',6969)) # 绑定要监听的端口

server.listen() # 监听
print("我要开始等信息了") # conn就是客户端连接过来而在服务器端为其生成的一个连接实例
conn,addr = server.accept() # 等待信息传送
print("conn: ",conn)
print("addr: ",addr)
print("信息来了") data = conn.recv(1024)
print("recv from client: ",data) conn.send(data.upper()) server.close()

socket_server.py

首先运行socket_server.py,结果显示:

我要开始等信息了

说明此时程序执行到conn,addr = server.accept()卡住,服务器端正在等待信息传送。

接着运行socket_client.py,结果显示:

recv from server:  b'HELLO WORLD'

说明客户端接收到了服务器端发送的数据。

再重新查看socket_server.py运行结果:

我要开始等信息了
conn: <socket.socket fd=332, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 6969), raddr=('127.0.0.1', 54153)>
addr: ('127.0.0.1', 54153)
信息来了
recv from client: b'hello world'

说明服务器端接收到了客户端发送的数据。

注意:Python 2.x中可以发送str类型和bytes类型数据,但是Python3.x中只能发送bytes类型数据。

将英文转化成bytes只需要在前面加上b即可,比如:client.send(b"Hello World");

将中文转化成bytes只需要编码一下即可,比如:client.send("我要发送中文".encode("UTF-8")),当然,如果在服务器端需要读取发送的数据,解码即可,比如:data.decode("UTF-8")。

实例2:

好了,接下来我们希望客户端可以一直向服务器端发送数据,修改代码如下:

#Author:Zheng Na

# 客户端

import socket

client = socket.socket()
client.connect(('localhost',6969)) while True:
msg = input(">>: ").strip()
client.send(msg.encode("UTF-8")) data = client.recv(1024) # 接收1024字节
print("recv from server: ",data) client.close()

socket_client2.py

#Author:Zheng Na

# 服务器端

import socket

server = socket.socket()
server.bind(('localhost',6969)) server.listen() # 监听
print("我要开始等信息了") conn, addr = server.accept()
print("信息来了") while True:
data = conn.recv(1024)
print("recv from client: ",data.decode("UTF-8"))
conn.send(data.upper()) server.close()

socket_server2.py

在Windows下的PyCharm中,先点击运行socket_server2.py,结果显示:

我要开始等信息了

接着运行socket_client2.py,输入1,回车,2,回车,3,回车。结果显示:

>>: 1
recv from server: b''
>>: 2
recv from server: b''
>>: 3
recv from server: b''
>>:

说明客户端接收到了服务器端发送的数据。

再重新查看socket_server2.py运行结果:

我要开始等信息了
信息来了
recv from client: 1
recv from client: 2
recv from client: 3

说明服务器端接收到了客户端发送的数据。

最后我们关闭客户端程序,查看服务器端,结果显示:

Traceback (most recent call last):
File "D:/python-study/s14/Day08/socket_server2.py", line 17, in <module>
data = conn.recv(1024)
ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。

实例3:

现在我们按照同样的步骤,把这2段代码在linux执行一下试试,linux下安装Python3.6,以后的实例均在linux环境下测试。

结果发现:前几步结果与在Windows下的结果都一致,但是最后一步,在我们关闭客户端程序后,查看服务器端,结果显示服务器端进入了死循环:

我要开始等信息了
信息来了
recv from client: 1
recv from client: 2
recv from client: 3
recv from client:
recv from client:
recv from client:
recv from client:
...

说明当客户端关闭后,服务器认为自己一直收到了空字符。在linux和Mac上,Python2和Python3结果都是这样的。

为了解决这个问题,我们将socket_server2.py修改一下代码:加个判断条件,如果接收到的数据为空,就自动跳出循环。

#Author:Zheng Na

# 服务器端

import socket

server = socket.socket()
server.bind(('localhost',6969)) server.listen() # 监听
print("我要开始等信息了") conn, addr = server.accept()
print("信息来了") while True:
data = conn.recv(1024) if not data:
print("client is lost...")
break print("recv from client: ",data.decode("UTF-8")) conn.send(data.upper()) server.close()

socket_server2.1.py

重新将前面的步骤执行一遍,最后一步,在我们关闭客户端程序后,查看服务器端,结果显示服务器端已自动关闭:

[root@hadoop my-test-files]# python3 socket_server2.1.py
我要开始等信息了
信息来了
recv from client: 1
recv from client: 2
recv from client: 3
client is lost...
[root@hadoop my-test-files]#

现在我们的程序实现了:客户端可以一直向服务器端发送数据,当客户端关闭后,服务器端也自动关闭了。

实例4:

那么如何让客户端断开后,服务器端仍然持续运行,等待另一个客户端连接呢?修改代码如下(在服务器端再加一个while True,使其一直在等待下一次连接):

#Author:Zheng Na

# 服务器端

import socket

server = socket.socket()
server.bind(('localhost',6969)) server.listen() # 监听
print("我要开始等信息了") while True:
conn, addr = server.accept()
print("信息来了") while True:
data = conn.recv(1024) if not data:
print("client is lost...wait next client to connect...")
break print("recv from client: ",data.decode("UTF-8")) conn.send(data.upper()) server.close()

socket_server2.2.py

这段代码中,里面的while True使客户端可以一直跟服务器端发送数据,外面的while True使当客户端断开后,服务器端能够保持连接不断,等待下一个客户端连接。

重新将前面的步骤执行一遍,最后一步,在我们关闭客户端程序后,查看服务器端,结果显示服务器端未关闭:

[root@hadoop my-test-files]# python3 socket_server2.2.py
我要开始等信息了
信息来了
recv from client: 1
recv from client: 2
recv from client: 3
client is lost...wait next client to connect...

此时再执行一遍客户端程序,相当于重新打开一个客户端,输入a,回车,b,回车,c,回车,Ctrl+C关闭客户端。查看服务端结果显示:

[root@hadoop my-test-files]# python3 socket_server2.2.py
我要开始等信息了
信息来了
recv from client: 1
recv from client: 2
recv from client: 3
client is lost...wait next client to connect...
信息来了
recv from client: a
recv from client: b
recv from client: c
client is lost...wait next client to connect...

实例5:

server.listen()可以加参数,一般不超过10。比如server.listen(5)最常见,意思是,程序最多可以挂起5个客户端。即当一个客户端在通信时,最多可以有5个客户端在排队。但是在目前的代码里没法测试,等我们学异步的时候再测吧。

实例6:

上述代码还有缺陷,那就是客户端不能向服务器端发送空字符串,一旦客户端向服务器端发送空字符串就会卡住。

可以在代码中客户端向服务器端发送数据之前加上如下代码解决上述问题

if len(msg) == :continue # 如果为空,重新输入

即将客户端代码改为如下

#Author:Zheng Na

# 客户端

import socket

# 声明socket类型,同时生成socket连接对象
client = socket.socket() # 默认参数family=AF_INET(表示地址簇为IPV4),type=SOCK_STREAM(表示socket类型为TCP) client.connect(('localhost',6969)) while True:
msg = input(">>: ").strip()
if len(msg) == 0:continue # 如果为空,重新输入
client.send(msg.encode("UTF-8")) data = client.recv(1024) # 接收1024字节
print("recv from server: ",data) client.close()

socket_client2.1.py

实例7:socket实现简单的ssh

接下来我们尝试模拟ssh客户端,即连到一台机器上执行命令返回结果。

修改代码如下

#encoding:utf-8 # python2.x
#Author:Zheng Na # 客户端 import socket # 声明socket类型,同时生成socket连接对象
client = socket.socket() # 默认参数family=AF_INET(表示地址簇为IPV4),type=SOCK_STREAM(表示socket类型为TCP) client.connect(('localhost',6969)) while True:
msg = input(">>: ").strip() # python3.x
#msg = raw_input(">>: ").strip() # python2.x if len(msg) == 0:continue # 如果为空,重新输入
client.send(msg.encode("UTF-8")) data = client.recv(1024) # 接收1024字节 print(data.decode()) # python3.x
#print(data) # python2.x client.close()

socket_client2.2-ssh.py

#encoding:utf-8 # python2.x
#Author:Zheng Na # 服务器端 import os
import socket server = socket.socket()
server.bind(('localhost',6969)) server.listen(5) # 监听
print("我要开始等信息了") while True:
conn, addr = server.accept()
print("信息来了") while True:
data = conn.recv(1024) if not data:
print("client is lost...wait next client to connect...")
break print("recv from client: ",data.decode("UTF-8")) # 注意:popen()可以执行shell命令,并读取此命令的返回值
res = os.popen(data.decode("UTF-8")).read() # python3.x
#res = os.popen(data).read() # python2.x conn.send(res.encode("UTF-8")) # python3.x 只能发送bytes
#conn.send(res) # python2.x 发送str
server.close()

socket_server2.3-ssh.py

服务器端先启动,当客户端启动后输入命令df,输出结果如下

[root@hadoop my-test-files]# python3 socket_client2.2.py
>>: df
文件系统 1K-块 已用 可用 已用% 挂载点
/dev/mapper/cl_linux-root 17811456 7000392 10811064 40% /
devtmpfs 922660 0 922660 0% /dev
tmpfs 933632 0 933632 0% /dev/shm
tmpfs 933632 8764 924868 1% /run
tmpfs 933632 0 933632 0% /sys/fs/cgroup
/dev/sda1 1038336 141564 896772 14% /boot
tmpfs 186728 0 186728 0% /run/user/0

df

very cool , 这样我们就做了一个简单的ssh , 但多试几条命令你就会发现,上面的程序有以下几个问题。

1.不能执行top等类似的 会持续输出的命令,这是因为,服务器端在收到客户端指令后,会一次性通过os.popen执行,并得到结果后返回给客户,但top这样的命令用os.popen执行你会发现永远都不会结束,所以客户端也永远拿不到返回。(真正的ssh是通过select 异步等模块实现的,我们以后会涉及)

[root@hadoop my-test-files]# python3 socket_client2.2.py
>>: top

top 卡住

2.不能执行像cd这种没有返回的指令, 因为客户端每发送一条指令,就会通过client.recv(1024)等待接收服务器端的返回结果,但是cd命令没有结果 ,服务器端调用conn.send(data)时是不会发送数据给客户端的。 所以客户端就会一直等着,等到天荒地老,结果就卡死了。解决的办法是,在服务器端判断命令的执行返回结果的长度,如果结果为空,就自己加个结果返回给客户端,如写上"cmd exec success, has no output."

        if len(res)==:
res = "cmd exec success,has not output!"

3.如果执行的命令返回结果的数据量比较大,会发现,结果返回不全,在客户端上再执行一条命令,结果返回的还是上一条命令的后半段的执行结果,这是为什么呢?这是因为,我们的客户写client.recv(1024), 即客户端一次最多只接收1024个字节,如果服务器端返回的数据是2000字节,那有至少9百多字节是客户端第一次接收不了的,那怎么办呢,服务器端此时不能把数据直接扔了呀,so它会暂时存在服务器的io发送缓冲区里,等客户端下次再接收数据的时候再发送给客户端。 这就是为什么客户端执行第2条命令时,却接收到了第一条命令的结果的原因。

比如输入top -bn 1命令,由于此命令的输出非常大,超过1024字节,而代码限制了输出不能超过1024字节,导致输出只能为1024字节,并且下次输入别的命令是会显示此命令结果剩余未输出的部分。这就相当于服务器把命令的结果放在一个缓冲区里,排队输出。

>>: top -bn 1
top - 17:02:36 up 5:19, 3 users, load average: 0.00, 0.01, 0.05
Tasks: 96 total, 2 running, 92 sleeping, 2 stopped, 0 zombie
%Cpu(s): 0.1 us, 0.1 sy, 0.0 ni, 99.6 id, 0.2 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 1867268 total, 100748 free, 1583232 used, 183288 buff/cache
KiB Swap: 2097148 total, 2097148 free, 0 used. 101496 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
3971 root 20 0 157620 2044 1516 R 0.7 0.1 0:01.28 top
1 root 20 0 125124 3604 2428 S 0.0 0.2 0:02.94 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd
3 root 20 0 0 0 0 S 0.0 0.0 0:00.86 ksoftirqd/0
6 root 20 0 0 0 0 S 0.0 0.0 0:01.70 kworker/u256:0
7 root rt 0 0 0 0 S 0.0 0.0 0:00.00 migration/0
8 root 20 0 0 0 0 S 0.0 0.0 0:00.00 rcu_bh
9 root 20 0 0
>>: df
0 0 R 0.0 0.0 0:06.94 rcu_sched
10 root rt 0 0 0 0 S 0.0 0.0 1:08.00 watchdog/0
12 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 khelper
13 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kdevtmpfs
14 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 netns
15 root 20 0 0 0 0 S 0.0 0.0 0:00.00 khungtaskd
16 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 writeback
17 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kintegrityd
18 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 bioset
19 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kblockd
20 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 md
26 root 20 0 0 0 0 S 0.0 0.0 0:00.01 kswapd0
27 root 25 5 0 0 0 S 0.0 0.0 0:00.00 ksmd
28 root 39 19 0 0 0 S 0.0 0.0 0:05.56
>>: df
khugepaged
29 root 20 0 0 0 0 S 0.0 0.0 0:00.00 fsnotify_mark
30 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 crypto
38 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kthrotld
40 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kmpath_rdacd
41 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kpsmoused
43 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 ipv6_addrconf
62 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 deferwq
94 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kauditd
275 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 ata_sff
276 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 mpt_poll_0
277 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 mpt/0
285 root 20 0 0 0 0 S 0.0 0.0 0:00.00 scsi_eh_0
286 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 scsi_tmf_0
>>: df 287 root 20 0 0 0 0 S 0.0 0.0 0:00.01 scsi_eh_1
290 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 scsi_tmf_1
292 root 20 0 0 0 0 S 0.0 0.0 0:00.00 scsi_eh_2
294 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 scsi_tmf_2
295 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 ttm_swap
368 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kdmflush
369 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 bioset
378 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kdmflush
379 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 bioset
394 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 xfsalloc
395 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 xfs_mru_cache
396 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 xfs-buf/dm-0
397 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 xfs-data/dm-0
398
>>:

top -bn 1 输出不全

为了解决这个问题,我直接在客户端把client.recv(1024)改大一点,比如将客户端代码修改代码为:

data = client.recv(1073741824) # 接收1G字节
#encoding:utf-8 # python2.x
#Author:Zheng Na # 客户端 import socket # 声明socket类型,同时生成socket连接对象
client = socket.socket() # 默认参数family=AF_INET(表示地址簇为IPV4),type=SOCK_STREAM(表示socket类型为TCP) client.connect(('localhost',6969)) while True:
msg = input(">>: ").strip() # python3.x
#msg = raw_input(">>: ").strip() # python2.x if len(msg) == 0:continue # 如果为空,重新输入
client.send(msg.encode("UTF-8")) data = client.recv(1073741824) # 接收1G数据 print(data.decode()) # python3.x
#print(data) # python2.x client.close() socket_client2.3.py

socket_client2.3-ssh.py

修改过后,top -bn 1命令输出如下

[root@hadoop my-test-files]# python3 socket_client2.3.py
>>: top -bn 1
top - 17:45:02 up 6:01, 3 users, load average: 0.00, 0.01, 0.05
Tasks: 95 total, 2 running, 93 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.1 us, 0.1 sy, 0.0 ni, 99.6 id, 0.2 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 1867268 total, 99452 free, 1584028 used, 183788 buff/cache
KiB Swap: 2097148 total, 2097148 free, 0 used. 100288 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
9 root 20 0 0 0 0 S 0.1 0.0 0:06.97 rcu_sched
1 root 20 0 125124 3604 2428 S 0.0 0.2 0:03.11 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd
3 root 20 0 0 0 0 S 0.0 0.0 0:00.88 ksoftirqd/0
6 root 20 0 0 0 0 S 0.0 0.0 0:01.70 kworker/u256:0
7 root rt 0 0 0 0 S 0.0 0.0 0:00.00 migration/0
8 root 20 0 0 0 0 S 0.0 0.0 0:00.00 rcu_bh
10 root rt 0 0 0 0 S 0.0 0.0 1:11.38 watchdog/0
12 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 khelper
13 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kdevtmpfs
14 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 netns
15 root 20 0 0 0 0 S 0.0 0.0 0:00.00 khungtaskd
16 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 writeback
17 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kintegrityd
18 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 bioset
19 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kblockd
20 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 md
26 root 20 0 0 0 0 S 0.0 0.0 0:00.01 kswapd0
27 root 25 5 0 0 0 S 0.0 0.0 0:00.00 ksmd
28 root 39 19 0 0 0 S 0.0 0.0 0:05.57 khugepaged
29 root 20 0 0 0 0 S 0.0 0.0 0:00.00 fsnotify_mark
30 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 crypto
38 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kthrotld
40 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kmpath_rdacd
41 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kpsmoused
43 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 ipv6_addrconf
62 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 deferwq
94 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kauditd
275 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 ata_sff
276 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 mpt_poll_0
277 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 mpt/0
285 root 20 0 0 0 0 S 0.0 0.0 0:00.00 scsi_eh_0
286 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 scsi_tmf_0
287 root 20 0 0 0 0 S 0.0 0.0 0:00.01 scsi_eh_1
290 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 scsi_tmf_1
292 root 20 0 0 0 0 S 0.0 0.0 0:00.00 scsi_eh_2
294 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 scsi_tmf_2
295 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 ttm_swap
368 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kdmflush
369 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 bioset
378 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kdmflush
379 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 bioset
394 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 xfsalloc
395 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 xfs_mru_cache
396 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 xfs-buf/dm-0
397 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 xfs-data/dm-0
398 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 xfs-conv/dm-0
399 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 xfs-cil/dm-0
400 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 xfs-reclaim/dm-
401 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 xfs-log/dm-0
402 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 xfs-eofblocks/d
403 root 20 0 0 0 0 S 0.0 0.0 0:03.70 xfsaild/dm-0
477 root 20 0 37012 3552 3244 S 0.0 0.2 0:05.23 systemd-journal
497 root 20 0 192680 1408 988 S 0.0 0.1 0:00.00 lvmetad
501 root 20 0 44756 2976 1260 S 0.0 0.2 0:00.39 systemd-udevd
575 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 xfs-buf/sda1
576 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 xfs-data/sda1
577 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 xfs-conv/sda1
578 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 xfs-cil/sda1
579 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 xfs-reclaim/sda
580 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 xfs-log/sda1
581 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 xfs-eofblocks/s
582 root 20 0 0 0 0 S 0.0 0.0 0:00.64 xfsaild/sda1
593 root 16 -4 55476 1744 1344 S 0.0 0.1 0:00.15 auditd
611 root 20 0 24252 1708 1408 S 0.0 0.1 0:02.60 systemd-logind
614 dbus 20 0 24600 1696 1308 S 0.0 0.1 0:00.37 dbus-daemon
623 root 20 0 442684 10844 6532 S 0.0 0.6 0:00.46 NetworkManager
625 polkitd 20 0 534208 12060 4648 S 0.0 0.6 0:00.09 polkitd
629 root 20 0 126280 1572 956 S 0.0 0.1 0:00.03 crond
634 chrony 20 0 115940 1912 1516 S 0.0 0.1 0:00.33 chronyd
875 root 20 0 287496 5056 3800 S 0.0 0.3 0:00.53 rsyslogd
876 root 20 0 105996 4136 3156 S 0.0 0.2 0:00.10 sshd
878 root 20 0 560344 16624 5920 S 0.0 0.9 0:03.19 tuned
1368 root 20 0 115644 1764 1440 S 0.0 0.1 0:00.03 mysqld_safe
2437 root 20 0 89536 2160 1128 S 0.0 0.1 0:00.13 master
2584 postfix 20 0 89708 4036 3036 S 0.0 0.2 0:00.01 qmgr
2616 mysql 20 0 10.866g 1.386g 6368 S 0.0 77.8 1:28.59 mysqld
2705 root 20 0 110092 852 724 S 0.0 0.0 0:00.00 agetty
2836 root 20 0 113360 15952 3460 S 0.0 0.9 0:00.05 dhclient
3757 root 20 0 0 0 0 S 0.0 0.0 0:00.15 kworker/u256:1
3761 root 20 0 148388 5864 4484 S 0.0 0.3 0:01.11 sshd
3779 root 20 0 0 0 0 R 0.0 0.0 0:01.72 kworker/0:1
3781 root 20 0 115536 2208 1692 S 0.0 0.1 0:00.02 bash
3883 root 20 0 148388 5784 4464 S 0.0 0.3 0:00.12 sshd
3885 root 20 0 115536 2204 1692 S 0.0 0.1 0:00.01 bash
3903 postfix 20 0 89640 4008 3012 S 0.0 0.2 0:00.08 pickup
4042 root 20 0 148388 5784 4464 S 0.0 0.3 0:00.10 sshd
4044 root 20 0 115536 2216 1692 S 0.0 0.1 0:00.01 bash
4064 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/0:1H
4065 root 20 0 0 0 0 S 0.0 0.0 0:00.01 kworker/0:2
4087 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/0:0H
4089 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kworker/0:0
4101 root 20 0 137020 7788 3008 S 0.0 0.4 0:00.06 python3
4102 root 20 0 135040 6744 2864 S 0.0 0.4 0:00.04 python3
4103 root 20 0 157620 2048 1516 R 0.0 0.1 0:00.00 top >>: df
文件系统 1K-块 已用 可用 已用% 挂载点
/dev/mapper/cl_linux-root 17811456 7000408 10811048 40% /
devtmpfs 922660 0 922660 0% /dev
tmpfs 933632 0 933632 0% /dev/shm
tmpfs 933632 8792 924840 1% /run
tmpfs 933632 0 933632 0% /sys/fs/cgroup
/dev/sda1 1038336 141564 896772 14% /boot
tmpfs 186728 0 186728 0% /run/user/0

top -bn 1 输出完整

实例8:发送&接收大量数据

虽然上一个实例中我们通过data = client.recv(1073741824)设置了客户端一次可以接收1G数据,但是实际上客户端是不可能一次接收这么多数据的。

因为socket每次接收和发送都有最大数据量限制的,毕竟网络带宽也是有限的呀,不能一次发太多,发送的数据最大量的限制 就是缓冲区能缓存的数据的最大量,这个缓冲区的最大值在不同的系统上是不一样的, 我实在查不到一个具体的数字,但测试的结果是,在linux上最大一次可接收10mb左右的数据,不过官方的建议是不超过8k,也就是8192,并且数据要可以被2整除,不要问为什么 。anyway , 如果一次只能接收最多不超过8192的数据 ,那服务端返回的数据超过了这个数字怎么办呢?比如让服务器端打开一个5mb的文件并返回,客户端怎么才能完整的接受到呢?那就只能循环收取啦。

在开始解决上面问题3之前,我们要考虑,客户端要循环接收服务器端的大量数据返回直到一条命令的结果全部返回为止, 但问题是客户端知道服务器端返回的数据有多大么?答案是不知道,那既然不知道服务器的要返回多大的数据,那客户端怎么知道要循环接收多少次呢?答案是不知道。那咋办? 总不能靠猜吧?呵呵。。。 当然不能,那只能让服务器在发送数据之前主动告诉客户端,要发送多少数据给客户端,然后再开始发送数据。

先简单测试接收数据量大小

#encoding:utf-8 # python2.x
#Author:Zheng Na # 服务器端 # import time
import os,subprocess
import socket server = socket.socket()
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) server.bind(('localhost',6969)) # 绑定ip port
server.listen(5) # 监听 while True:
print("等待客户端的连接...")
conn, addr = server.accept() # 接受并建立与客户端的连接,程序在此处开始阻塞,直到有客户端连接进来...
print("新连接:",addr) while True:
data = conn.recv(1024) if not data:
print("客户端断开了...")
break # 这里断开就会再次回到第一次外层的loop print("收到命令:",data.decode("UTF-8")) # 注意:popen()可以执行shell命令,并读取此命令的返回值
#res = os.popen(data.decode("UTF-8")).read() # python3.x里socket发送的只有bytes,os.popen又只能接受str,所以要decode一下
#res = os.popen(data).read() # python2.x
res = subprocess.Popen(data,shell=True,stdout=subprocess.PIPE).stdout.read() #跟上面那条命令的结果是一样的 if len(res)==0:
res = "cmd exec success,has not output!" conn.send(str(len(res)).encode("UTF-8")) # 发送数据之前先告诉客户端要发多少数据给它
# time.sleep(0.5)
conn.sendall(res) # python3.x 只能发送bytes;发送端也有最大数量限制,所以这里用sendall,相当于重复循环电源conn.send(),直至数据发送完毕· server.close()

socket_server3-test-len-res.py

#encoding:utf-8 # python2.x
#Author:Zheng Na # 客户端 import socket # 声明socket类型,同时生成socket连接对象
client = socket.socket() # 默认参数family=AF_INET(表示地址簇为IPV4),type=SOCK_STREAM(表示socket类型为TCP) client.connect(('localhost',6969)) while True:
msg = input(">>: ").strip() # python3.x
#msg = raw_input(">>: ").strip() # python2.x if len(msg) == 0:continue # 如果为空,重新输入
client.send(msg.encode("UTF-8")) res_return_size = client.recv(1024)
print("getting cmd result,",res_return_size.decode("UTF-8")) total_rece_size = int(res_return_size)
print(total_rece_size) client.close()

socket_client3-test-len-res.py

PS:上面服务端代码在创建socket实例后,新加了如下一行代码

server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #允许socket重用地址

结果输出:报错

[root@hadoop my-test-files]# python3 socket_client3-test-len-res.py
>>: df
getting cmd result, 539文件系统 1K-块 已用 可用 已用% 挂载点
/dev/mapper/cl_linux-root 17811456 7062976 10748480 40% /
devtmpfs 922660 0 922660 0% /dev
tmpfs 933632 0 933632 0% /dev/shm
tmpfs 933632 8736 924896 1% /run
tmpfs 933632 0 933632 0% /sys/fs/cgroup
/dev/sda1 1038336 141564 896772 14% /boot
tmpfs 186728 0 186728 0% /run/user/0 Traceback (most recent call last):
File "socket_client3-test-len-res.py", line 23, in <module>
total_rece_size = int(res_return_size)
ValueError: invalid literal for int() with base 10: b'539\xe6\x96\x87\xe4\xbb\xb6\xe7\xb3\xbb\xe7\xbb\x9f 1K-\xe5\x9d\x97 \xe5\xb7\xb2\xe7\x94\xa8 \xe5\x8f\xaf\xe7\x94\xa8 \xe5\xb7\xb2\xe7\x94\xa8% \xe6\x8c\x82\xe8\xbd\xbd\xe

结果输出报错

看程序执行报错了, 我在客户端本想只接收服务器端命令的执行结果,但实际上却连命令结果也跟着接收了。 这是为什么呢?服务器不是只send了结果的大小么?不应该只是个数字么?命令结果不是第2次send的时候才发送的么??

哈哈,这里就引入了一个重要的概念,“粘包”, 即服务器端你调用时send 2次,但你send调用时,数据其实并没有立刻被发送给客户端,而是放到了系统的socket发送缓冲区里,等缓冲区满了、或者数据等待超时了,数据才会被send到客户端,这样就把好几次的小数据拼成一个大数据,统一发送到客户端了,这么做的目地是为了提高io利用效率,一次性发送总比连发好几次效率高嘛。 但也带来一个问题,就是“粘包”,即2次或多次的数据粘在了一起统一发送了。就是我们上面看到的情况。

我们在这里必须要想办法把粘包分开, 因为不分开,你就没办法取出来服务器端返回的命令执行结果的大小呀。so ,那怎么分开呢?首先你是没办法让缓冲区强制刷新把数据发给客户端的。 你能做的只有一个,就是让缓冲区超时,超时了,系统就不会等缓冲区满了,会直接把数据发走,因为不能一个劲的等后面的数据呀,等太久,会造成数据延迟了,那可是极不好的。so,如何让缓冲区超时呢?

答案就是:

1. time.sleep(0.5),经多次测试,让服务器程序sleep至少0.5就会造成缓冲区超时。哈哈,这么玩很有可能会被老板开除的,虽然我们觉得0.5s不多,但是对数据实时要求高的业务场景,比如股票交易,过了0.5s股票价格可以就涨跌很多。so,这个是很low的方法。

2. 比较好的方法就是,不用sleep,服务器端每发送一个数据给客户端,就立刻等待客户端进行回应,即调用 conn.recv(1024), 由于recv在接收不到数据时是阻塞的,这样就会造成,服务器端接收不到客户端的响应,就不会执行后面的conn.sendall(命令结果)的指令,收到客户端响应后,再发送命令结果时,缓冲区就已经被清空了,因为上一次的数据已经被强制发到客户端了。好机智,看下面代码实现。

#encoding:utf-8 # python2.x
#Author:Zheng Na # 服务器端 import os,subprocess
import socket server = socket.socket() # 获得socket实例
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) server.bind(('localhost',6969)) # 绑定ip port
server.listen(5) # 监听 while True:
print("等待客户端的连接...")
conn, addr = server.accept() # 接受并建立与客户端的连接,程序在此处开始阻塞,直到有客户端连接进来...
print("新连接:",addr) while True:
data = conn.recv(1024) if not data:
print("客户端断开了...")
break # 这里断开就会再次回到第一次外层的loop print("收到命令:",data.decode("UTF-8")) # 注意:popen()可以执行shell命令,并读取此命令的返回值
#res = os.popen(data.decode("UTF-8")).read() # python3.x里socket发送的只有bytes,os.popen又只能接受str,所以要decode一下
#res = os.popen(data).read() # python2.x
res = subprocess.Popen(data,shell=True,stdout=subprocess.PIPE).stdout.read() #跟上面那条命令的结果是一样的 if len(res)==0:
res = "cmd exec success,has not output!" conn.send(str(len(res)).encode("UTF-8")) # 发送数据之前先告诉客户端要发多少数据给它
print("等待客户ack应答...")
client_final_ack = conn.recv(1024) # 等待客户端响应
print("客户应答:",client_final_ack.decode("UTF-8"))
print(type(res))
conn.sendall(res) # python3.x 只能发送bytes;发送端也有最大数量限制,所以这里用sendall,相当于重复循环电源conn.send(),直至数据发送完毕· server.close()

socket_server4-bigdata.py

#encoding:utf-8 # python2.x
#Author:Zheng Na # 客户端 import socket
import sys # 声明socket类型,同时生成socket连接对象
client = socket.socket() # 默认参数family=AF_INET(表示地址簇为IPV4),type=SOCK_STREAM(表示socket类型为TCP) client.connect(('localhost',6969)) while True:
msg = input(">>: ").strip() # python3.x
#msg = raw_input(">>: ").strip() # python2.x if len(msg) == 0:continue # 如果为空,重新输入
client.send(msg.encode("UTF-8")) res_return_size = client.recv(1024)
print("getting cmd result,",res_return_size.decode("UTF-8")) total_rece_size = int(res_return_size)
print("total size,",total_rece_size)
client.send("准备好接收了,发吧".encode("UTF-8"))
received_size = 0 # 已接收到的数据
cmd_res = b''
f = open("test_copy.txt","wb") # 把接收到的结果存下来,一会儿看看收到的数据对不对
while received_size != total_rece_size: # 代表还没收完
data = client.recv(1024)
received_size += len(data) # 为什么不是直接加1024,还判断len干嘛,注意,实际收到的data有可能比1024少
cmd_res +=data
else:
print("数据收完了",received_size)
# print(cmd_res.decode())
f.write(cmd_res) # 把接收到的结果存下来,一会看看收到的数据对不对
#print(data.decode()) # 命令执行结果 client.close()

socket_client4-bigdata.py

[root@hadoop my-test-files]# python3 socket_client4-bigdata.py
>>: cat test.txt
getting cmd result, 5028317
total size, 5028317
数据收完了 5028317
>>: [root@hadoop my-test-files]# ll test*
-rw-r--r-- 1 root root 5028317 12月 5 18:28 test.txt
-rw-r--r-- 1 root root 5028317 12月 5 18:33 test-copy.txt test-copy.txt打开后内容与原文件一致

执行结果

接下来我们再写一个传输视频的实例

#encoding:utf-8 # python2.x
#Author:Zheng Na # 服务器端 import os,subprocess
import socket server = socket.socket() # 获得socket实例
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) server.bind(('localhost',6969)) # 绑定ip port
server.listen(5) # 监听 while True:
print("等待客户端的连接...")
conn, addr = server.accept() # 接受并建立与客户端的连接,程序在此处开始阻塞,直到有客户端连接进来...
print("新连接:",addr) while True:
data = conn.recv(1024) if not data:
print("客户端断开了...")
break # 这里断开就会再次回到第一次外层的loop print("收到命令:",data.decode("UTF-8")) #这里随便输入几个字母即可 f = open("video-send.avi")
res = f.read()
conn.send(str(len(res)).encode("UTF-8")) # 发送数据之前先告诉客户端要发多少数据给它
print("等待客户ack应答...")
client_final_ack = conn.recv(1024) # 等待客户端响应
print("客户应答:",client_final_ack.decode("UTF-8"))
# print(type(res))
conn.sendall(res) # python3.x 只能发送bytes;发送端也有最大数量限制,所以这里用sendall,相当于重复循环电源conn.send(),直至数据发送完毕· server.close()

socket_server5-bigdata-video.py

#encoding:utf-8 # python2.x
#Author:Zheng Na # 客户端 import socket
import sys # 声明socket类型,同时生成socket连接对象
client = socket.socket() # 默认参数family=AF_INET(表示地址簇为IPV4),type=SOCK_STREAM(表示socket类型为TCP) client.connect(('localhost',6969)) while True:
msg = input(">>: ").strip() # python3.x
#msg = raw_input(">>: ").strip() # python2.x if len(msg) == 0:continue # 如果为空,重新输入
client.send(msg.encode("UTF-8")) res_return_size = client.recv(1024)
print("getting cmd result,",res_return_size.decode("UTF-8")) total_rece_size = int(res_return_size)
print("total size,",total_rece_size)
client.send("准备好接收了,发吧".encode("UTF-8"))
received_size = 0 # 已接收到的数据
cmd_res = b''
f = open("video-receive.avi","wb") # 把接收到的结果存下来,一会儿看看收到的数据对不对
while received_size != total_rece_size: # 代表还没收完
data = client.recv(1024)
received_size += len(data) # 为什么不是直接加1024,还判断len干嘛,注意,实际收到的data有可能比1024少
cmd_res +=data
else:
print("数据收完了",received_size)
f.write(cmd_res) # 把接收到的结果存下来,一会看看收到的数据对不对 client.close()

socket_client5-bigdata-video.py

[root@hadoop my-test-files]# python3 socket_client5-bigdata-video.py
>>: aaa
getting cmd result, 63855708
total size, 63855708
数据收完了 63855708
>>: [root@hadoop my-test-files]# ll video*
-rw-r--r-- 1 root root 63855708 12月 5 18:59 video-receive.avi
-rw-r--r-- 1 root root 63855708 12月 4 18:26 video-send.avi video-receive.avi打开后可正常播放

执行结果