Python网络编程二黏包问题、socketserver、验证合法性

TCP/IP网络通讯粘包问题

案例:模拟执行shell命令,服务器返回相应的类容。发送指令的客户端容错率暂无考虑,按照正确的指令发送即可。

服务端代码

# -*- coding: utf-8 -*-

# 声明字符编码
# coding:utf-8

import socket
import subprocess


def server_Tcp():
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(('', 9999))
    server.listen(10)

    while True:
        client_socket, client_info = server.accept()
        print("%s 已连接" % str(client_info))
        try:

            while True:
                client_msg = client_socket.recv(1024)
                if not client_msg: break
                print("%s 收到指令<<%s" % (client_info, client_msg.decode('utf-8')))
                # 处理执行的命令
                res = subprocess.Popen(client_msg.decode('utf-8'), shell=True, stderr=subprocess.PIPE,
                                       stdout=subprocess.PIPE, stdin=subprocess.PIPE)
                err = res.stderr.read()
                if err:
                    cmd_err = err
                else:
                    cmd_err = res.stdout.read()

                # print(cmd_err.decode("gbk"))
                client_socket.send(cmd_err)
        except Exception as e:
            print("%s 已断开" % str(client_info))
            print(e)
            continue
        client_socket.close()

    server.close()


if __name__ == "__main__":
    server_Tcp()

客户端代码

# -*- coding: utf-8 -*-

# 声明字符编码
# coding:utf-8

import socket


def client_Tcp():
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    client.connect(('127.0.0.1', 9999))

    while True:
        msg = input('>>').strip()
        if not msg: continue
        if msg == 'quit': break
        client.send(msg.encode('utf-8'))
        result = client.recv(1024)
        print(result)
        print("<<%s" % (result.decode('utf-8')))

    client.close()


if __name__ == "__main__":
    client_Tcp()

说明:python中执行命令的指令模块是

import subprocess
  # 处理执行的命令
  res = subprocess.Popen(client_msg.decode(‘utf-8’), shell=True, stderr=subprocess.PIPE,stdout=subprocess.PIPE, stdin=subprocess.PIPE)
执行演示

首先运行服务端,然后运行客户端输入ipconfig指令。

服务端
在这里插入图片描述
客户端
在这里插入图片描述
我们在windows下的cmd命令下看看,或者是在shell客户端查看Linux系统的一样
在这里插入图片描述
以上客户端接收的结果为什么不是完整的简要说明

首先我们的客户端和服务端在电脑中其实就是两个程序。也就是应用程序,应用程序是无法和硬件接触的,应用程序去调用socket层去处理收发数据,而socket后面是去和操作系统对接,最后我们的收发数据是由操作系统去和硬件对接完成的。我们的数据都是在内存中读取的。如下图
 在这里插入图片描述
当服务器一次性发送了10M数据,然而客户端每次接收2M这时候客户端就没有接收到完整的数据,下次接收还是在接收上次服务器发送的数据,也就是说,服务端可以发送任意大小的数据(配置条件下),而客户端不知道这次接收多大的数据就产生了黏包。

还有一种情况是服务端连续多次send如果这几次send相隔很近,TCP内部机制会将这个几次send组包为一次send发送内容。

在上述案例中我输入第二个指令dir 结果接收的还是第一次的内容
在这里插入图片描述
解决黏包方法之一

思路是在我们每次发送的信息中封装一个头部信息,告诉对方这次发送数据的大小这样客户端就知道要接收多少数据量了。

服务端

# -*- coding: utf-8 -*-

# 声明字符编码
# coding:utf-8

import socket
import subprocess
import struct

BUFFER_SIZE = 1024


def server_Tcp():
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(('', 7777))
    server.listen(10)

    while True:
        client_socket, client_info = server.accept()
        print("%s 已连接" % str(client_info))
        try:

            while True:
                client_msg = client_socket.recv(BUFFER_SIZE)
                if not client_msg: break
                print("%s 收到指令<<%s" % (client_info, client_msg.decode('utf-8')))
                # 处理执行的命令
                res = subprocess.Popen(client_msg.decode('utf-8'), shell=True, stderr=subprocess.PIPE,
                                       stdout=subprocess.PIPE, stdin=subprocess.PIPE)
                err = res.stderr.read()
                if err:
                    cmd_err = err
                else:
                    cmd_err = res.stdout.read()

                # # 第一种方式:解决粘包问题
                # msg_len = len(cmd_err)
                # print("数据长度为:", msg_len)
                # client_socket.send(str(msg_len).encode('utf-8'))
                # # 马上等待回复
                # is_ok = client_socket.recv(BUFFER_SIZE)
                # if is_ok == b"OK":
                #     client_socket.send(cmd_err)

                # 第二种方式:解决粘包问题
                msg_len = len(cmd_err)
                msg_len = struct.pack('i', msg_len)
                # 下面两次发送,在客户端会当成一次接收
                client_socket.send(msg_len)
                client_socket.send(cmd_err)

        except Exception as e:
            print("%s 已断开" % str(client_info))
            print(e)
            continue
        client_socket.close()

    server.close()


if __name__ == "__main__":
    server_Tcp()

客户端

# -*- coding: utf-8 -*-

# 声明字符编码
# coding:utf-8

import socket
import struct

BUFFER_SIZE = 1024


def client_Tcp():
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    client.connect(('127.0.0.1', 7777))

    while True:
        msg = input('>>').strip()
        if not msg: continue
        if msg == 'quit': break

        client.send(msg.encode('utf-8'))

        # # 第一种方式:解决粘包问题
        # content_length = client.recv(BUFFER_SIZE)
        # # 规定好第一次发来的内容是内容大小,给服务器回复个OK
        # client.send(b"OK")
        # content_length = int(content_length.decode('utf-8'))
        # 第二种方式:解决粘包问题
        # 先接收四个字节
        length_data = client.recv(4)
        content_length = struct.unpack('i', length_data)[0]

        print("准备接收%d大小的数据")
        recv_size = 0
        recv_msg = b''
        # 循环获取数据
        while recv_size < content_length:
            recv_msg += client.recv(BUFFER_SIZE)
            recv_size = len(recv_msg)

        print("<<%s" % (recv_msg.decode('gbk')))

    client.close()


if __name__ == "__main__":
    client_Tcp()

当我们再次运行后输入ipconfig命令,这时候客户端收到的就是完整的内容了。截图略…

总结:

TCP是面向连接的,传输是基于数据流的,收发两端进行连接,数据传输,断开连接都是要经过来回的确认。TCP协议数据传输是不会丢失的,一次没有接收完,下次依然会接收,直到一端接收到ack时才知道数据接收完成。

UDP是无连接的。面向消息的,不管发送的是什么内容,内部都会为当前消息组装一个报文头。

UDP不会产生粘包问题,因为UDP只收一次,每次都会有个报文头+内容

socketserver ------多线程服务

用法很简单,在服务端代码中,创建一个类,让这个类继承BaseRequestHandler 然后在类中实现父类方法handle ,客户端代码不需做改变。详见代码

服务端

# -*- coding: utf-8 -*-

# 声明字符编码
# coding:utf-8

import socketserver

BUFFER_SIZE = 1024
ADDRESS_IP = ('172.16.6.5', 7777)


class Communication(socketserver.BaseRequestHandler):

    def handle(self):
        print(self.request)  # ------->
        print(self.client_address)
        while True:
            try:
                client_msg = self.request.recv(BUFFER_SIZE)
                if not client_msg:
                    break

                print("客户端【%s】>>%s" % (self.client_address, client_msg))
                self.request.sendall(client_msg.upper())
            except Exception as e:
                print(e)
                break


if __name__ == "__main__":
    server = socketserver.ThreadingTCPServer(ADDRESS_IP, Communication)
    server.serve_forever()

客户端代码

# -*- coding: utf-8 -*-

# 声明字符编码
# coding:utf-8

import socket

BUFFER_SIZE = 1024
ADDRESS_IP = ('172.16.6.5', 7777)


def client_Tcp():
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect(ADDRESS_IP)

    while True:
        msg = input('>>:')
        client.sendall(msg.encode('utf-8'))

        server_msg = client.recv(BUFFER_SIZE)
        print("<<%s" % (server_msg.decode('utf-8')))

    client.close()


if __name__ == "__main__":
    client_Tcp()

这样就可以实现服务端响应多个客户端了。

验证合法性

案例:写一个服务端,客户端。服务端验证客户端是否是合法的,验证通过的客户端才能正常进行通讯。

思路:和解决粘包思路一样,在头部再次封装一个验证的信息,当客户端连接服务成功后,服务端发送一个验证信息,客户端接收后通过key值加密后发回给服务端,服务端接收客户发来的密文和自己的进行比较,相同则是合法的。

服务端

# -*- coding: utf-8 -*-

# 声明字符编码
# coding:utf-8

import socketserver
import struct
import os
import hmac

BUFFER_SIZE = 1024
BIND_IP_PORT = ('172.16.6.5', 9999)
AUTO_KEY = b"bc892ed2-8b2c-11e8-a328-f40669b72fef"


class Communication(socketserver.BaseRequestHandler):
    """ 通讯类型 """

    def handle(self):
        # print(self.client_address)
        # print(self.request)
        cl, pl = self.client_address
        print("[%s:%s]客户端已连接" % (cl, pl))
        # 合法验证
        if conn_auth(self.request) == True:
            # 循环接收客户端信息
            while True:
                try:
                    # 等待客户端消息
                    # 数据粘包处理
                    # 数据的前四位存储的是数据的大小
                    # 先接收四个字节
                    length_data = self.request.recv(4)
                    if not length_data: break

                    content_length = struct.unpack('i', length_data)[0]

                    print("接收客户端[%s][%d]大小的数据" % (self.client_address, content_length))
                    recv_size = 0
                    recv_msg = b''
                    # 循环获取数据
                    while recv_size < content_length:
                        recv_msg += self.request.recv(BUFFER_SIZE)
                        recv_size = len(recv_msg)
                    print("[%s]>>%s" % (self.client_address, recv_msg.decode('utf-8')))
                    # 服务器回复客户端
                    server_msg = input("<<")

                    server_send_length = struct.pack('i', len(server_msg))

                    self.request.sendall(server_send_length)
                    self.request.sendall(server_msg.encode("utf-8"))
                except Exception as e:
                    print("[%s]客户端异常关闭" % self.client_address)
                    print(e)
        else:
            print("客户端【%s:%s】非法接入" % (cl, pl))


def conn_auth(conn):
    # 合法性验证
    msg = os.urandom(32)
    conn.sendall(msg)
    h = hmac.new(AUTO_KEY, msg)
    digest = h.digest()
    respone = conn.recv(len(digest))
    return hmac.compare_digest(respone, digest)


if __name__ == "__main__":
    server = socketserver.ThreadingTCPServer(BIND_IP_PORT, Communication)
    server.serve_forever()

客户端

# -*- coding: utf-8 -*-

# 声明字符编码
# coding:utf-8

import socket
import struct
import os, hmac

BUFFER_SIZE = 1024
BIND_IP_PORT = ('172.16.6.5', 9999)
AUTO_KEY = b"bc892ed2-8b2c-11e8-a328-f40669b72fef"


def client_Tcp():
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    client.connect(BIND_IP_PORT)

    # 合法性验证
    conn_auth(client)
    while True:

        msg = input('>>').strip()
        if not msg: continue
        if msg == 'quit': break

        server_send_length = struct.pack('i', len(msg))
        client.send(server_send_length)
        client.send(msg.encode('utf-8'))
        # # 第一种方式:解决粘包问题
        # content_length = client.recv(BUFFER_SIZE)
        # # 规定好第一次发来的内容是内容大小,给服务器回复个OK
        # client.send(b"OK")
        # content_length = int(content_length.decode('utf-8'))
        # 第二种方式:解决粘包问题
        # 先接收四个字节
        length_data = client.recv(4)
        content_length = struct.unpack('i', length_data)[0]

        print("准备接收服务端[%d]大小的数据" % content_length)
        recv_size = 0
        recv_msg = b''
        # 循环获取数据
        while recv_size < content_length:
            recv_msg += client.recv(BUFFER_SIZE)
            recv_size = len(recv_msg)

        print("<< %s" % (recv_msg.decode('utf-8')))

    client.close()


def conn_auth(conn):
    msg = conn.recv(32)
    h = hmac.new(AUTO_KEY, msg)
    digest = h.digest()
    conn.sendall(digest)


if __name__ == "__main__":
    client_Tcp()

程序跑起来…

客户端发送一个内容
在这里插入图片描述
服务端接收
在这里插入图片描述
当我把客户端的key值改一下试试…

客户端连接服务端后,服务端进行验证不通过。
在这里插入图片描述

  • 34
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值