python网络编程

1,socket通信

python网络编程 

2,socket对象的参数

socket families:网络层

    socket.AF_INETIPV4

    socket.AF_INET6IPV6

    socket.AF_UNIX unix本机进程间通信

socket types:传输层

    socket.SOCK_STREAM #TCP

    socket.SOCK_DGRAM #UDP    

    socket.SOCK_RAW  #原始套接字,可以伪造IP头

    socket.SOCK_RDM #UDP,保证传到,但不保证顺序

3,黏包

场景:

单次接收设置1024,但是实际数据大于1024,多于1024的内容会再下次接收到

服务器连续两次send,可能会被放进缓冲区形成黏包

 

解决方法

计算发送内容大小,先把大小发送给接收方,发送完大小后服务器必须加阻塞确认,防止黏包

拿到内容大小后,后续如果还有多次send多问题,就可以改用精确接收内容大小防止黏包了,无需每次阻塞确认

特别注意中文str长度1,变成bytes后长度是3

4,模拟SSH

服务器端:

import socket
import os

server = socket.socket()
server.bind(('localhost', 19999))  # 绑定端口
server.listen(3)  # 监听端口,这里不是并发
while True:
    conn, addr = server.accept()  # 等待连接,进入阻塞状态
    while True:
        cmd = conn.recv(1024)  # 接收大小是1024
        if not cmd:  # linux会陷入recv死循环,建议都加上防止反复接收死循环
            break
        cmd = cmd.decode()
        cmd_res = os.popen(cmd).read().encode()

        cmd_lens = len(cmd_res)
        conn.send(str(cmd_lens).encode())
        client_ack = conn.recv(1024)   # 加个确认防止黏包
        conn.send(cmd_res) 

        # server.close()  # 这里如果加close,server不会立即关闭,会在当前client结束后再关

客户端:

import socket

client = socket.socket()
client.connect(('localhost', 19999))
while True:
    msg = input('>>: ').strip()
    if not msg:
        continue
    elif msg == 'exit':  # 退出循环,关闭连接
        break

    client.send(msg.encode())  # 只能send bytes,默认utf-8
    cmd_res_size = client.recv(1024)
    cmd_res_size = int(cmd_res_size.decode())  # bytes -> str -> int
    client.send(b'ack')  # 发送确认,解决长度与实际data之间的黏包问题

    received_size = 0
    received_data = b''
    while received_size < cmd_res_size:
        data = client.recv(1024)
        received_size += len(data)
        received_data += data

    print(received_data.decode())

client.close()

5,socket接收文件

流程:读取文件名,检测文件是否存在,打开文件,检测文件大小,发送文件大小给客户端,等待客户端确认,边读边发,发送MD5

服务器端:

import socket
import os
import hashlib

server = socket.socket()
server.bind(('localhost', 19999))
server.listen()

while True:
    conn, addr = server.accept()
    while True:
        data = conn.recv(1024)
        if not data:
            break
        cmd, filename = data.decode().split()
        if os.path.isfile(filename):
            f = open(filename, 'rb')  # 'rb'打开,后面不用encode了
            file_size = os.stat(filename).st_size
            conn.send(str(file_size).encode())  # 发送文件大小
            conn.recv(1024)  # 等待确认
            m = hashlib.md5()
            for line in f:   # 发送文件,f是迭代器
                m.update(line)  # 逐行更新计算MD5
                conn.send(line)
            f.close()
            conn.send(m.hexdigest().encode())  # 将MD5发送给客户端

客户端:

使用get + 文件名,获取文件

import socket
import hashlib

client = socket.socket()
client.connect(('localhost', 19999))

while True:
    cmd = input('>>: ').strip()
    if not cmd:
        continue
    elif cmd == 'break':
        break
    elif cmd.startswith('get'):
        client.send(cmd.encode())  # 只能send bytes,默认utf-8
        file_size = client.recv(1024)
        file_size = int(file_size.decode())  # bytes -> str -> int
        client.send(b'ack')  # 发送确认,解决长度与实际data之间的黏包问题

        received_size = 0
        file_name = cmd.split()[1]
        m = hashlib.md5()

        f = open(file_name + '.new', 'wb')
        while received_size < file_size:
            size = 1024
            if file_size - received_size > 1024:
                buff = file_size - received_size  # 最后一次收实际数据,防止黏包把服务器MD5写入文件
            data = client.recv(size)
            received_size += len(data)
            m.update(data)
            f.write(data)
        f.close()

        client_file_md5 = m.hexdigest()
        server_file_md5 = client.recv(1024).decode()

        print('客户端文件MD5:', client_file_md5)
        print('服务器端文件MD5:', server_file_md5)

client.close()

6,socketserver

对socket进行二次封装,简化了socket服务器端编写,实现服务器端并发

socketserver实现模拟SSH服务器端(客户端不变):

class TCPHandler(socketserver.BaseRequestHandler):

    def handle(self):
        while True:  # 不写while True,每个连接只能处理一次命令
            cmd = self.request.recv(1024)
            if not cmd:  # cmd为空代表客户端断开了,不做非空判断客户端断开时可能会无限循环
                break
            cmd = cmd.decode()
            cmd_res = os.popen(cmd).read().encode()
            cmd_lens = len(cmd_res)
            self.request.send(str(cmd_lens).encode())
            self.request.recv(1024)
            self.request.send(cmd_res)


if __name__ == '__main__':
    server = socketserver.TCPServer(('localhost', 9999), TCPHandler)
    server.serve_forever()

 将TCPServer改成ThreadingTCPServer可以实现多并发