疫情环境下的网络学习笔记 python 4.21

4.21

今日内容

  1. 基于tcp实现远程执行命令
  2. tcp的粘包问题:自定义协议
  3. socketserver实现并发
  4. 阿里云服务器

tcp远程命令

客户端

from socket import *
client = socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))
while True:
	msg = input('input command:')
	if len(msg) == 0:continue
	client.send(msg.encode('utf-8'))  # windows系统用gbk
	cmd_res = client.recv(1024)  # 本次接收最大1024字节
	print(cmd_res)

服务端

from socket import *
import subprocess
server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
    conn, clienr_addr = server.accept()
    while True:
        try:
            res = conn.recv(1024)
            if len(res) == 0:
                break
            # 使用subprocess模块,返回正确结果和错误结果
            obj = subprocess.Popen(
                res.decode('utf-8'),
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            stdout_res = obj.stdout.read()
            stderr_res = obj.stderr.read()
            conn.send(stdout_res + stderr_res)
        except Exception as e:
            print(e)
            break
    conn.close()

服务端做的两件事

  1. 循环地从半连接池中取出链接,于其建立双向链接,拿到链接对象:accept
  2. 拿到链接对象,于其进行通信循环

服务端应该满足两个特点

  1. 一直对外提供服务
  2. 并发地服务多个链接

粘包问题

  • 对于结果比较大的命令,如ps aux,客户端一次最多只能接收1024字节
  • 那么socket一次会收不完,但是数据已经接收到客户端缓存里
  • 接受完1024字节客户端又接着让用户输入,再进行一次通信。客户端的命令能够正确发送给服务端,也能正确返回数据。
  • 因为上一次的数据socket没有收干净,所以client.recv会继续收上一次通信的后1024个字节,显示上一次通信的结果,于是以后所有命令都乱了

粘包问题出现的原因

  1. tcp是流式协议,数据像水流一样无法区分地连在一起
  2. 收数据没收干净,有残留,会跟下一次结果混在一起

解决粘包的核心法则就是每次都收干净,没有任何残留

增大recv里的数字没有意义,因为缓存是有限的,超过缓冲区的大小就不能接收了

udp不会粘包:udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠

不能用udp协议解决粘包问题

解决粘包的思路

客户端

  1. 先收固定长度的头,解析出数据的描述信息,包括数据的总大小
  2. 根据解析出的描述信息,接收真实的数据
    1. recv_size=0,循环接收,每接收一次,recv_size+=接收的长度
    2. 直到recv_size=total_size,结束循环
header=client.recv(4)
total_size=struct.unpack('i',header)[0]
recv_size = 0
while recv_size < total_size:
    recv_data=client.recv(1024)
    recv_size+=len(recv_data)
    print(recv_data.decode('utf-8'),end='')
else:
	print()

服务端

struct模块:把数据转换成固定长度

  1. 先发头信息,使用struct模块,将数据长度转换成四个bytes字节
  2. 再发送真实的数据
# 接上面服务端
# ...
total_size = len(stdout) + len(stderr)
header = struct.pack('i',total_size)
conn.send(header)
conn.send(stdout)
conn.send(stderr)

这样就可以改写上面服务端的 conn.send(stdout_res + stderr_res)

改成正确结果和错误结果分开send,客户端接受完正确结果,才会接收错误结果

传输文件

header里不可能只放数据大小,在传输文件加头的时候可以放进其他属性:写一个header_字典,转成json格式,先发送header字典的长度,再发header字典,再发真实数据

服务端

# 服务端
import json,struct
# 制作头
header_dic = {
	'file_name':'a.txt',
	'total_size':total_size,
	'md5':md5
}
json_str = json.dumps(header_dic)
json_str_bytes = json_str.encode('ut-8')
# 得到头的长度
header_size = struct.pack('i',len(json_str_bytes))
# 先把头的长度发去,固定四个字节
conn.send(head_size)
# 发送头
conn.send(json_str_bytes)
# 发送真实的数据

客户端

# 1. 先收四个字节,得到头字典的长度
x = client.recv(4)
header_len = struct.unpack('i',x)[0]
# 2. 接收字典
# 3. loads接开字典,得到数据长度,接收数据

基于socketserver实现并发

服务端要做两件事:

  1. 循环地从半连接池中取出链接请求并与其建立双向链接,拿到链接对象conn
  2. 拿到链接对象,与其进行通信循环
import socketserver
class MyRequestHandle(socketserver.BaseRequestHandler):
	def handle(self):
		print(self.request)
		print(self.client_address)
		
socketserver.ThreadingTCPServer(('127.0.0.1',8888),MyRequestHandle)
s.serve_forever()
# serve forever等同于
# while True: conn,client_addr=server.accept()
# 启动一个线程(conn,client)

部署服务端到阿里云

  • 客户端发包要connect的是公网地址
  • 远程链接可以用web界面操作
  • windows用 Xshell
  • 新建py文件,放进去服务端,绑定 0.0.0.0
  • wget:下载python3
  • 添加安全组,开放端口

作业 单例

单例:让实例化类得到对象的时候,多个对象都指向同一个内存地址。调用多次类,也只产生一个示例

类,元类,装饰器,__new__,模块导入实现

p1 = People('deimos',21,'male')
p2 = People('deimos',21,'male')
p3 = People('deimos',21,'male')

p1 is p2
# False

打开同一个文件,链接MySQL的时候会使用到

模块方法:模块只会被导入一次,所以只有一个内存空间

posted @ 2020-04-21 19:43  黑猫警长紧张  阅读(167)  评论(0)    收藏  举报