Python C/S模式 socket网络编程之WebApi心跳模型
- 一、案例要求
- 二、代码实现
- 1.源代码
- 2. 要点讲解
- 三、效果演示
一、案例要求
在本实验中,意图从客户端主动、定时发起请求,从服务器端拉取下发到客户端的数据——这解决了数据下行(从服务器端到客户端)要求如下:
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积累的数字就会比较多,后面的话就会趋于平稳。