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

实例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'1'
>>: 2
recv from server:  b'2'
>>: 3
recv from server:  b'3'
>>: 

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

再重新查看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) == 0: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)==0:
             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打开后可正常播放
执行结果

 

转载于:https://www.cnblogs.com/zhengna/p/9646270.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值