物联网网关
"""
功能定义:为智能云设备提供云服务、用户通过手机远程控制智能设别运行
项目依赖:Twisted + SQLAlchemy + ZeroMQ
OpenSSL : 安全组件库,提供安全的传输层接口 —— 完成对SSL接口的开发
ZeroMQ :高效的消息队列中间件 —— 完成不同主机间的通信
其他依赖库 : 查看 requirement.txt 文件
"""
# 安装其他依赖库
pip install -r requirement.txt # 根据文件内容进行安装依赖库
# 通过命名行新建PostgreSQL数据库
create database SBDB
# 数据库篇配置文件 - src/sbs.conf 文件中
# 将USER、PASS、DBNAME 修改为 已安装数据库的用户名、密码、数据库名
db_connection_string = 'postgresql://USER:PASS@localhost/DBNAME'
# 启动服务 src项目目录下
python main.py
# 服务监听端口(默认TCP-9630,SSL - 9631)
# src/SBPS/ProtocolReactor.py (服务器与客户端的通信引擎)
if withListen:
reactor.listenTCP(9630,instance_SBProtocolFactory)
# ......
reactor.listenSSL(9631,instance_SBProtocolFactory,cert.options())
项目结构
Command # 含所有协议的处理代码,每个协议由Command包中单独的一个命令文件和类进行处理
__init__.py
AddAccount.py # 账号
AddApartment.py # 物理分区(区域名,区域设备版本号)
Authorize.py # 通过 authorize 命令向服务器端提供身份验证信息,根据验证结果返回Response
BaseCommand.py
DB # 含所有数据库ORM对象定义 和 数据访问逻辑
__init__.py
SBDB.py
SBDB_ORM.py
emuSBPS # 模拟客户端的引擎,为 emuHuman.py 和 emuRelayer.py 提供通信接口
__init__.py
ControlDevice.py
emuReactor.py
QuerDevice.py
SBPS # 服务通信引擎,包括服务器内部通信引擎 (InternalMessage.py)、服务器与客户端通信引擎(ProtoocolReator.py)
__init__.py
InternalMessage.py # 集群通信
ProtocolReactor.py # 与客户端通信
Utils # 通用工具包
__init__.py
Config.py
Util.py
main # 服务器主文件,加载各个通信模块,并启动通信引擎为客户端提供服务
server.pem # SSL通信的服务器证书文件
emuHuman.py emuRelayer.py # 客户端模拟程序,通过他们测试服务器的功能模块
跨平台安全入口
"""
windows : 通过 IOCP 完成端口,作为底层接口
linux :使用Epoll 作为底层通信管理接口
"""
# **Utils.Util** 判断函数再什么程序中运行
import platform
platform_system = platform.system().lower()
# 如果平台名称中有 "win",则认为是 windows 平台
def isWindwos():
return platform_system.find("win")>=0
# **scr/main.py** 中
from Utils import Util
# 判断操作系统,以什么方式运行
if Util.isWindows():
from twisted.internet import iocpreactor
# 以 IOCP 方式运行
iocpreactor.install()
else:
from twisted.internet import epollreactor
# 以 Epoll 方式运行
epollreactor.install()
# 配置日志文件、级别、格式
import logging
loging.basicConfig(filename='example.log',level=logging.INFO,format="%(asctime)s-%(name)s-%(levelname)s-%(message)s")
# **IotGateway 系统通信引擎**
# SBPS.ProtocolReactor 服务器与客户端的通信引擎
import SBPS.ProtocolReactor as ProtocolReactor
if__name__=='__main__':
logging.info("Relayer Server start...")
ProtocolReactor.Run() # 挂起服务
系统通信引擎-src/SBPS/ProtocolReactor.py
# ProtocolReactor.py 服务器与客户端通信引擎文件
from twisted.internet import reactor,ssl
# 导入集群通信文件 InternalMessage
from SBPS import InternalMessage
def Run(withListen=True):
# SBProtocolFactory 是 ServerFactory 子类,在下面的函数中进行定义,负责维护所有客户端的连接
# SBProtocol 是 Twister 框架中 Protocol
instance_SBProtocolFactory = SBProtocolFactory()
if withListen:
# 监听普通TCP
reator.listenTCP(9630,instance_SBProtocolFactory)
cert = None
# 加载服务器证书
with open('server.pem') as keyAndCert:
cert = ssl.PrivateCertificate.loadPEM(keyAndCert.read())
# 监听 SSL 端口
reactor.listenSSL(9631,instance_SBProtocolFactory,cert.options())
reator.run() # 启动通信引擎
管理连接
# src/SBPS/ProtocolReactor.py (服务器与客户端的通信引擎)
# SBProtocolFactory 负责维护所有的客户端连接
from twisted.internet.potocol,ServerFactory
import threading # 线程
"""
class SBProtocol(Protocol)
1、def __init__(self) —— 初始化
2、def dataReceived(self, data) —— AddDataAndDecode(回调函数) —— 接收数据时调用
3、def connectionMade(self) —— 连接时调用(状态变化)
4、def RunCommand(self,command)
5、def AddDataAndDecode(self,lock,data) —— 耗时操作的回调函数
6、def Decode(self,data)
7、def connectionLost(self, reason=twistedError.ConnectionDone) —— 关闭连接时调用
8、def timeout(self) —— 超时处理函数
9、def isDeadSession(self)
10、def releaseFromDict(self):
"""
# **管理客户端连接的类**
class SBProtocol(Protocol):
connection_count=0 # 连接总数
countPendingCmd=0 # 正在处理的命令数
def __init__(self):
'''
Constructor
'''
self.m_buffer="" # 接收缓冲区
self.lockBuffer=DeferredLock()
self.tmActivate=time.time() # 最近活跃时间
self.dictWaitResp={} # 待反馈命令表
self.lock_dictWaitResp=threading.RLock()
self.dictControlling={} # 控制指令表
self.cond_dictControlling=threading.Condition()
#self.timer=threading.Timer(Config.time_heartbeat,self.timeout)
#self.timer.start()
self.timer=reactor.callLater(Config.time_heartbeat,self.timeout) # 超时定时器
self.lockCmd=threading.RLock()
self.HeaderTagType=-1 #-1: not decided, 0: no header_tag, 1: has header_tag
self.rcv_alarm="False"
self.role=""
# 接收数据时调用
def dataReceived(self, data):
Protocol.dataReceived(self, data)
self.lockBuffer.acquire().addCallback(self.AddDataAndDecode,data)
# 建立连接时调用
def connectionMade(self):
#print "a connection made: ", id(self.transport), self.transport.getPeer().host
ip=self.transport.getPeer().host
# if ip.find("10.")!=0:
# logging.info("a connection made:%s,%s ", id(self.transport), ip)
# pass
#
with self.factory.lockPendingCmd:
SBProtocol.connection_count=SBProtocol.connection_count+1
if SBProtocol.connection_count>Config.count_connection:
self.transport.loseConnection()
print "close connection due to reaching connection limit."
# 辅助线程中执行的函数
def RunCommand(self,command):
with self.factory.lockPendingCmd:
SBProtocol.countPendingCmd=SBProtocol.countPendingCmd-1
command.Run()
# 接收数据时,需要处理字节流的粘包,拆分情况,保证收到完整数据段后才进行命令的解析和执行工作
"""
1、AddDataAndDecode() 将所有接收到的消息合并到接收缓冲区中
2、根据缓冲区的长度判断是否已经接收到完整的消息头,如果已经接收到,则调用 Decode() 函数尝试解包
3、Decode() 函数中,首先是除去消息的固定头
4、使用strct.unpack() 函数将字节流转化 为python 数据格式,从而得到命令长度、命令ID.
5、使用命令ID 从 Command.dicInt_type 中建立新的命令对象,其中dicInt_Type是一个命令对象类型字典,内容是所有命令ID 和 命令类的映射。该字典在 Command 包的__init__.py 文件中进行初始化。
"""
def AddDataAndDecode(self,lock,data):
print "data received in transport %d : %s (%s)" % (id(self.transport),Util.asscii_string(data),data)
self.m_buffer+=data # 将新接收到的数据放入 接收缓冲区
# 如果已经接收到完整的信息头,则尝试解包
while len(self.m_buffer)>=Command.BaseCommand.CBaseCommand.HEAD_LEN :
self.m_buffer,command,=self.Decode(self.m_buffer)
if command == None:
break
#the maximum pending command is set to equal with connection count, one command for one connection by average
# 如果正在处理的命令数在阈值允许的范围内
if SBProtocol.countPendingCmd<Config.count_connection/100:
# 在Twisted 辅助线程中运行
threads.deferToThread(self.RunCommand,command)
with self.factory.lockPendingCmd:
SBProtocol.countPendingCmd=SBProtocol.countPendingCmd+1
else:
try:
# 无法处理的命令,直接生成错误消息给客户端
cmd_resp=command.GetResp()
cmd_resp.SetErrorCode(Command.BaseCommand.CS_SERVERBUSY)
cmd_resp.Send()
except:
pass
lock.release()
def Decode(self,data):
'''
return a tuple: new data,command
'''
# 检查协议是否含固定前缀,如果有,则切换到前缀模式
if self.HeaderTagType < 0: # not decide # 尚未确认前缀类型
if data[:4]==self.factory.SBMP_HEADERTAG:
self.HeaderTagType=1
else:
self.HeaderTagType=0
if self.HeaderTagType==1: # 如果有前缀,则在解码前去删除
tag_position=data.find(self.factory.SBMP_HEADERTAG)
if tag_position<0: return (data,None)
data=data[tag_position+4:] #remove head tag
length,command_id=struct.unpack("!2I",data[:8]) # 获取命令长度和命名ID
command=None
if length<=len(data): # 如果整条命令已收取完毕,则解析命令
command_data=data[:length]
if(Command.dicInt_Type.has_key(command_id)) :
try:
# 根据命令ID 初始化 命令对象
command=Command.dicInt_Type[command_id](command_data,self)
except Exception,e:
logging.error("build command exception in transport %d: %s :%s",id(self.transport),str(e),Util.asscii_string(command_data))
command=None
else:
command=Command.BaseCommand.CMesscodeCommand(command_data,self)
data=data[length:] # 删除已解包数据
else:
if self.HeaderTagType==1:
data=self.factory.SBMP_HEADERTAG+data #if command is not completed, add the head tag again
return (data,command) # 返回命令
# 断开连接时调用
def connectionLost(self, reason=twistedError.ConnectionDone):
if self.role==Command.BaseCommand.PV_ROLE_HUMAN:
InternalMessage.UnregistFilter(InternalMessage.TTYPE_HUMAN, self.client_id)
InternalMessage.NotifyTerminalStatus(InternalMessage.TTYPE_HUMAN, self.client_id, id(self.transport), InternalMessage.OPER_OFFLINE,'n')
elif self.role== Command.BaseCommand.PV_ROLE_RELAYER:
InternalMessage.UnregistFilter(InternalMessage.TTYPE_GATEWAY, self.relayer_id)
InternalMessage.NotifyTerminalStatus(InternalMessage.TTYPE_GATEWAY, self.relayer_id, 0, InternalMessage.OPER_OFFLINE)
try:
self.timer.cancel() # 取消超时定时器
except Exception:
pass
#print "connection lost:",id(self.transport),reason
self.releaseFromDict() # 取消保存 SBProtocol 对象
with self.factory.lockPendingCmd:
SBProtocol.connection_count=SBProtocol.connection_count-1
Protocol.connectionLost(self, reason=reason) # 调用基类函数
# 超时后取消
def timeout(self):
if self.role!=Command.BaseCommand.PV_ROLE_INTERNAL:
self.transport.loseConnection()
def isDeadSession(self):
return time.time()-self.tmActivate>Config.time_heartbeat
# 取消在factory 中保存的 SBProtocol 对象
def releaseFromDict(self):
with self.factory.lockDict:
if 'role' not in dir(self): return
if self.role == Command.BaseCommand.PV_ROLE_RELAYER:
if self.factory.dictRelayer.has_key(self.relayer_id):
if self.factory.dictRelayer[self.relayer_id]==self:
self.factory.dictRelayer.pop(self.relayer_id)
elif self.role == Command.BaseCommand.PV_ROLE_HUMAN:
for relayerId in SBDB.GetRelayerIDsByAccountId(self.account_id):
if self.factory.dictAccounts.has_key(relayerId):
listAccount=self.factory.dictAccounts[relayerId]
if self in listAccount:
listAccount.remove(self)
if len(listAccount)<=0:
self.factory.dictAccounts.pop(relayerId)
# **负责维护所有客户端连接**(建立处理客户端连接的类)
class SBProtocolFactort(ServerFactory):
# 真正的协程处理类 SBProtocol
protocol = SBProtocol # 配置类属性、指定本工厂建立的所有连接都有协议类 SBProtocol 进行处理
def __init__(self):
self.lockDict = threading.RLock() # 在多线程环境下,保护对象变量 dictRelayer、dictAccounts 进行操作
self.dictRelayer={} # 保存已经与服务器建立连接的中继器客户端Protocol对象
self.dictAccounts={} # 保存手机设备建立连接的 Protocol对象
self.SBMP_HEADERTAG = struct.pack("2B",0X01,0xBB)
self.lockPendingCmd = threading.RLock()
# 根据中继器ID 或 手机ID查询 ,获取 SBProtocol 对象
def GetAccountProtocol(slef,relayer_id,client_id):
with self.lockDict:
if self.dictAccounts.has_key(relayer_id):
for clientProtocol in self.dictAccounts[relayer_id]:
if clientProtocol.client_id == client_id
returen clientProtocol
return None
收发数据
# 通信引擎中的SBProtocol 是真正的协议处理类,继承自Protocol基类,
# 负责数据包的 收、发、解析,并对客户端连接数和正在处理的命令数据进行控制
# src/SBPS/ProtoclReactor.py
from twisted.internet.protocol import Protocol,ServerFactory
from twisted.internet import reactor,threads,ssl
from twisted.internet.defer import DeferredLock
import twisted.internet.error as twistedError
import struct
import Command
import logging,time
import threading
from Utils import Util,Config
from SBPS import InternalMessage
# 真正的协程处理类 继承于 Twisted 框架的 Protocol 基类
class SBProctocol(Protocol):
# 当客户端连接数量或命令数量超过阈值时,需要做丢弃处理,以防止流量过大而瘫痪
connection_count=0 # 连接总数,活跃的客户端数量
countPendingCmd = 0 # 待处理命令数
def __init__(self):
self.m_buffer="" # 接收缓冲区
self.lockBuffer=DeferredLock()
self.tmActivate=time.time() # 最近活动时间
self.dictWaitResp={} # 待反馈命令表
self.lock_dictWaitResp=threading.RLock()
self.dictControlling={} # 控制命令表
self.cond_dictControlling=threading.Condition()
# 回收不活跃的客户端连接,当延时到期时,通过self.timeout() 函数断开连接
self.timer=reactor.callLater(Config.time_heartbeat,self.timeout) # self.timer 超时定时器,Twisted 延时调用器 reator.callLaster() 返回的对象,当延时到期时,通过 self.timeout() 函数断开连接
self.lockCmd=threading.RLock()
self.HeaderTagType=-1 #-1: not decided, 0: no header_tag, 1: has header_tag
self.rcv_alarm="False"
self.role=""
# 接收数据时调用
def dataReceived(self, data):
Protocol.dataReceived(self, data)
# lockBuffer 是 DeferredLock() 对象,通过它保证在 多线程环境中同一个 SBProtocol的多个AddDataAndDecode函数之间保持顺序调用关系
# 异步调用消息处理函数 self.AddDataAndDecode,
self.lockBuffer.acquire().addCallback(self.AddDataAndDecode,data)
# 建立连接时调用
def connectionMade(self):
ip=self.transport.getPeer().host
with self.factory.lockPendingCmd:
SBProtocol.connection_count=SBProtocol.connection_count+1
if SBProtocol.connection_count>Config.count_connection:
self.transport.loseConnection()
print "close connection due to reaching connection limit."
# 连接断开时调用
def connectionLost(self, reason=twistedError.ConnectionDone):
try:
self.timer.cancel() # 取消超时定时器
except Exception:
pass
self.releaseFromDict() #取消保存 SBProtocol 对象
with self.factory.lockPendingCmd:
SBProtocol.connection_count=SBProtocol.connection_count-1
Protocol.connectionLost(self, reason=reason) # 调用基类函数
# 超时后取消
def timeout(self):
if self.role!=Command.BaseCommand.PV_ROLE_INTERNAL:
self.transport.loseConnection()
# 取消在 factory 中保存的SBProtocol对象
# 对 factory 中的dictRelayer 和 dict Accounts 进行简单的搜索,保证释放掉线的客户端SBProtocol对象
def releaseFromDict(self):
with self.factory.lockDict:
if 'role' not in dir(self): return
if self.role == Command.BaseCommand.PV_ROLE_RELAYER:
if self.factory.dictRelayer.has_key(self.relayer_id):
if self.factory.dictRelayer[self.relayer_id]==self:
self.factory.dictRelayer.pop(self.relayer_id)
elif self.role == Command.BaseCommand.PV_ROLE_HUMAN:
for relayerId in SBDB.GetRelayerIDsByAccountId(self.account_id):
if self.factory.dictAccounts.has_key(relayerId):
listAccount=self.factory.dictAccounts[relayerId]
if self in listAccount:
listAccount.remove(self)
if len(listAccount)<=0:
self.factory.dictAccounts.pop(relayerId)
TCP流式分包
"""
TCP通信流式特征:连接性、可靠性、有序性。 即发送端多次发送可能再接收端一次接收,或发送端一次发送被拆分多个包发送给接收端。
"""
SBProtocol / AddDataAndDecode() # 进行处理数据,接收数据时,需要处理字节流的粘包,拆分情况,保证收到完整数据段后才进行命令的解析和执行工作
异步执行
# 解包后,需要执行收到的命令。命令执行的时间长短不一,为了不拖延后续命令解包,所以需要异步方式执行命令
from twisted.internet.protocol import Protocol
from twisted.internet import threads
class SBProtocol(Protocol):
# ...省略代码
def AddDataAndDecode(self,lock,data):
# 如果正在处理的命令数在阈值允许范围内
# 用配置阈值检查当前正在处理的命令数是否已经太多,如果是则丢弃当前的命令,直接向客户端返回服务器的信息
if SBProtocol.coutPendingCmd < config.count_connection/100;
# 在Twisted辅助线程中运行
threads.deferToThread(self.RunCommand,command)
with self.factory.lockPendingCmd:
# 命令计数器 + 1
SBProtocol.countPendingCmd = SBProtocol.countPendingCmd + 1
else:
try:
# 无法处理命令,直接生成错误信息给客户端
cmd_resp = command.GetResp()
cmd_resp.SetErrorCode(Command.BaseCommand.CS_SERVERBUSY)
cmd_resp.send()
except:
pass
# 减少 SBProtocol.countPendingCmd 的值
def RunCommand(self,command): # 执行命令
with self.factory.lockPendingCmd:
SBProtocol.countPendingCmd = SBProtocol.countPendingCmd + 1
command.Run()
执行命令 - 用户注册命令
"""
需要在 Command 文件中,需要对所有的命令进行封装,
在通信引擎 ProtocolReactor.py中,对所有解析到的客户端命令调用命令类的 Run() 函数
"""
# 用户注册命令为例子
from BaseCommand import CBaseCommand
from sqlalchemy.exc import SQLAlchemyError
from DB import SBDB,SBDB_ORM
from Command import BaseCommand
import logging
from Utils import Util
from sqlalchemy import or_
# 所有命令继承自 CBaseCommand
class CAddAccount(CBaseCommand):
'''
classdocs
'''
command_id=0x00020005 # 命令ID 为 0x00020005
def __init__(self,data=None,protocol=None):
'''
Constructor
'''
CBaseCommand.__init__(self, data, protocol) # 执行基类构造函数,完成基本的命令解析工作
def Run(self):
with self.protocol.lockCmd: # 启用锁,防止同一客户端命令并行处理,使客户端所有命令都能够顺序进行
CBaseCommand.Run(self) # 通用逻辑写在基类中,更新客户端激活时间等公共逻辑
# 提取JSON 消息体中的命令参数
user_name=self.body.get(BaseCommand.PN_USERNAME)
if user_name is not None: user_name=user_name.strip()
password=self.body[BaseCommand.PN_PASSWORD]
email=self.body.get(BaseCommand.PN_EMAIL)
# 检查消息体参数,如果不满足要求则向客户端返回错误
if email is not None: email=email.strip()
mobile_phone=self.body.get(BaseCommand.PN_MOBLEPHONE)
if mobile_phone is not None: mobile_phone=mobile_phone.strip()
# 用 基类函数 self.GetResp() 生成 Response 消息对象
respond=self.GetResp()
# 读取消息中的对象,并判断参数是否允许,如同时出现相同的用户名注册等问题,则 respond.SetErrorCode() 设置错误代码
with SBDB.session_scope() as session :
if user_name is None and password is None and email is None:
respond.SetErrorCode(BaseCommand.CS_PARAMLACK)
elif user_name is not None and (session.query(SBDB_ORM.Account).filter(or_(SBDB_ORM.Account.user_name==user_name,SBDB_ORM.Account.email==user_name,SBDB_ORM.Account.mobile_phone==user_name)).first() is not None or len(user_name)<2):
respond.SetErrorCode(BaseCommand.CS_USERNAME)
elif email is not None and (session.query(SBDB_ORM.Account).filter(or_(SBDB_ORM.Account.user_name==email,SBDB_ORM.Account.email==email,SBDB_ORM.Account.mobile_phone==email)).first() is not None or not Util.validateEmail(email)):
respond.SetErrorCode(BaseCommand.CS_EMAIL)
elif mobile_phone is not None and (session.query(SBDB_ORM.Account).filter(or_(SBDB_ORM.Account.user_name==mobile_phone,SBDB_ORM.Account.email==mobile_phone,SBDB_ORM.Account.mobile_phone==mobile_phone)).first() is not None or not Util.validateMobilePhone(mobile_phone)):
respond.SetErrorCode(BaseCommand.CS_MOBILEPHONE)
else:
# 若所有命令参数正确,则执行命令代码
try:
# 用SQLAlchemy 增加用户账号对象
account=SBDB_ORM.Account()
account.language_id=2
account.email=email
#account.password=password
account.password=Util.hash_password(password)
account.user_name=user_name
account.mobile_phone=mobile_phone
account.version=0
# 用SQLAlchemy 增加区域对象
apartment=SBDB_ORM.Apartment()
apartment.arm_state=BaseCommand.PV_ARM_OFF
apartment.name="Home"
apartment.scene_id=None
apartment.version=0
account.apartments.append(apartment)
session.add(account)
session.commit() # 提交 SQLAlchemy 会话
respond.body[BaseCommand.PN_VERSION]=apartment.version
respond.body[BaseCommand.PN_APARTMENTID]=apartment.id
respond.body[BaseCommand.PN_NAME]=apartment.name
except SQLAlchemyError,e:
respond.SetErrorCode(BaseCommand.CS_DBEXCEPTION)
logging.error("transport %d:%s",id(self.protocol.transport),e)
session.rollback()
respond.Send()
struct 解析字节流 - 所有执行命令的基类 CBaseCommand
/ Command / CBaseCommand.py
class CBaseCommand(object):
HEAD_LEN=16 # 命令头长度固定为 16
# data - 客户端从TCP信道发来的字节流 - 命令消息头和消息体都是由data 参数解析而来
# protocol - 接收到字节流的 SBProtocol 对象实例
def __init__(self,data=None,protocol=None):
'''
Constructor
'''
self.protocol=protocol
self.role=None
self.tmActivate=time.time()
self.body={}
self.relayer_id=0
if data is not None:
self.data=data
self.command_len,self.command_id,self.command_status,self.command_seq=struct.unpack("!4I",data[:CBaseCommand.HEAD_LEN]) # 解析消息头
# 如果消息长度大于 16,则解析消息体
if self.command_len>CBaseCommand.HEAD_LEN: self.body=json.loads(data[CBaseCommand.HEAD_LEN:])
else:
self.command_len=CBaseCommand.HEAD_LEN
self.command_status=0
self.command_id=type(self).command_id
# 生成序列号
self.command_seq=self.GetNextSeq()
self.internalMessage=None
序列号生成
"""
在 CBaseCommand.__init__() 中,对于没有赋值 data 参数的情况 被认为是服务器主动发送的消息,所以需要调用 self.GetNetSeq() 函数生成唯一的序列号
"""
import threading
class CBaseCommand(object):
sequence_latest=0 # 定义变量 sequence_latest 用于保存当前服务器的序列号
lock_sequence=threading.RLock() # 序列号锁,用于在多线程环境下保护 sequence_latest 变量
# 在生成新的序列号之前用 acquire() 函数获取锁,在生成完成后 用 release() 函数释放锁
def GetNextSeq(self):
# 获取锁
CBaseCommand.lock_sequence.acquire()
# 判断序列号过大时(大于0x7fffffff),将 sequence_latest 重置为 0
if CBaseCommand.sequence_latest>=0x7fffffff: CBaseCommand.sequence_latest=0
next_sequence=CBaseCommand.sequence_latest+1
CBaseCommand.sequence_latest=next_sequence
CBaseCommand.lock_sequence.release()
return next_sequence
保持连接
"""
因为网络的不稳定性,服务器于客户端的TCP长连接会随时中断,因此服务器需要建立机制于确认客户端仍然有效,策略如下
1、服务器端在每次收到客户端发送来命令时,都更新该客户端端在 SBProticol 对象中激活时间
2、如果客户端在一定的激活阈值时间内(如300秒)没有任何消息,则认为连接已经中断,并主动断开连接
3、如果客户端在阈值时间内没有发送过消息,则应主动发送一条 heartBest 命令,以更新服务器中该连接的激活时间
4、服务器中更新激活时间的代码 在 CBaseCommand.Run() 函数中实现
**函数判断过程**
1、判断命令是内部命令还是外部命令,只有对外部命令才需要控制激活。
—— 内部命令:服务器之间 ,外部命令:客户端连接发送来命令
2、代码中先取消 之前设置的超时计时器,并以激活阈值时间设置新的计时器。
3、只要能在计时器到期之前收到新的命令,就不会真正产生超时调用。
当超时真的发生时,则认为客户端连接已经终端,通过 SBProtocol.timeout() 函数从服务器 SBProtocolFactory中删除该连接。(详见 SBProtocol 解析)
"""
from twisted.internet import reactor
class CBaseCommand(object):
def run(self):
def Run(self):
print "run: ",self.__class__
if self.protocol is not None and self.protocol.role != PV_ROLE_INTERNAL:
try:
# 取消 SBProtocol 对象中之前的超时计算器,而建立新的计时器
self.protocol.timer.cancel()
#self.protocol.timer=threading.Timer(Config.time_heartbeat,self.protocol.timeout)
#self.protocol.timer
self.protocol.timer=reactor.callLater(Config.time_heartbeat,self.protocol.timeout)
except Exception:
pass
self.protocol.tmActivate=time.time()
发送Response
"""
命令基类 CBaseCommand 中定义了统一的 Respinse 生成及发送机制,使的各个命令子类,可以 通过统一如下命令向客户端发送命令的消息响应
"""
# 指令函数中
class CXXXXXXXX(CBaseCommand):
command_id=0x000xxxxxx
def run(self):
respond = self.GetResp()
respond.setErrorCode(BaseCommand.CS_XXX)
respond.Send()
# 其中用到 GetResp()、Send() 方法为CBaseCommand 基类中的方法 代码如下
from twisted.internet import reactor
class CBaseCommand(object):
TypeResp=object # Response 命令类
"""
1、GetResp() 函数首先根据 "Response命令号最高位置 1" 的规则生成Response 命令 ID, 然后通过类属性TypeResp 生成 Response 消息对象
2、TypeResp默认为object,当该属性没有被设置为其他类型时,GetResp() 函数使用 CBaseRespCommand 作为Response 消息对象的类型,否则使用TypeResp 指定的类
3、CBaseRespCommand 也是CBaseCommand 的子类,所以它能使用CBaseCommand 中定义的 send()、setErrorCode()等函数
4、在Send()中使用struct 和json库将命令属性打包为网络字节流。如果时 内部协议命令,则使用内部通信引擎 internalMessage 进行发送,否则调用 Send_Real() 函数直接向客户端发送。
5、作为实际的消息发送者,Send_Real() 函数使用 reactor.callFromThread() 将发送任务交给Twisted 主线程
6、Send() 函数不仅可以用于发送 Response消息,也可作为服务器主动向客户端发送Request 消息的工具函数。
"""
def GetResp(self):
TypeResp=type(self).TypeResp # 获取 Response 命令类
# 根据 Response 命令号最高位置 1 的规则设置命令ID
command_id=Util.int32_to_uint32(self.command_id)|0x80000000
# 生成 Response 命令对象
if TypeResp == object: # 默认 Response 命令对象
cmd_resp=CBaseRespCommand(protocol=self.protocol,request=self,command_id=command_id)
return cmd_resp
else:
return TypeResp(protocol=self.protocol,request=self,command_id=command_id)
# 调用 Twisted 的 TCP 信道发送接口
def Send_Real(self):
reactor.callFromThread(self.protocol.transport.write,self.data)
print "data sent in transport %d : %s (%s)" % (id(self.protocol.transport),Util.asscii_string(self.data),self.data)
# 发送
def Send(self,internalMessage=None):
body_string=""
if len(self.body)>0:
body_string=json.dumps(self.body) # 生成消息体
self.command_len=CBaseCommand.HEAD_LEN+len(body_string)
# 将消息头的 4 个变量打包为字节流
self.data=struct.pack("!4I",self.command_len,self.command_id,self.command_status,self.command_seq)
self.data=self.data+body_string
if internalMessage is None:
if self.protocol.HeaderTagType==1:
self.data=self.protocol.factory.SBMP_HEADERTAG+self.data
self.Send_Real()
return
internalMessage.body=self.data
internalMessage.Send()
# Response 消息
class CBaseRespCommand(CBaseCommand):
command_id=0x80000000
def __init__(self,data=None,protocol=None,request=None,command_id=None):
CBaseCommand.__init__(self,data, protocol)
self.request=request
if command_id is not None: self.command_id=command_id