游戏服务器之长连接服务器实现tcp连接的数据异步收发。
一个网络收发处理进程,一个服务器对象逻辑处理进程。两个进程之间使用管道通信。
网络收发处理进程:
(1)网络处理是由反应器的子线程来处理的。
(2)从管道的一端读取数据,读取管道后和网络发送前,需要前处理连包和反序列化来检查包的完整性。这里还缺少合适的连包断包处理。
(3)反应器的子线程接受网络数据再写到管道。
服务器对象逻辑处理进程:
(1)读取网络数据并反序列化的是在服务器对象进程的一个接受循环线程里处理。并派送到服务器对象的消息队列。
(2)反应器的处理定时器事件(在主线程)。
(3)主线程序列化并发送到管道。
这样的好处是逻辑服务进程只处理逻辑相关的事情,网络收发处理进程只是处理网络数据收发(可以使用两个进程的两个反应器分开处理,一个进程只可以使用一个反应器),可以异步处理。
缺点是网络收发处理进程和服务器对象逻辑处理进程都需要连包和反序列化来检查数据的完整性,使用管道也会有消耗。
总的来说,使用管道发送局限于单一物理机器,序列化反序列化次数增加,这种方法没有提升效率。需要在网络收发进程增加连包段宝处理,需要拓展到适用于服务器集群。
1、网络收发进程服务器对象
(1)网络收发进程服务器对象定义
class BaseNetServer:
u"""
. 子进程,模拟网络模块
. 发送消息过程:从线程从管道中接收消息,
. 查看消息目标服务
. 根据目标服务查找通信频道
. 调用通信频道发送消息
. 接收消息:通信频道将消息回调给网络服务
. 网络服务将消息通过管道发送给主服务
"""
def __init__(self, recv_pipe, send_pipe, conf):
log.info(u"netserver init")
self.recv_pipe = recv_pipe
self.send_pipe = send_pipe
self.btime = time.time()
self.etime = 0
self.count = 0
self.conf = conf
self.name = conf.myself.name
self.channels = {} # server_name: channel
self.route = {} # service_id: ServiceItem
for s in conf.servers:
for svcId, svc in s.services.items():
self.route[svcId] = ServiceItem(svcId, s.name)
# 创建工作线程
self.thread = Thread(target=self.run)
self.thread.setDaemon(True)
self.thread.start()
# 主线程loop,启动网络模块
print "------------------------------"
self.start()
# -------------------------------------------------------------------------
def requestCreateChannel(self, target, transport):
u"""
. 网络层在网络协议建立后,请求服务器对象建立和对端Server的通讯频道
1)服务器对象创建Channel对象
2)服务器对象为每个服务设置通讯Channel"""
channel = Channel(transport)
self.channels[target] = channel
for k, v in self.route.items():
if v.serverName == target:
v.channel = channel
log.info(u"service (%d) is in the server(%s)", k, target)
log.info(u"[%s] : server(%s) is connected", self.name, target)
channel.setListener(self)
return channel
def requestDestroyChannel(self, target):
u"""
.网络层在网络协议断连后,请求服务器对象销毁和对端Server的通讯频道
1)服务器对象销毁Channel对象
2)服务器对象设置相应服务Channel=None,后续发给该服务的消息将无法送达
"""
del self.channels[target]
for k, v in self.route.items():
if v.serverName == target:
v.channel = None
log.info(u"server(%s) is disconnected", target)
def getChannel(self, serviceId):
item = self.route[serviceId]
if item != None:
return item.channel
return None
# -------------------------------------------------------------------------
def start(self):#建立需求的工厂
conf = self.conf
log.info(u"Setup network configuration....")
for i in range(0, len(conf.servers)):
if conf.servers[i].name == self.name:
reactor.listenTCP(conf.servers[i].port,
ServerConnectionFactory(self, self.name))
break
else:
log.info(u"conf.servers[i].ip: %s, port: %s", \
conf.servers[i].ip, conf.servers[i].port)
reactor.connectTCP(self.conf.servers[i].ip, conf.servers[i].port,
ClientConnectionFactory(self, self.name))
log.info("network recv task run")
try :
reactor.run()
except:
reactor.stop()
def run(self):#读取管道数据并发送
log.info("network send task run")
while True:
try:
data = self.send_pipe.recv()#接受到的逻辑进程发来的网络数据则转发出去
self.send(data)
except:
traceback.print_exc()
def send(self, data):#管道发送到逻辑处理进程
u"""
. 发送消息
"""
self.count += 1
if self.count%10000==0:
log.info("network send event:%d", self.count)
(rc, event) = PipeEvent.createEventFromStream(data)
if rc:
channel = self.getChannel(event.dstId)
if channel!=None:
channel.send(event.eventData)
else:
log.error("service:%d channel not find", event.dstId)
else:
log.error("create pipe event error")
def on_event(self, event):
u"""
. event: PipeEvent
"""
try:
self.recv_pipe.send(event.eventData)
except:
traceback.print_exc()
(2)启动网络收发进程服务器对象
读取配置文件启动子服务对象。
def start_net_server(recv_queue, send_queue, file):
u"""创建子进程服务对象"""
log.info(u"System Starting -> loading configuration %s" , file)
conf = SystemConfig(file)
net_server = BaseNetServer(recv_queue, send_queue, conf)
2、逻辑处理服务器对象
逻辑处理服务器对象接受网络收发进程发送来的数据,反序列化后检查事件的合法性和完整性,然后派送到具体的逻辑服务并进行处理。
class TaskServer:
u"""
.服务器对象,各个服务都通过服务器对象,可进行
1)服务之间通讯
2)读取配置
.服务器对象主要的功能包括
1) 初始化
2)读取配置
3)装载服务
4)实现通讯机制
.服务器维护以下关键数据结构
1)路由表:当收到事件后需要查询路由表寻找事件的接收服务器
2)服务表:该表保存服务数据,服务和网络的关系
3)通讯频道表:该表保存当前的通道数据信息
"""
def __init__(self, conf, file):#file配置文件,conf配置文件的解析对象
self.conf = conf
self.route = {} # service_id: ServiceItem
self.services = {} # service_id: service object
self.lock = Lock()
self.name = conf.myself.name
self.recv_pipe, self.child_recv_pipe = Pipe()#接收管道的两端
self.send_pipe, self.child_send_pipe = Pipe()#发送管道的两端
self.network = Process(target=start_net_server, args=(self.child_recv_pipe, \
self.child_send_pipe, file))#管道网络进程处理收发,进程是创建子进程服务器对象
self.network.start()#启动网络进程
self.init_server_data()
# create route table
for s in conf.servers:
for svcId, svc in s.services.items():
self.route[svcId] = ServiceItem(svcId, s.name)
global SERVER
SERVER = self
def init_server_data(self):
log.info("begin init data")
ServerData.init_load()
log.info("end init data")
def getServerConfig(self, serverName):
u"""获得指定服务器的配置信息"""
for server in self.conf.servers:
if server.name == serverName:
return server
return None
def getServiceConfig(self, serviceId):
u"""获得指定服务的配置信息"""
return self.conf.myself.services[serviceId]
# ----- service ----------------------------------------------------------
def registeService(self, service):
u"""服务注册函数,服务注册成功后,则可以收发消息"""
self.services[service.serviceId] = service
def unregisteService(self, service):
u"""服务注销函数,服务注册成功后,则可以收发消息"""
del self.services[service.serviceId]
# ----- event ------------------------------------------------------------
def onChannelEvent(self, event):
u"""
Channel收到相应的事件后将调用服务器对象的onChannelEvent
. 服务器对象根据dstId,转发给相应的本地服务
"""
self._dispatchEvent(event)
def _sendEvent(self, buf):#通过管道进程来发送
self.send_pipe.send(buf)
def sendToPipe(self, event):#管道发送
u"""event:"""
self.send_pipe.send(event.toStream())#reactor.callFromThread(self._sendEvent, event.toStream())
def sendEvent(self, eventData, srcId= -1, dstId= -1, eventType= -1, \
param1= -1, param2= -1, senceId=-1, origEvent=None):
u"""
.可通过调用本函数,发送事件给指定服务
1)服务器对象首先检查目标服务的位置
2)对于远程服务,通过Channel对象发送事件至对端服务器
3)对于本地服务,则直接转发
eventData : 发送的数据
srcId : 源服务的标记,如origEvent不为空,则为origEvent.dstId
dstId : 目标服务的标记,如origEvent不为空,则为origEvent.srcId
eventType: 事件 型,如果不填写则为-1
origEvent: 为源事件
"""
# check whether event should be sent over network or not
if origEvent != None:
srcId = origEvent.dstId
dstId = origEvent.srcId
param1 = origEvent.param1
param2 = origEvent.param2
senceId = origEvent.senceId
item = self.route[dstId]
if item == None:
log.warning(u"Event(%s) Missing due to no this service(%d)", \
eventData, dstId)
return - 1
event = Event.createEvent(srcId, dstId, eventType, param1, param2, eventData, senceId)
if item.serverName == self.name: # local event
self._dispatchEvent(event)
else:
self.sendToPipe(event)#不是本服务器进程的发送到别的服务器进程
return event.tranId
def notifyEvent(self, userId, data, event_type, sceneId=0, serviceId=0):
u"""
. 发送通知事件给客户端
"""
self.sendEvent(data, serviceId, NOTIFY_SERVICE, \
NOTIFY_SERVICE_SENDTO_CLIENT.REQ, userId, event_type, sceneId)
def _dispatchEvent(self, event):
u"""分发事件至本地服务的函数"""
svc = self.services[event.dstId]
if svc != None:
try :
svc.dispatch(event)
except Full, e:
log.warning(u"Event(%s) Missing due to queue is full", event)
else:
log.warning(u"Event(%s) Missing due to no this service", event)
def _dispatchTimerEvent(self, event):
u"""分发时间事件至本地服务的函数"""
svc = self.services[event.dstId]
if svc != None:
svc.dispatchTimerEvent(event)
else:
log.warning(u"Event(%s) Missing due to no this service", event)
def localBroadcastEvent(self, eventType, param1, param2, eventData):
u"""实现本地服务广播"""
for id, svc in self.services.items():
evt = Event.createEvent(-1, svc.serviceId, eventType, param1, \
param2, eventData)
self._dispatchEvent(evt)
# ----- timer -------------------------------------------------------------
def setTimerByFunc(self, delay, func, *args, **kw):
u"""设置定时器,并通过另外的函数唤醒"""
reactor.callLater(delay, func, args, kw)
def setTimerByClientEvent(self, delay, sid, event):
u"""
.设置定时器,直接分发调用者传入的事件,也是通过消息队列的方式返回给客户端
"""
def onTime(server, sid, evt):
svc = self.services[sid]
if svc != None:
svc.dispatchTimerEvent(evt)
else:
log.warning(u"Event(%s) Missing due to no this service", event)
return reactor.callLater(delay, onTime, self, sid, event)
def setTimerByEvent(self, delay, sid, eventData):
u"""设置定时器的函数"""
event = Event.createEvent(0, sid, 1, -1, -1, eventData)
def onTime(server, evt):
self._dispatchTimerEvent(evt)
return reactor.callLater(delay, onTime, self, event)
# ----- run --------------------------------------------------------------
def installServices(self):
u"""通过配置文件的信息,启动,初始化和注册服务"""
for svcId, svc in self.conf.myself.services.items():
name = svc.options["module"]
__import__(name)
module = sys.modules[name]
log.info(u"install service : %s", name)
print "----- install service:", name
self.registeService(eval(svc.options["code"])) # TODO: ?
def run(self, file):
u"""
.主循环函数
1)创建 网络线程
2) 监听接收管道
"""
self.thread = Thread(target=self.recv_loop)#
self.thread.setDaemon(True)
self.thread.start()
log.info("reactor run")
try :
reactor.run()
except:
reactor.stop()
def recv_loop(self):
log.info("recv loop")
while True:
try:
data = self.recv_pipe.recv()#监听管道的事件
(rc,event) = Event.createEventFromStream(data)
if rc:
self.onChannelEvent(event)#把消息派送到服务的消息队列
else:
log.error("create event error")
except:
traceback.print_exc()
def stop(self):
u"""停止各个服务"""
for service in self.services.values():
service.stop()
#time.sleep(5)
for service in self.services.values():
self.unregisteService(service)
log.debug("stop server")
3、会话
class Channel:
u"""
.Channel类用于和网络上其它服务器进行通讯,对于每个连接的Server,本服务器会维护一个Channel实例
.用于接收和发送相关事件消息
.Channel会注册至服务器上,从网络上收到消息后会缓冲以及解析,生成事件对象并交服务器处理
.Channel会打包发送的数据,并通过Twisted提供的传输机制来发送数据,Channel对象不进行数据缓存,
.Twisted会解决发送过程中的缓存问题
"""
def __init__(self, transport):
u"""
.通过建立的网络传输对象,初始化Channel实例
"""
self.transport = transport
self.buffer = ""
self.listener = None
def send(self, buf):
u"""
. 发送消息方法
. 该方法会使用反应器的callFromThread方法进行处理
. 1) 解决线程安全的问题
. 2) 解决网络发送的问题
. 注意:trasport.write方法不是线程安全的方法
"""
#self._sendEvent(buf)
reactor.callFromThread(self._sendEvent, buf) # NOTE: reactor保证线程安全
def _sendEvent(self, buf):
self.transport.write(buf)
def writeBuffer(self, data):#接收数据,检查完整性,写到管道
u"""
. 在网络上收到消息后,该方法将被调用
. 该方法缓存并解析事件,同时交监听器处理
"""
self.buffer += data
while True:
(result, event) = PipeEvent.createEventFromStream(self.buffer)#检查事件合法性(这里暂时不能优化,需要在管道保证消息数据接收完整性)
if result:
self.buffer = self.buffer[event.length:]
if (self.listener != None):
self.listener.on_event(event)
else:
return
def setListener(self, listener):
u""" listener: server.onChannelEvent """
self.listener = listener
4、管道事件
class PipeEvent:
u"""
. 进程管道传输事件
"""
def __init__(self, ver, length, sid, did, eventType, eventData):
self.version = ver
self.length = length
self.srcId = sid
self.dstId = did
self.eventType = eventType
self.eventData = eventData
@staticmethod
def createEventFromStream(data):
u"""
.return: (boolean, Event | None)
"""
l = len(data)
if l < lengthOfHeader:
return (False, None)
(ver, length, sid, did, eventType) = struct.unpack_from(formatOfHeader, data)
if l < length:
return (False, None)
return (True, PipeEvent(ver, length, sid, did, eventType, data[:length]))