套接字(socket) + 模拟ssh (subprocess) + 黏包 (struct) + hashlib 模块

 

套接字 (socket)  

1. socket 层   

 

2. TCP协议 和 UDP协议

TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。

UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。

 

3.  基于TCP协议的 socket 

tcp是基于链接的,必须先启动服务端,然后再启动客户端取链接服务端.

socket使用前,需要导入. import   socket

示例 : 

  登录验证, 在客户端进行登录, 在服务端进行验证, 返回登录结果

# 在服务端存储的用户名和密码,目前暂时存储在文件 account.txt 中
chen:123
zhang:123
liu:456
du:456
# sever 端  
import socket

sock=socket.socket() # TCP协议  # 创建socket对象
IP_PORT=("192.168.13.22",8899)  # 地址和端口用括号
sock.bind(IP_PORT)  # 绑定
sock.listen(5) # 设置等待数为5

while 1:
    conn,addr=sock.accept()  # 等待连接,返回  conn全收发管道(套接字对象) 和 addr客户端地址信息
连接对象conn和客户端地址信息addr
while 1: try: # 需要进行异常处理 避免客户端主动或意外关闭程序 data=conn.recv(1024).decode("utf8") # 接受数据,收到的是字节,要解码 user,pwd=data.strip().split("|") flag=False with open("account","r") as f: # 文件操作 for line in f: username,password=line.strip().split(":") if username==user and password==pwd: flag=True break if flag: conn.send(b"success") # 发送的必须是字符串且为字节型 else: conn.send(b"fail") except Exception as e: # 捕获异常,当客户端主动退出程序时 break # 捕获异常后,服务端也退出循环,否则会报错
# client 端  
import socket

sock=socket.socket() # TCP
sock.connect(("192.168.13.22",8899))

while 1:
    user=input("用户名>>>>")
    pwd=input("密码>>>>")
    val=("%s|%s"%(user,pwd)).encode("utf8")
    sock.send(val)    # 发送用户名和密码
    response=sock.recv(1024)  # 接收返回的值 为字节型的
    print(response.decode("utf8"))
    if response.decode("utf8")=="success":
        print('登录成功!')
        break
    else:
        print("用户名或者密码错误!")
        continue

4.  基于UDP协议的 socket 

udp是无连接的,启动服务之后可以直接接受消息不需要提前建立连接 

简单示例 :

# sever 端  
import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM)   #创建一个服务器的套接字
udp_sk.bind(('127.0.0.1',9000))        #绑定服务器套接字
msg,addr = udp_sk.recvfrom(1024)
print(msg)
udp_sk.sendto(b'hi',addr)                 # 对话(接收与发送)
udp_sk.close()                         # 关闭服务器套接字
# client 端  
import socket
ip_port=('127.0.0.1',9000)
udp_sk=socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello',ip_port)
back_msg,addr=udp_sk.recvfrom(1024)
print(back_msg.decode('utf-8'),addr)

 

总结 : (基于tcp)

  1. socket 对象使用时,服务端和客户端要注意 收发配合好,两边不能同时收,或者同时发.

  2. socket 中 send() 发送的内容不能为空, 有时候需要用 if来判断.

  3. 客户端和服务端要保证双方退出,强迫关闭一个现有的连接,如果客户端主动退出程序(红按钮),或者用break退出循环程序,服务端还处在接收的状态,需要用try,处理异常,捕获到异常后,要break退出循环,不再接收. 注意,try处理异常是针对windows系统的. linux系统和其他系统不需要用try处理异常.

模拟ssh (subprocess)

ssh 是指在客户端输入命令去操作服务端.

知识补充 :  subprocess 模块

subprocess是Python 2.4中新增的一个模块,它允许你生成新的进程,连接到它们的 input/output/error 管道,并获取它们的返回(状态)码。这个模块的目的在于替换几个旧的模块和方法,如:

  • os.system
  • os.spawn*

♥♥♥  示例 : 使用 subprocess 模块可以运行 cmd(终端) 命令. 类似于os.system()

# 远程执行命名程序
import
subprocess res=subprocess.Popen("dir", shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE) con = res.stderr.read() # 如果命令错误可以获取错误结果 ret = res.stdout.read() # 获取的是字节型的,dir命令后要展示的内容, print(ret) # print(ret.decode('utf-8')) # 报错 print(ret.decode('gbk')) # 由于window系统终端中用的是gbk编码的,因此需要用gbk解码
'''
ret结果:
b' \xc7\xfd\xb6\xaf\xc6\xf7 E \xd6\xd0\xb5\xc4\xbe\xed\xca\xc7 DATA\r\n \xbe\xed\xb5
\xc4\xd0\xf2\xc1\xd0\xba\xc5\xca\xc7 26D4-A412\r\n\r\n E:\\E \xd7\xca\xc

ret编码后结果:
驱动器 E 中的卷是 DATA
 卷的序列号是 26D4-A412

 E:\E 资料\PythonStudy\workplace\code\ssh 的目录

2018/09/04  17:26    <DIR>          .
2018/09/04  17:26    <DIR>          ..
2018/09/04  12:29               444 hashlib模块.py
2018/09/04  12:49               810 ssh_client.py
2018/09/04  17:07             1,530 ssh_server.py
2018/09/04  12:03               122 struct模块.py
2018/09/04  17:26               547 subprocess模块.py
2018/09/04  09:59                19 __init__.py
2018/09/04  11:42    <DIR>          __pycache__
               6 个文件          3,472 字节
               3 个目录 1,951,513,604,096 可用字节
'''
结果

总结:

  1. dir 在终端是查看文件夹中的内容列表

  2. windows系统中,终端使用的是gbk编码,因此解码的时候需要用 .decode('gbk')

      linux系统终端用的是 utf-8

  3. 关于上述  res.stderr.read( )    res.stdout.read( )  只能从管道中读取一次,读取两次的话,第二次是空值.

黏包 (struct)

1. 什么是黏包?

黏包就是socket对象, .send() 发送的时候, 如果有连续的两次及以上的发送, 由于send()没有阻塞, 发送的数据在对方那里会在一起存储,无法分开,就是黏包现象.

或者说是,同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令的时候又接收到之前执行的另外一部分结果,这种显现就是黏包。

2. 黏包形成的原因

连续发送, 发送间隔时间短, 数据量小

3. 解决黏包的方法(3)中

  a.  time.sleep() ,两个send()之间使用,一般不要用,效率太低,太low

  b.  利用阻塞,服务端连续发送之间 添加recv, 客户端 send, 避免黏包

  c.  利用 struct 模块

      - 将数据的长度压缩成固定的字节,发送给服务端

      - 服务端接收后再解压获取长度

      - 通过循环, 再recv接收数据

知识补充 :  struct 模块

struct 模块可用于处理网络通信中的黏包问题.

struct.pack(  )   压缩 , 压缩后是字节型的. 

struct.unpack(  )    解压, 解压后是元组, 通过索引[0] 取得原值即可.

 ♥ 数字在某个范围之内压缩后的字节长度都是4

import struct

res=struct.pack("i",1234543)  # pack 是压缩, i模式是压缩数字,
                            # 返回的是字节型的,且无论数字是多少(范围之内)为4个字节
print(res)    # b'o\xd6\x12\x00'
print(len(res))  # 4

obj=struct.unpack("i",res)   # unpack 是解压, i模式解压的是数字, 返回的是元组
print(obj)     # (1234543,)
print(obj[0])  # 1234543 , 索引[0] 取值,即可得到原来的值

使用 struct 模块 解决黏包示例  :     

   在客户端中通过输入命令(可以使终端命令,也可以是自定义命令),远程控制服务端, 在数据发送中会出现黏包的问题.  

# sever 端   
import socket
import subprocess

server = socket.socket()
server.bind(('192.168.13.22',8008))
server.listen(5)

while True:
    print("server is working.....")
    conn,addr = server.accept()
    # 字节类型
    while True:
        # 针对window系统   异常处理
        try:
            cmd = conn.recv(1024).decode("utf8") # 阻塞

            if cmd == b'exit':
                break

            res=subprocess.Popen(cmd,
                             shell=True,
                             stderr=subprocess.PIPE,
                             stdout=subprocess.PIPE,
                             )
            out=res.stdout.read()  # 读取的是字节型的
            err=res.stderr.read()

            if err:     # send发送,需要判断是否为空
                 import struct
                 header_pack = struct.pack("i", len(err))
                 conn.send(header_pack)
                 conn.send(err)
            else:
                 #构建报头
                 import struct
                 header_pack = struct.pack("i",len(out))   # 将获取的out的长度压缩
                 conn.send(header_pack)   # 发送压缩后的 数据的长度 值
                 conn.send(out)  # 发送数据
        except Exception as e:
            break
    conn.close()
# client 端    
import socket
import struct

sk = socket.socket()
sk.connect(('192.168.13.22',8008))

while 1:
    cmd = input("请输入命令:")
    sk.send(cmd.encode('utf-8')) # 字节
    if cmd=="":
        continue
    if cmd == 'exit':
        break

    header_pack=sk.recv(4)
    data_length=struct.unpack("i",header_pack)[0]  # unpack解压后为元组,通过[0],取值
    '''
     黏包后,长度和数据在一起,关键是获取 数据长度的字节数,压缩后去字节4即可.
    b'xxx/xxx/xxx/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
    '''
    # 取数据的值,每次只能取1024,通过循环取数据,当取后的数据长度<总长度 就要recv()取值
    recv_data_length=0
    recv_data=b""
    while recv_data_length < data_length:
        data=sk.recv(1024)
        recv_data_length+=len(data)
        recv_data+=data
    print(recv_data.decode("gbk"))  # windows终端中用gbk编码,需要用gbk解码.

sk.close()

hashlib 模块

1. 为字符串添加摘要

对字符串加密,这是不可逆的算法,并且 update() 属于追加摘要.

import hashlib

md5 = hashlib.md5()
md5.update(b'hellochen')
print(md5.hexdigest()) # 获取密文
# 6e92e6e4207d0f85e6f73e7f7eb10f8a

md5 = hashlib.md5()
md5.update(b'hello')
md5.update(b'chen')   # update()是追加,
print(md5.hexdigest()) # 获取密文 结果与上面密文一样
# 6e92e6e4207d0f85e6f73e7f7eb10f8a

2. 对比文件中内容是否一致

同样的字符串,用 hashlib 中MD5或she...加密之后,得到的密文是一样的(md5是32位).通过对比密文来判断文件中的内容是否一致.

update() 中内容为什么会是追加的,用处是什么?

文件中内容特别大,不会用 f.read() 将所有的内容读取到内存中占用大量的内存,当一行行读取的时候, 可以将一行行的数据追加到update() 中,之后再获取密文就可以了.

md5=hashlib.md5()

with open("ssh_client.py","rb") as f:
    for line in f:
        md5.update(line)  # update中是字节,所以用rb模式,读取字节

print(md5.hexdigest()) # f.read()
posted @ 2018-09-04 15:52  葡萄想柠檬  Views(309)  Comments(0)    收藏  举报
目录代码