• 疫情环境下的网络学习笔记 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的时候会使用到

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

  • 相关阅读:
    SpringBoot打包的程序部署到服务器后一直在后台运行
    ubuntu搭建mysql数据库
    解决ubuntu16.04 ‘E: 无法获得锁 /var/lib/dpkg/lock-frontend
    项目部署篇之——下载安装Xftp6,Xshell6
    linux 文件 chgrp、chown、chmod
    linux 正确的关机方法
    linux 常用命令
    spring 事务
    Spring 中 ApplicationContext 和 BeanFactory 的区别
    java 异常处理
  • 原文地址:https://www.cnblogs.com/telecasterfanclub/p/12747325.html
Copyright © 2020-2023  润新知