Python C/S模式 socket网络编程之WebApi心跳模型

Python C/S模式 socket网络编程之WebApi心跳模型

一、案例要求

在本实验中,意图从客户端主动、定时发起请求,从服务器端拉取下发到客户端的数据——这解决了数据下行(从服务器端到客户端)要求如下:
1、编写一个客户端、一个服务器端程序
2、服务器端程序要求:
1)只提供一个接口;
2)以WebApi方式部署
3)数据封装为Json格式
4)每500ms,生成一个随机数(int范围内),缓存到集合/数组中
5)当接口被调用时,把从上次请求后生成的随机数集合,返回给客户端,并清空对应集合
3、客户端程序要求:
1)程序内使用计时器,每10秒调用一次服务器端的WebApi
2)每次调用后,把获取回来的数据显示在控制台黑窗口上

二、代码实现

1.源代码

# encoding:utf-8
"""
server.py
"""
__author__ = 'Regan_zhx'

import socket, sys, json, time
from threading import Thread, Lock
from queue import Queue
from random import randint


class Server:
    def __init__(self, port=9999):
        self.queue = Queue() # 存储随机数的队列
        self.lock = Lock() # 全局锁
        self.HOST = socket.gethostname()  # 主机名
        self.PORT = port  # 端口
        self.BUF_SIZE = 4096  # 缓冲区大小
        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建socket套接字
        self.server.bind((self.HOST, self.PORT))  # 套接字绑定
        self.server.listen()  # 监听

    def server_thread(self, conn): #服务端线程
        global data_loaded
        while True:
            try:
                data = conn.recv(self.BUF_SIZE) #接受客户端发来的心跳包
                data_loaded = json.loads(data.decode('utf8')) #解码
                print("Receive the heart beat from ip:" + str(data_loaded['ip']) + " |status: " + data_loaded['status']
                       + " |pid: " + str(data_loaded['pid']) + " |Time: "+time.strftime('%Y-%M-%d %H:%m:%S'))
                if not self.queue.empty(): # 如果队列不为空则发送全部内容
                    num_dict = {
    
    }
                    num_id = 0
                    self.lock.acquire() # 上锁,保证线程安全
                    while not self.queue.empty():
                        num_dict[num_id] = self.queue.get()
                        num_id += 1
                    self.lock.release()
                    data_package = json.dumps(num_dict)
                    conn.sendall(data_package.encode('utf8')) # sendall发送TCP完整数据包
            except socket.error:
                print("One Client (IP: %s) Connected over!" % data_loaded['ip'])
                break
        conn.close()

    def produce_thread(self, queue): # 随机数生成单独线程
        while True:  # 每500ms生成一个数字加入到queue队列中
            num = randint(-2 ** 32, 2 ** 32) # 随机整数生成
            queue.put(num) # 放入队列
            time.sleep(0.5) # 睡眠500ms

    def run(self):
        print("Welcome to the WebApi Heart Beat Model!")
        Thread(target=self.produce_thread, args=(self.queue,)).start()  # 开启生成数字的线程
        while True:
            try:
                conn, addr = self.server.accept() # 被动接受TCP客户端连接,一直等待直到连接到达(阻塞)
                print("Connected with %s:%s " % (addr[0], addr[1]))
                Thread(target=self.server_thread, args=(conn,)).start()  # 开启线程
            except Exception as e:
                print(e)
                self.server.close()
                break


if __name__ == '__main__':
    Server().run()

# encoding:utf-8
"""
client.py
"""
__author__ = 'Regan_zhx'

import socket, sys, os
import time, json
from threading import Thread


class Client:
    def __init__(self, port=9999):
        self.host = socket.gethostname()
        self.port = port
        self.BUF_SIZE = 4096
        self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.remote_ip = socket.gethostbyname(self.host)
        self.client.connect((self.remote_ip, self.port)) # 客户端主动连接服务端
        """
        Bug在这里
        self.client.setblocking(False)
        """
        print("Socket connected to %s on ip %s" % (self.host, self.remote_ip))

    def heart_beat(self):
        while True:
            try:
                host_name = socket.gethostname()
                # 发送请求包,并告知ip,状态和进程ID
                data_to_server = {
    
    'ip': socket.gethostbyname(host_name), 'status': 'alive', 'pid': os.getpid()}
                data_dumped = json.dumps(data_to_server)
                try:
                    self.client.sendall(data_dumped.encode()) # 发送心跳数据包
                except socket.error:
                    print("Send failed!!")
                    break
                try:
                    data = self.client.recv(self.BUF_SIZE) # 接收返回数据包
                    data_package = json.loads(data.decode('utf8'))
                    print(data_package)
                except:
                    print("Receive failed!!")
                    break
            except:
                print("One Server Connected over!")
                self.client.close()
                break
            time.sleep(10) #睡眠10s后再发送心跳


if __name__ == '__main__':
    Client().heart_beat()

2. 要点讲解

本文参考自Python Socket实现心跳监测
然而原文年代久远,使用的还是Python2的语法,socket那时的结构和如今的还有些出入,不过大体都是差不多的。
Tip1:显而易见,我将服务端和客户端都稍微封装了一下让代码更易懂可读,其中类初始化参数port我以后统一设置为9999,当然这个对于读者来说是可以任意更改的,不过要保证客户端和服务端的端口一致。
Tip2:在编写的时候遇到了一个bug,那就是server端可以正常传输数据,而client却不能正常接收,可是我开启debug模式的时候程序又没有任何问题,我就下意识地查看socket的参数,也就是上文代码特地标出bug的地方,这个地方在参考文章中是setblocking(0),由于0就相当于False,我就改成了False,即非阻塞模式,非阻塞就是问题所在。非阻塞模式下,client端并不会等待服务端发送过来的数据包,而是一股脑地向下执行,这样自然触发了异常。所以只要将这句话删除或者设置为True阻塞模式即可。
Tip3:在本程序中出现了多线程的情况,这是为了顺应题目,因为关于socket的线程都会因为接受或者发送事件而发生阻塞,所以需要同时进行不同任务就要使用并发。比如produce_thread线程的启动位置也是有讲究的,不放入下层的While循环是为了在程序启动的时候让这个线程开始生成随机整数,而不是再客户端发起请求的时候再开始生成。

三、效果演示

使用Pycharm同时运行两个py程序,方便观察。
为了加速程序,设置server生成随机整数睡眠时间为0.3s,client请求时间间隔设置为1s,可以看出每条日志的时间间隔恰好也是1s。
在这里插入图片描述
由于启动后不可能那么快开启客户端,所以服务端queue积累的数字就会比较多,后面的话就会趋于平稳。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43594279/article/details/116070733