Python学习笔记 day8 网络编程

对send和receive进行封装,socket

大概流程:
发送端
import socket
socket.TCP/IP 声明类型
connect(a.ip,a.port) 连接远程机器
socket.send(hello)
socket.recv()
socket.close()

接收端
import socket
socket.TCP/IP 声明类型
listen(0.0.0.0,6969) 监听端口
waiting() 等待连接
recv() 接收数据
send

 
TCP/IP 协议
在这里插入图片描述

socket 概述

socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求。

socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)

socket和file的区别:

file模块是针对某个指定文件进行【打开】【读写】【关闭】
socket模块是针对 服务器端 和 客户端Socket 进行【打开】【读写】【关闭】

socket本质上就是在2台网络互通的电脑之间,架设一个通道,两台电脑通过这个通道来实现数据的互相传递。 我们知道网络 通信 都 是基于 ip+port 方能定位到目标的具体机器上的具体服务,操作系统有0-65535个端口,每个端口都可以独立对外提供服务,如果 把一个公司比做一台电脑 ,那公司的总机号码就相当于ip地址, 每个员工的分机号就相当于端口, 你想找公司某个人,必须 先打电话到总机,然后再转分机 。

建立一个socket必须至少有2端, 一个服务端,一个客户端, 服务端被动等待并接收请求,客户端主动发起请求, 连接建立之后,双方可以互发数据。
在这里插入图片描述

socket 参数

sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)

参数一:地址簇

  socket.AF_INET IPv4(默认)
  socket.AF_INET6 IPv6

  socket.AF_UNIX 只能够用于单一的Unix系统进程间通信

参数二:类型

  socket.SOCK_STREAM  流式socket , for TCP (默认)
  socket.SOCK_DGRAM   数据报式socket , for UDP

  socket.SOCK_RAW 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
  socket.SOCK_RDM 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
  socket.SOCK_SEQPACKET 可靠的连续数据包服务
sk.bind(address)
  s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。

sk.listen(backlog)
  开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。
      backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5
      这个值不能无限大,因为要在内核中维护连接队列

sk.setblocking(bool)
  是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。

sk.accept()
  接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。
  接收TCP 客户的连接(阻塞式)等待连接的到来

sk.connect(address)
  连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。

sk.connect_ex(address)
  同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061

sk.close()
  关闭套接字

sk.recv(bufsize[,flag])
  接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。

sk.recvfrom(bufsize[.flag])
  与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。

sk.send(string[,flag])
  将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。

sk.sendall(string[,flag])
  将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
      内部通过递归调用send,将所有内容发送出去。

sk.sendto(string[,flag],address)
  将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。

sk.settimeout(timeout)
  设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s )

sk.getpeername()
  返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。

sk.getsockname()
  返回套接字自己的地址。通常是一个元组(ipaddr,port)

sk.fileno()
  套接字的文件描述符

socket.sendfile(file, offset=0, count=None)

socket 简单实例

客户端 client:

import socket

client = socket.socket() # 声明socket类型,同时生成socket连接对象
client.connect(('localhost',6969))
client.send(b"Hello World!")  # client.send("Hello World!".encode("utf-8"))
data = client.recv(1024)
print("recv:",data)  # print("recv:",data.decode())
client.close()

服务端 server:

import socket

server = socket.socket()
server.bind(('localhost',6969))  # 绑定要监听端口
server.listen()  # 监听
print("我要开始等电话了")
# 连接的标志位,对方的地址
# conn就是客户端连过来而在服务器端为其生成的一个连接实例
conn,addr = server.accept()  # 等待数据接收
print(conn,addr)
print("电话来了")
data = conn.recv(1024)
print("recv:",data)
conn.send(data.upper())
server.close()
结果:
服务端:
我要开始等电话了
<socket.socket fd=564, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 6969), raddr=('127.0.0.1', 2530)> ('127.0.0.1', 2530)
电话来了
recv: b'Hello World!'

客户端:
recv: b'HELLO WORLD!'

改良版

增强了持续输入信息,还有信息为空的判断

第一个客户端会占用,第二个客户端持续等待,当第一个挂断后,服务端也会跟着挂断

服务端:
import socket
import os
server = socket.socket()
server.bind(('localhost',6969))  # 绑定要监听端口
server.listen()  # 监听 (最大允许挂起的连接数)
print("我要开始等电话了")
# 连接的标志位,对方的地址
# conn就是客户端连过来而在服务器端为其生成的一个连接实例
while True:
    conn, addr = server.accept()  # 等待数据接收
    while True:
        data = conn.recv(1024)
        print("recv:",data)
        if not data:
            print("client has lost...")
            break
        # ssh 执行命令
        # res = os.popen(data).read()
        # conn.send(res)
        conn.send(data.upper()) # 转为大写后返回
server.close()

客户端:
import socket
client = socket.socket() # 声明socket类型,同时生成socket连接对象
client.connect(('localhost',6969))
while True:
    msg = input(">>:").strip()
    if len(msg) == 0:continue
    client.send(msg.encode("utf-8"))
    data = client.recv(102400)
    print("recv:",data.decode())  #配合data来验证
    #print(data.decode())  # ssh
    # 不能执行top这种持续的命令。 还有不能有中文字符那种
client.close()

 

socket 实现 ssh

通过socket实现减半ssh

服务端:

import socket,os
server = socket.socket()
server.bind(('localhost',9999))
server.listen()

while True:
    conn,addr = server.accept()
    print("new conn:",addr)
    while True:
        print("等待新指令")
        data = conn.recv(1024)
        if not data:
            print("客户端已断开")
            break
        print("执行指令:",data)
        cmd_res = os.popen(data.decode()).read()  # 接收字符串,执行结果也是字符串
        print("before send",len(cmd_res))

        if len(cmd_res) == 0:
            cmd_res = "cmd has no output..."

        # send在socket机制中不是立刻发送,先把数据存到缓冲区,缓冲区满了系统调用接口自动send。
        # 剩余的部分数据会存放缓冲区,没有堆满,如何解决? 多收几次,那是多少次?
        # 给客户端发送数据前,要给客户端发送多少数据,判断len一下。 然后告诉客户端大小。客户端就知道接收多大,接收多少次,然后循环
        conn.send(cmd_res.encode("utf-8"))
        print("send done")
server.close()

客户端:

import socket
client = socket.socket()
client.connect(('localhost',9999))

while True:
    cmd = input(">>:").strip()
    if len(cmd) == 0:continue
    client.send(cmd.encode('utf-8')) # encode会转成bytes,如果里面有中文,会转为ASCII码里对应的内容再转成byte
    cmd_res = client.recv(1024)
    print(cmd_res.decode())  # b'\nWindows IP \xe9\x85\x8d\x ,decode
client.close()

 

通过socket实现完整ssh

    # send在socket机制中不是立刻发送,先把数据存到缓冲区,缓冲区满了系统调用接口自动send。
    # 剩余的部分数据会存放缓冲区,没有堆满,会出现命令结果断断续续,如何解决? 多收几次,那是多少次?
    # 给客户端发送数据前,要给客户端发送多少数据,判断len一下。 然后告诉客户端大小。客户端就知道接收多大,接收多少次,然后循环

在这里插入图片描述
中文encode后长度不一致。

服务端:


import socket,os
server = socket.socket()
server.bind(('localhost',9999))
server.listen()

while True:
    conn,addr = server.accept()
    print("new conn:",addr)
    while True:
        print("等待新指令")
        data = conn.recv(1024)
        if not data:
            print("客户端已断开")
            break
        print("执行指令:",data)
        cmd_res = os.popen(data.decode()).read()  # 接收字符串,执行结果也是字符串
        print("before send",len(cmd_res))

        if len(cmd_res) == 0:
            cmd_res = "cmd has no output..."

        # send在socket机制中不是立刻发送,先把数据存到缓冲区,缓冲区满了系统调用接口自动send。
        # 剩余的部分数据会存放缓冲区,没有堆满,如何解决? 多收几次,那是多少次?
        # 给客户端发送数据前,要给客户端发送多少数据,判断len一下。 然后告诉客户端大小。客户端就知道接收多大,接收多少次,然后循环
        conn.send(str(len(cmd_res.encode())).encode("utf-8"))  # 先发大小给客户端
        conn.send(cmd_res.encode("utf-8"))
        print("send done")
server.close()

客户端:

import socket
client = socket.socket()
client.connect(('localhost',9999))

while True:
    cmd = input(">>:").strip()
    if len(cmd) == 0:continue
    client.send(cmd.encode('utf-8')) # encode会转成bytes,如果里面有中文,会转为ASCII码里对应的内容再转成byte

    cmd_res_size = client.recv(1024)  # 接收命令结果的长度
    print("命令结果大小:",cmd_res_size)

    received_size = 0
    received_data = b''
    while received_size < int(cmd_res_size.decode()):
        data = client.recv(1024)
        received_size += len(data)  # 每次收到的有可能小于1024,所以必须用len判断,这也才准确
        #print(data.decode())
        received_data += data
    else:
        print("cmd res receive done..",received_size)
        print(received_data.decode())
client.close()

结果:

>>:ipconfig
命令结果大小: b'2080'
cmd res receive done.. 2080

Windows IP 配置
以太网适配器 VirtualBox Host-Only Network:
省略。。。。。。。。。。。。。。。。。。

和cmd下执行ipconfig一样,完整的显示出来了

 

socket 粘包

在Linux中 服务端两次send的话,缓冲区会把这两个send合为一条,然后 被客户端一次 rev接收了。
在这里插入图片描述 其实也会出现在Windows上,试的次数不够多。 有时会粘住,有时不会。 主要是看缓冲区机制

那么如何解决呢?
两个send之间可以 time.sleep(0.5)

服务端
        conn.send(str(len(cmd_res.encode())).encode("utf-8"))  # 先发大小给客户端
        time.sleep(0.5)
        conn.send(cmd_res.encode("utf-8"))

但这种解决机制也不行,面对实时场景,不用sleep如何解决?

服务端
        conn.send(str(len(cmd_res.encode())).encode("utf-8"))  # 先发大小给客户端
        client_ack = conn.recv(1024)  # wait client to confirm
        print("ack from client:",client_ack)
        conn.send(cmd_res.encode("utf-8"))
客户端
    print("命令结果大小:",cmd_res_size)
	client.semd("我准备好接受了".encode("utf-8"))

完整版本:

服务端:
import socket,os
server = socket.socket()
server.bind(('localhost',9999))
server.listen()

while True:
    conn,addr = server.accept()
    print("new conn:",addr)
    while True:
        print("等待新指令")
        data = conn.recv(1024)
        if not data:
            print("客户端已断开")
            break
        print("执行指令:",data)
        cmd_res = os.popen(data.decode()).read()  # 接收字符串,执行结果也是字符串
        print("before send",len(cmd_res))

        if len(cmd_res) == 0:
            cmd_res = "cmd has no output..."

        # send在socket机制中不是立刻发送,先把数据存到缓冲区,缓冲区满了系统调用接口自动send。
        # 剩余的部分数据会存放缓冲区,没有堆满,如何解决? 多收几次,那是多少次?
        # 给客户端发送数据前,要给客户端发送多少数据,判断len一下。 然后告诉客户端大小。客户端就知道接收多大,接收多少次,然后循环
        conn.send(str(len(cmd_res.encode())).encode("utf-8"))  # 先发大小给客户端
        client_ack = conn.recv(1024)  # wait client to confirm
        print("ack from client:",client_ack)
        conn.send(cmd_res.encode("utf-8"))
        print("send done")
server.close()


客户端:
import socket
client = socket.socket()
client.connect(('localhost',9999))

while True:
    cmd = input(">>:").strip()
    if len(cmd) == 0:continue
    client.send(cmd.encode('utf-8')) # encode会转成bytes,如果里面有中文,会转为ASCII码里对应的内容再转成byte

    cmd_res_size = client.recv(1024)  # 接收命令结果的长度
    print("命令结果大小:",cmd_res_size)
    client.send("我准备好接受了".encode('utf-8')) # print("ack from client:",client_ack.decode())

    received_size = 0
    received_data = b''
    while received_size < int(cmd_res_size.decode()):
        data = client.recv(1024)
        received_size += len(data)  # 每次收到的有可能小于1024,所以必须用len判断,这也才准确
        #print(data.decode())
        received_data += data
    else:
        print("cmd res receive done..",received_size)
        print(received_data.decode())
client.close()

 

socket 实现文件发送

ftp server
1.读取客户端发过来的文件名
2.检测文件是否存在
3.打开文件
4.检测文件大小,告诉客户端文件大小,让客户知道。
5.发送文件大小给客户端
6.等待客户端确认
7.开始边读边发数据
8.发送md5 (双方验证)

ftp server:

import socket,os,hashlib
server = socket.socket()
server.bind(('localhost',9999))
server.listen()

while True:
    conn,addr = server.accept()
    print("new conn:",addr)
    while True:
        print("等待新指令")
        data = conn.recv(1024)
        if not data:
            print("客户端已断开")
            break
        print("执行指令:",data)
        # cmd_res = os.popen(data.decode()).read()  # 接收字符串,执行结果也是字符串
        cmd,filename = data.decode().split()   # data是byte,所以要decode
        print(filename)
        if os.path.isfile(filename):  # 判断是否是一个文件,而不是目录
            f = open(filename,"rb")
            m = hashlib.md5()
            file_size = os.stat(filename).st_size  # 获取 st_size
            conn.send(str(file_size).encode())  # 发送文件大小
            conn.recv(1024)  # 等待确认 wait for ack
            for line in f:
                # 边发边md5
                m.update(line)
                conn.send(line)
            print("file md5",m.hexdigest())
            f.close()
            conn.send(m.hexdigest().encode())  # send md5
                # m = hashlib.md5()
                # m.update(b"test")
                # m.hexdigest()  # 十六进制格式
                # m.update(b"test") m,update(b"abc") 和 m2.update(b"testabc") 一样的
        print("send done")
server.close()

ftp client

import socket
import hashlib
client = socket.socket()
client.connect(('localhost',9999))

while True:
    cmd = input(">>:").strip()
    if len(cmd) == 0:continue
    # startswith() 方法用于检查字符串是否是以指定子字符串开头,如果是则返回 True
    if cmd.startswith("get"):
        client.send(cmd.encode())
        server_response = client.recv(1024)
        print("server response:",server_response) # 文件大小
        client.send(b"ready to recv file")
        file_total_size = int(server_response.decode())
        received_size = 0
        filename = cmd.split()[1] # 【1】是文件的名字
        f = open(filename + ".new","wb")
        m = hashlib.md5()
        # 最后一次不收1024,判断多少就收多少,可以有效解决粘包
        while received_size < file_total_size:
            if file_total_size - received_size > 1024: # 接收不止一次
                size = 1024
            else: # 最后一次了,剩多少接收多少
                size = file_total_size - received_size
                print("最后一次大小:",size)
            data = client.recv(size)
            received_size += len(data)
            m.update(data)
            f.write(data)
            # print(file_total_size,received_size) 看效果
        else:
            new_file_md5 = m.hexdigest()  # 最后一下
            print("文件接收完成",received_size,file_total_size)
            f.close()
        server_file_md5 = client.recv(1024)
        print("server file md5:",server_file_md5)
        print("client file md5:", new_file_md5)




client.close()

结果:

客户端:
>>:get test.txt
server response: b'26'
最后一次大小: 26
文件接收完成 26 26
server file md5: b'232f98cfb1a53747cb7a5eece0229cf7'
client file md5: 232f98cfb1a53747cb7a5eece0229cf7


服务端:
new conn: ('127.0.0.1', 4740)
等待新指令
执行指令: b'get test.txt'
test.txt
file md5 232f98cfb1a53747cb7a5eece0229cf7
send done
等待新指令

 

socketserver 实现多并发

最主要作用:实现并发处理
主要用两个类型:

class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)

This uses the Internet TCP protocol, which provides for continuous streams of data between the client and server. If bind_and_activate is true, the constructor automatically attempts to invoke server_bind() andserver_activate(). The other parameters are passed to the BaseServer base class.

class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)

This uses datagrams, which are discrete packets of information that may arrive out of order or be lost while in transit. The parameters are the same as for TCPServer.

创建一个socketserver 至少分以下几步:
1.创建 request handler 请求处理类,并且这个类要 继承BaseRequestHandle,然后重写父亲类里的handle方法
2.实例化 TCPServer 类,并且传递server ip 和 你上面创建的请求处理类 给这个 TCPServer
3.处理请求,
server.handle_request() # 只处理一个请求
server.serve_forever() # 处理多个请求,永远执行
4.关闭socket 。 server_close
服务端:socketserver

import socketserver

class MyTCPHandle(socketserver.BaseRequestHandler):
    # 与客户端所有的交互都在handle
    def handle(self):
        while True:
            try:
                #  self.request 每一个请求都会进行实例化 MyTCPHandler
                self.data = self.request.recv(1024).strip()
                print("{}wrote:".format(self.client_address[0]))  # 格式化,打印客户端的IP地址
                print(self.data)
                self.request.send(self.data.upper())  # 传回数据,sendall是重复调用send
            except ConnectionResetError as e:  # 在py2,如果数据为空,会报这个错误
                print("断开了",e)
                break

if __name__ == "__main__":
    HOST,PORT = "localhost",9999

    # 实例化,绑定IP地址和端口号
    server = socketserver.ThreadingTCPServer((HOST,PORT),MyTCPHandle)
    server.serve_forever()

客户端:

import socket

client = socket.socket() # 声明socket类型,同时生成socket连接对象
client.connect(('localhost',9999))

while True:
    msg = input(">>:").strip()
    if len(msg) == 0:continue
    client.send(msg.encode("utf-8"))
    data = client.recv(10240)
    print("recv:",data.decode())
client.close()

结果:运行两个客户端,发消息,能收到回复。 因此 支持多并发
来一个请求就开一个新的,独立的线程 ThreadingTCPServer

多进程:ForkingTCPServer 在Windows上不好用,在Linux创建一个进程就是fork

 

作业

作业1:开发一个支持多用户在线的FTP程序(用socketserver继续完善)
要求:

用户加密认证
允许同时多用户登录  —— 多并发 
每个用户有自己的家目录 ,且只能访问自己的家目录  
对用户进行磁盘配额,每个用户的可用空间不同  —— 在自己的家目录随意切换
允许用户在ftp server上随意切换目录
允许用户查看当前目录下文件
允许上传和下载文件,保证文件一致性  —— MD5验证
文件传输过程中显示进度条 —— (总大小÷已收大小)*100%
附加功能:支持文件的断点续传  —— 断点续传

大概实现思路
在这里插入图片描述
服务端:

import socketserver
import json,os

class MyTCPHandle(socketserver.BaseRequestHandler):
    # 与客户端所有的交互都在handle
    def put(self,*args): # 接收客户端文件
        cmd_dic = args[0]
        filename = cmd_dic["filename"]
        filesize = cmd_dic["filesize"]
        if os.path.isfile(filename):
            f = open(filename + ".new","wb")
        else:
            f = open(filename,"wb")

        self.request.send(b"200 ok")
        received_size = 0
        while received_size < filesize:
            data = self.request.recv(1024)
            f.write(data)
            received_size += len(data)
        else:
            print("file [%s] has uploaded...." % filename)

    def handle(self):
        while True:
            try:
                #  self.request 每一个请求都会进行实例化 MyTCPHandler
                self.data = self.request.recv(1024).strip()
                print("{}wrote:".format(self.client_address[0]))  # 格式化,打印客户端的IP地址
                print(self.data)

                cmd_dic = json.loads(self.data.decode())
                action = cmd_dic["action"]
                if hasattr(self,action):
                    func = getattr(self,action)
                    func(cmd_dic)

            except ConnectionResetError as e:
                print("断开了",e)
                break

if __name__ == "__main__":
    HOST,PORT = "localhost",9999

    # 实例化,绑定IP地址和端口号
    server = socketserver.ThreadingTCPServer((HOST,PORT),MyTCPHandle)
    server.serve_forever()

客户端:

#-*-coding:gb2312-*-
import socket
import os
import json


class FtpClient(object):
    def __init__(self):
        self.client = socket.socket()

    def help(self):
        msg = '''
        ls
        pwd
        cd ../..
        get filename
        put filename
        '''

    def connect(self,ip,port):
        self.client.connect((ip,port))

    def interactive(self):
        # 验证成功:self.authenticate()就往下走
        while True:
            cmd = input(">>").strip()
            if len(cmd) == 0:continue

            # 反射
            cmd_str = cmd.split()[0]
            if hasattr(self,"cmd_%s" % cmd_str):
                func = getattr(self,"cmd_%s" % cmd_str)
                func(cmd)  # 整个cmd传进去
            else:
                self.help()

    def cmd_put(self,*args):
        cmd_split = args[0].split()
        if len(cmd_split) > 1: # get算一个 filename也算
            filename = cmd_split[1]
            if os.path.isfile(filename):
                # 告诉服务器发的文件的大小 和 文件名
                filesize = os.stat(filename).st_size
                # 一次性发文件大小 和 名字,避免粘包问题
                msg_dic = {
                    "action":"put",
                    "filename":filename,
                    "filesize":filesize,
                    "overridden":True
                }
                # socket只能传byte,json.dumps(msg_dic)变为json模式,然后再encode
                self.client.send(json.dumps(msg_dic).encode('utf-8'))
                # 防止粘包,等待服务器确认
                # 这里可以深化,根据服务器传回来进行判断,文件过大?权限不足?等
                server_response = self.client.recv(1024)
                f = open(filename,"rb")
                for line in f:
                    self.client.send(line)
                else:
                    print("文件上传完毕")
                    f.close()
            else:
                print(filename,"is not exist")

    def cmd_get(self):
        pass

ftp = FtpClient()
ftp.connect("localhost",9999)
ftp.interactive()


结果:

客户端:
>>put Taylor.txt
文件上传完毕

服务端:
127.0.0.1wrote:
b'{"action": "put", "filename": "Taylor.txt", "filesize": 565, "overridden": true}'
file [Taylor.txt] has uploaded....

作业2:开发一个批量主机管理工具
需求:
1.可以对机器进行分组
2.可以对指定的一组或多组机器执行批量命令,分发文件(发送\接收)
3.纪录操作日志

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值