python制作物联网设备终端服务器并提供http查询接口

一、思路

物联网设备与服务器通讯用的是TCP,http查询用的也是TCP。所以初步设想是创建两个socket套接字同时监听两个端口,我这里采用8001端口与设备通讯,8002端口用于http查询,同时开2个线程工作。主要流程如下:

主要流程图

循环监听8001端口,一旦有设备发送数据过来,判断数据内容,并根据数据信息内容分别响应设备或存入内容字典响应应用端的http请求。

循环监听8002端口,一旦应用端有发请求,判断请求内容,根据请求内容直接返回或查询设备后返回。

二、存在问题

这里存在一个问题点:当有应用端需要查询设备状态信息,通过8002端口发请求,8002端口监听程序将请求再转发设备,之后响应给应用端,一旦设备响应未能及时放入内容字典,此处查询为空,会出现需要再次查询的问题。

另一个问题点就是设备在线状态的判断问题,我这里用一个在线字典来存储设备在线信息:在线字典包含设备编号,设备socket套接字,登记时间,一旦有设备发送登陆帧或心跳帧,更新在线字典的登记时间。然后另开一个线程,轮询在线字典,一旦登记时间与当前时间超过一定差值,判断为掉线,并删除在线字典里的设备信息。

三、具体实现

1、8001端口设备监听程序

class MyDeviceServer(socketserver.BaseRequestHandler):
    """
    监听设备:
    1、循环接收设备发送的数据;
    2、将数据发给转化函数解析相关内容并生成回复帧;
    3、将回复帧发给设备,消息内容存进内容字典
    """
    def handle(self):
        conn = self.request  # request里封装了所有请求的数据

        while True:
            recv_data = conn.recv(1024)
            if recv_data:
                # 解码数据并输出
                recv_text = recv_data
                logging.info('来自[%s]的信息:%s' % (self.client_address, recv_text))  # 记录接收数据日志
                fb_text = frame_calc.recv_to_send(recv_text, self.client_address, online_dict)
                try:
                    socket_dict[fb_text[1]] = conn   # 连上的设备套接字对象扔进列表
                    # 生成回复帧 返回是否需要发送,设备id,发送内容
                except Exception as e:
                    logging.warning('收到的数据%s格式错误%s' % (recv_text, e))
                    continue
                else:
                    if fb_text[0]:   # 需要返回发送
                        conn.sendall(fb_text[2])
                        logging.info('发送返回帧:%s' % fb_text[2])
                    else:   # 不需要返回帧,提取消息体内容放入内容字典
                        content_dict[fb_text[1]] = fb_text[2]
            else:
                break
        conn.close()

2、8002 http监听程序

class MyHttpServer(socketserver.BaseRequestHandler):
    """
    网页服务器,响应设备查询请求,包括设备列表,设备状态等。
    xxxx:6001/list/:显示在线列表
    xxxx:6001/status/T9000010:显示设备状态
    xxxx:6001/turnon/T9000010:开启设备
    xxxx:6001/restart/T9000010:设备重启
    """
    def handle(self):
        """接收http请求,并发送相关指令给设备"""
        conn = self.request  # request里封装了所有请求的数据
        device_list = json.dumps(online_dict)   # 在线设备列表字典转为json格式
        response = 'HTTP/1.1 200 OK \r\n'
        response += 'Content-Type: text/html; charset=utf-8\r\n'
        response += '\r\n'   # http 响应头
        dl = response + device_list  # 设备列表
        data = conn.recv(1024).decode()  # 提取网页的接收数据
        request = data.splitlines()   # 数据分行转成列表
        ret = re.match(r'[^/]+(/[^ ]*)', request[0])  # 正则匹配网址信息
        file_name = ret.group(1)
        # 匹配出网址包含的信息,并根据信息判断
        if file_name == '/':
            conn.sendall(dl.encode('utf-8'))

        elif file_name == '/favicon.ico':
            conn.close()

        elif file_name == '/list/':
            conn.sendall(dl.encode('utf-8'))

        elif file_name[:8] == '/status/':
            d_id = file_name[8:16]
            device_id = d_id.upper()

            if device_id in socket_dict:  # 收到网页查询指令
                msg_id = ['0x10', '0x5']   # 查询指令的消息代码
                d_id_list = frame_calc.byte_to_list(device_id.encode('utf-8'))  # 设备id转成列表
                send_list = frame_calc.create_frame(d_id_list,msg_id)  # 生成查询指令帧
                s = socket_dict[device_id]  # 提取该设备链接套接字
                s.sendall(frame_calc.list_to_byte(send_list))   # 向设备发送查询指令
                # 向设备发送查询指令,并等设备响应,如设备不能及时响应,会输出错误信息
                if device_id in content_dict:  # 内容字典是否有该设备的返回内容
                    content = content_dict[device_id]   # 提取设备返回内容并删除
                    del content_dict[device_id]
                    s_msg = content[3]   # 设备状态信息 0x1:开启,0x2暂停, 0x3散热,0x4:standy;0x5: 二维码状态;
                    conn.sendall((response+s_msg).encode('utf-8'))
                    conn.close()

                else:
                    s_msg = device_id + '发送指令未收到返回值,请重试!'
                    conn.sendall((response + s_msg).encode('utf-8'))

            else:
                msg = '设备不在线!'
                conn.sendall((response + msg).encode('utf-8'))
                conn.close()

        elif file_name[:8] == '/turnon/':   # 收到开机指令
            d_id = file_name[8:16]   # 获取设备id
            device_id = d_id.upper()
            if device_id in socket_dict:  # 收到网页开机指令,先发送查询指令查询设备状态
                msg_id = ['0x10', '0x5']  # 查询指令的消息代码
                d_id_list = frame_calc.byte_to_list(device_id.encode('utf-8'))
                send_list = frame_calc.create_frame(d_id_list, msg_id)
                s = socket_dict[device_id]  # 提取该设备链接套接字
                s.sendall(frame_calc.list_to_byte(send_list))  # 向设备发送查询指令
                # 向设备发送查询指令,并等设备响应,如设备不能及时响应,会输出错误信息
                if device_id in content_dict:
                    content = content_dict[device_id]
                    del content_dict[device_id]
                    if content[3] == '0x5':   # 待机状态才可开机
                        msg_id = ['0x10', '0x4']  # 开机指令的消息代码
                        frame_no = ['0x0', '0x10']  # 指令流水号
                        d_id_list = frame_calc.byte_to_list(device_id.encode('utf-8'))
                        msg_content = ['0x0','0x3','0x1','0x0','0x0','0x0','0x0','0x0']   # 开机指令消息体内容
                        send_list = frame_calc.create_frame(d_id_list, msg_id,frame_no,msg_content)
                        s.sendall(frame_calc.list_to_byte(send_list))  # 向设备发送开机指令
                        # print('开机指令是:', frame_calc.list_to_byte(send_list))
                        s_msg = '已经向设备发送开机指令'

                    elif content[3] == '0x1':
                        s_msg = '设备已经开启,请换一台设备!'

                    elif content[3] == '0x2':
                        s_msg = '设备处于暂停状态,请按设备上开机键开启!'

                    elif content[3] == '0x3':
                        s_msg = '设备处于散热状态,请稍等后再开启!'

                    elif content[3] == '0x4':
                        s_msg = '设备处于standby状态,直接按设备上的开机键即可!'

                    else:
                        s_msg = '设备状态不明,请检查设备'
                    conn.sendall((response + s_msg).encode())

                else:
                    s_msg = device_id + '发送指令未收到返回值,请重试!'
                    conn.sendall((response + s_msg).encode('utf-8'))
            else:
                msg = '设备不在线!'
                conn.sendall((response + msg).encode())
                conn.close()

        else:
            not_found = '没找到对应页面,请检查网址!'
            conn.sendall((response+not_found).encode('utf-8'))

        conn.close()

3、掉线检测

def on_line_off():
    """
    设备掉线判断:
    1、设备发送登陆帧或心跳帧将设备编号及接收到的时间添加到在线字典;
    2、循环检测当前时间与在线字典里设备最后一次时间差,若超过1分钟判断为掉线,删除在线字典里相关信息,并删除套接字字典该设备信息;
    3、日志记录掉线信息
    :return:
    """
    while True:
        for k in list(online_dict.keys()):
            if int(time.time()) - int(online_dict[k][1]) > 60:  # 超过当前时间值1分钟判断为掉线
                del online_dict[k]
                del socket_dict[k]
                logging.info('%s is off line!' % k)
        time.sleep(20)

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值