python学习第33天网络编程part3通信循环、bug处理、链接循环、远程控制命令、粘包问题处理

之前简单介绍了tcp和udp的服务端和客户端,但一个完整的服务端必须至少满足三个功能

(1)绑定一个固定的ip和port

(2)一直对外提供服务,稳定运行

(3)能够支持并发

一、通信循环

对于客户端与服务端,不单单只能交流一次,正常需要交流多次,这时候需要支持通信循环,用while循环实现多次交流

服务端:

from socket import *

server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)

conn, client_addr = server.accept()

# 通信循环
while True:
    data = conn.recv(1024)
    conn.send(data.upper())

conn.close()
server.close()

客户端:

from socket import *

client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8080))

# 通信循环
while True:
    msg=input('>>: ').strip()
    client.send(msg.encode('utf-8'))
    data=client.recv(1024)
    print(data)

client.close()

二、bug处理

(1)起因:当客户端非正常的断开,服务端就会报错,可预知但无法准确知道。

过程分析:客户端输入了空,服务端不会收到空数据;如果服务端收到了空数据,肯定是客户端单方面的把链接异常中断掉,而在在windows系统上服务端就会抛出异常,在Linux系统上服务端recv一直收空,无法预知异常发生的条件

解决方法:异常处理    try...except

服务端

from socket import *

server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)

conn, client_addr = server.accept()
print(client_addr)

# 通信循环
while True:
    try:                                 
        data = conn.recv(1024)
        if len(data) == 0:break # 针对linux系统
        print('-->收到客户端的消息: ',data)
        conn.send(data.upper())
    except ConnectionResetError:
        break

conn.close()
server.close()

(2)起因:当用户端输入空时候,用户端堵塞住

过程分析:当用户端输入了空数据,用户端的操作系统收不到数据,收不到数据不会传到用户端的网卡,也就不能传输数据,服务端收不到数据,所以

服务端也不会回发数据,用户端也就收不到数据最终堵塞

解决方法:添加判断,不让用户端输入空

from socket import *

client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8081))

# 通信循环
while True:
    msg=input('>>: ').strip() #msg=''
    if len(msg) == 0:continue        #输入不能为空
    client.send(msg.encode('utf-8')) #client.send(b'')
    # print('has send')
    data=client.recv(1024)
    # print('has recv')
    print(data)

client.close()

三、链接循环

单单一个服务端和客户端交流是不够的,需要多个客户端可以与链接循环

服务端

from socket import *

server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8081))
server.listen(5)

# 链接循环
while True:
    conn, client_addr = server.accept()
    print(client_addr)

    # 通信循环
    while True:
        try:
            data = conn.recv(1024)
            if len(data) == 0: break  # 针对linux系统
            print('-->收到客户端的消息: ', data)
            conn.send(data.upper())
        except ConnectionResetError:
            break

    conn.close()

server.close()

客户端

from socket import *

client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8081))

# 通信循环
while True:
    msg=input('>>: ').strip() #msg=''
    if len(msg) == 0:continue
    client.send(msg.encode('utf-8')) #client.send(b'')
    # print('has send')
    data=client.recv(1024)
    # print('has recv')
    print(data)

client.close()

 四、模拟ssh实现远程执行命令

为了执行系统命令,服务端需要导入subprocess模块

server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8081))
server.listen(5)

# 链接循环
while True:
    conn, client_addr = server.accept()
    print(client_addr)

    # 通信循环
    while True:
        try:                         
            cmd = conn.recv(1024) #cmd=b'dir'
            if len(cmd) == 0: break  # 针对linux系统
            obj=subprocess.Popen(cmd.decode('utf-8'),
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE
                             )
            stdout=obj.stdout.read()
            stderr=obj.stderr.read()
            print(len(stdout) + len(stderr))
            conn.send(stdout+stderr)
        except ConnectionResetError:
            break

    conn.close()

server.close()

 客户端:

from socket import *

client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8081))

# 通信循环
while True:
    cmd=input('>>: ').strip()
    if len(cmd) == 0:continue
    client.send(cmd.encode('utf-8'))
    cmd_res=client.recv(1024000)
    print(cmd_res.decode('gbk'))

client.close()

五、粘包问题

1、粘包问题的起因:tcp协议流式传输数据的方式 导致的,必须要全部收完

假设客户端、服务端接受的数据为1024个字节

当发送的数据量大于接受量1024的时候,就会出现每次接受只有1024个字节,剩余的字节等下次命令/输入的时候再输入过来,导致

命令错乱

2、解决思路:

(1)方案一:可以提高接受数据的上限,但是提高也有上限的,当设置的上限过高或者超过内存大小的时候会报错,

而且往往传输的文件大于内存,这个方法不适用

(2)方案二:自定义报头 ,循环多收几次收干净,一次性打印出来

步骤一:设计一个固定长度的报头数据,报头含有数据流的长度,长度是bytes才能传输,需要导入

struct模块,把整型转化为bytes,底层原理就是报头与真正的数据连在一起

ps;补充struct模块的使用

import struct

# obj1=struct.pack('i',13321111111)
# print(obj1,len(obj1))          将整型转化为bytes

# res1=struct.unpack('i',obj1)          将bytes反解化成整型
# print(res1[0])

obj1=struct.pack('q',1332111111111111111111111)
print(obj1,len(obj1))

服务端发给客户端:

1先制作固定长度的报头
2再发送报头
3最好发送真实的数据
server.bind(('127.0.0.1', 8081))
server.listen(5)

# 链接循环
while True:
    conn, client_addr = server.accept()
    print(client_addr)

    # 通信循环
    while True:
        try:
            cmd = conn.recv(1024) #cmd=b'dir'
            if len(cmd) == 0: break  # 针对linux系统
            obj=subprocess.Popen(cmd.decode('utf-8'),
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE
                             )
            stdout=obj.stdout.read()
            stderr=obj.stderr.read()
            # 1. 先制作固定长度的报头
            header=struct.pack('i',len(stdout) + len(stderr))
            # 2. 再发送报头
            conn.send(header)
            # 3. 最后发送真实的数据
            conn.send(stdout)
            conn.send(stderr)
        except ConnectionResetError:
            break

    conn.close()

server.close()

步骤二:

客户端接受数据

1、先收报头,从报头里解出数据的长度

2、接受真正的数据

3、每次以1024个数据回收数据,直到收到的数据长度大于含报头的所有数据流的长度,就不收了,然后打印所有收到的数据

from socket import *
import struct

client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8081))

# 通信循环
while True:
    cmd=input('>>: ').strip()
    if len(cmd) == 0:continue
    client.send(cmd.encode('utf-8'))
    #1. 先收报头,从报头里解出数据的长度
    header=client.recv(4)
    total_size=struct.unpack('i',header)[0]
    #2. 接收真正的数据
    cmd_res=b''
    recv_size=0
    while recv_size < total_size:
        data=client.recv(1024)
        recv_size+=len(data)
        cmd_res+=data

    print(cmd_res.decode('gbk'))

client.close()

(3)方案三(完整版):方案二还有个问题没有解决,那就是如果当报头中的文件长度很大,而struct只能把整型转化为4、8字节,当发送的数据很大的时候也会超出struct的转化长度,struct也无法转化数据的长度

解决思路:1、先制作报头,把报头做成一个字典包含比如文件名、md5值、数据长度等等

序列化字典得到字典的bytes,这时候的bytes长度就很小

这时候struct就可以又把字典的bytese的长度转化为固定4个bytes

2、先发送4个bytes(包含报头的长度)
3、在发送报头

4、最好发送真实的数据

from socket import *
import subprocess
import struct
import json

server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8081))
server.listen(5)

# 链接循环
while True:
    conn, client_addr = server.accept()
    print(client_addr)

    # 通信循环
    while True:
        try:
            cmd = conn.recv(1024)  # cmd=b'dir'
            if len(cmd) == 0: break  # 针对linux系统
            obj = subprocess.Popen(cmd.decode('utf-8'),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE
                                   )
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            # 1. 先制作报头
            header_dic = {
                'filename': 'a.txt',
                'md5': 'asdfasdf123123x1',
                'total_size': len(stdout) + len(stderr)
            }
            header_json = json.dumps(header_dic)
            header_bytes = header_json.encode('utf-8')

            # 2. 先发送4个bytes(包含报头的长度)
            conn.send(struct.pack('i', len(header_bytes)))
            # 3  再发送报头
            conn.send(header_bytes)

            # 4. 最后发送真实的数据
            conn.send(stdout)
            conn.send(stderr)
        except ConnectionResetError:
            break

    conn.close()

server.close()

客户端接受数据

1先收4bytes,解出报头的长度

2再接受报头,拿到字典

3接受真正的数据

from socket import *
import struct
import json

client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8081))

# 通信循环
while True:
    cmd=input('>>: ').strip()
    if len(cmd) == 0:continue
    client.send(cmd.encode('utf-8'))
    #1. 先收4bytes,解出报头的长度
    header_size=struct.unpack('i',client.recv(4))[0]

    #2. 再接收报头,拿到header_dic
    header_bytes=client.recv(header_size)
    header_json=header_bytes.decode('utf-8')
    header_dic=json.loads(header_json)
    print(header_dic)
    total_size=header_dic['total_size']

    #3. 接收真正的数据
    cmd_res=b''
    recv_size=0
    while recv_size < total_size:
        data=client.recv(1024)
        recv_size+=len(data)
        cmd_res+=data

    print(cmd_res.decode('gbk'))

client.close()
原文地址:https://www.cnblogs.com/ye-hui/p/9911736.html