采用Ice编写的Rpc服务应用,endpoint部署在5173.com的游戏做单PC上,sync_server部署在游戏运维网管机器上。 sync_server与endpoint是1对多的形态部署。某一款游戏做了修改或者逆向代码有了修改,通过sync_server将新增部分同步到几百台endpoint游戏主机。 类能类似 EMC的 Networker 同步软件。 文件校验使用md5 通信接口定义sync.ice(IDL)
1 2 #ifndef _SYNC_ICE 3 #define _SYNC_ICE 4 6 module games { 7 8 dictionary<string,string> HashValueSet; 9 dictionary<string,string> ReturnValueT; 10 sequence<byte> StreamDataT; 11 sequence<string> StringListT; 12 sequence<HashValueSet> HashValueListT; 13 sequence<int> IntListT; 14 sequence<StringListT> StringListListT; 15 16 17 interface IService{ 18 void invokeOneway(HashValueSet value,StreamDataT stream); 19 string getSequence(); 20 string getType(); // das ,cmc, 21 string getId(); //service module id 22 int getTimestamp(); //获取系统时钟 1970之后秒数 23 string getVersion(); //系统版本 2009.12.27 24 }; 25 26 27 struct FileEntryT{ 28 string filename; //文件不能携带远端的存储路径 29 int size; 30 int timestamp; 31 string digest; 32 }; 33 sequence<FileEntryT> FileEntryListT; 34 35 36 interface ISyncEndpoint extends IService{ 37 /*************************************************** 38 @methond: getFileDescList() 39 @params: 读取指定目录文件描述信息 40 @return: 41 {'succ':true/false,'errmsg':xxx} 42 ****************************************************/ 43 FileEntryListT getFileDescList(string path); 44 45 /*************************************************** 46 @methond: getFileDigest() 47 @params: file - 远程文件名称 48 @return: 49 {'succ':true/false,'errmsg':xxx,'digest':''} 50 ****************************************************/ 51 ReturnValueT getFileDigest(string file); 52 /*************************************************** 53 @methond: Start() 54 @params: 55 file - 远程文件名称 56 @return: 57 {'succ':true/false,'errmsg':xxx} 58 ****************************************************/ 59 ReturnValueT syncFileStart(string file); 60 /*************************************************** 61 @methond: Data() 62 @params: [] 63 @return: 64 {'succ':true/false,'errmsg':xxx} 65 ****************************************************/ 66 ReturnValueT syncFileData(string bytes); 67 /*************************************************** 68 @methond: End() 69 @params: 70 @return: 71 {'succ':true/false,'digest':xxx,'errmsg':xxx} 72 完成之后是否计算digest 73 ****************************************************/ 74 ReturnValueT syncFileEnd(); 75 76 /*************************************************** 77 @methond: launchApp() 78 @params: 79 {'app':'path','param':'xxx'} 80 @return: 81 {'succ':true/false,'errmsg':xxx} 82 运行进程 83 ****************************************************/ 84 ReturnValueT launchApp(HashValueSet params); 85 86 /*************************************************** 87 @methond: deleteFile() 88 @params: 89 file - 文件名称或者目录 90 @return: 91 {'succ':true/false,'errmsg':xxx} 92 运行进程 93 ****************************************************/ 94 ReturnValueT deleteFile(string file); 95 96 /*************************************************** 97 @methond: terminateGame() 98 停止游戏运行 99 @params: 100 game - 游戏目录名称 (英文) 101 @return: 102 {'succ':true/false,'errmsg':xxx} 103 ****************************************************/ 104 ReturnValueT terminateGame(string game); 105 106 /*************************************************** 107 @methond: launchGame() 108 加载游戏运行 109 @params: 110 game - 游戏目录名称 (英文) 111 @return: 112 {'succ':true/false,'errmsg':xxx} 113 ****************************************************/ 114 ReturnValueT launchGame(string game); 115 116 /*************************************************** 117 @methond: executeBatchFile() 118 执行远程批处理文件 119 @params: 120 file - 文件全路径 121 @return: 122 {'succ':true/false,'errmsg':xxx} 123 运行进程 124 ****************************************************/ 125 ReturnValueT executeBatchFile(string file); 126 127 /*************************************************** 128 @methond: killProcessByName() 129 停止指定名称的进程 130 ****************************************************/ 131 ReturnValueT killProcessByName(string procname); 132 133 /*************************************************** 134 @methond: startUpdate() 135 启动endpoint的自我更新 136 endpoint应当加载 update.exe ,然后令自己退出 137 update.exe的位置在jerray.conf配置 138 ****************************************************/ 139 ReturnValueT startUpdate(); 140 141 //读取远端指定文件数据到本地,适合小文件 142 StreamDataT getRemoteFile(string remotefile); 143 //读取远端文件信息 144 FileEntryT getFileDesc(string remotefile); 145 //读取远端文件,offset-偏移位置,size-读取数据大小 146 StreamDataT getRemoteFileData(string remotefile,int offset,int size); 147 148 //关闭endpoint服务 149 void shutdown(); 150 151 }; 152 }; 153 154 155 #endif 156 157
endpoint.conf
1 server.id=GEP-1 2 SyncEndPoint.Endpoints=tcp -p 5000 3 endpoint_updir=c:/ep_update 4 5 Ice.Override.Compress=0 6 Ice.Compression.Level=5 7 Ice.MessageSizeMax=50000
server.conf
1 server.id=SERVER-1 2 SyncEndPoint.Endpoints=tcp -p 5000 3 4 endpoint.servant=endpoint 5 endpoint.port=5000 6 endpoint_hosts=hosts.list 7 #logfile=abc.log 8 9 games=A#,B #游戏列表 10 A.path=c:/a/ #本地游戏数据目录 11 A.dest_path=c:/a2 #远端游戏存储数据目录 12 A.launch.app= #完成更新执行命令 13 A.launch.params= #执行远程命令携带的参数 14 15 B.path=c:/audio 16 B.dest_path=d:/audio_remote 17 game_hosts=hosts.list 18 workthread.num=1 #并发传送工作线程数量 19 20 Ice.Warn.Connections=1 21 Ice.ThreadPool.Server.Size=20 22 Ice.ThreadPool.Server.SizeMax=100 23 Ice.Override.Compress=0 24 Ice.Compression.Level=5 25 Ice.MessageSizeMax=50000 26 Ice.Override.ConnectTimeout=100 27
endpoint.py 游戏主机服务程序
1 # -*- coding:utf-8 -*- 2 3 # revisions: 4 # 2009.11.05 scott 5 # 1. created 6 # 2. 实现c++版本全部接口,保持调用一致性 7 # 2009-12.26 scott 8 # 1. 支持升级: 创建c:/ep_update目录存放更新文件,当升级时将新的endpoint程序传输到目标主机的d:/ep_update下 9 # 启动ep_update/update.exe,然后endpoint.exe自动退出; update将新的程序文件覆盖掉原来的endpoint文件 10 # 最后启动endpoint.exe,update.exe自动退出 11 12 13 # 2009.12.27 scott 14 # 1. 为支持endpoint的升级,目前解决方法是在syncserver端主动停止endpoint进程,然后再加载新的enpoint 15 # 这就要求endpoint退出之前加载新的endpoint进程,新的进程必须delay加载,因为端口会产生冲突 16 17 import sys,os 18 #sys.path.insert(0,'C:\Ice-3.3.1-VC90\python') 19 #sys.path.append('../lib') 20 21 import traceback,threading,time,struct,os,os.path,shutil,distutils.dir_util,array,base64,zlib,struct,binascii 22 import copy,socket,select,hashlib,win32process,win32api,win32con,re 23 #import win32com.client 24 #from xml.dom import minidom 25 import codec,log,config 26 27 import Ice 28 29 Ice.loadSlice('-I../idl -I../idl/slice ../idl/sync.ice') 30 from games import * 31 32 ############################################################## 33 VERSION='v0.2.0 2009.12.27 scott' 34 35 36 class SyncServiceEndpoint(ISyncEndpoint): 37 def __init__(self,app): 38 self._app = app 39 self._fwh = None 40 self._seqno = 0 41 pass 42 def invokeOneway(self, value, stream, current=None): 43 pass 44 45 def getSequence(self, current=None): 46 self._seqno+=1 47 return self._seqno 48 49 def getType(self, current=None): 50 return 'GRT_UPDATER' 51 52 def getId(self, current=None): 53 return self._app.getPropertyValue('server.id') 54 55 def getTimestamp(self, current=None): 56 return int(time.time()) 57 58 def getVersion(self,current=None): 59 return VERSION 60 61 #-------------- 62 #获取指定路径下所有文件信息 63 64 def getFileDescList(self, path, current=None): 65 rs=[] 66 try: 67 print "calc digest and return" 68 #print ">>getFileDescList:%s"%path 69 for root, dirs, files in os.walk(path, topdown=False): 70 for name in files: 71 file = os.path.join(root, name) 72 fe = FileEntryT() 73 fe.filename = file.lower() 74 fe.size = os.stat(file).st_size 75 fe.timestamp = 0 76 fe.digest = codec.calcFileMd5Digest(file) 77 rs.append(fe) 78 except: 79 print traceback.print_exc() 80 pass 81 #print str(rs) 82 83 return rs 84 85 #取单个文件的摘要信息 86 def getFileDigest(self, file, current=None): 87 digest='' 88 try: 89 digest = codec.calcFileMd5Digest(file) 90 except: 91 pass 92 return digest 93 94 95 #文件同步开始 96 def syncFileStart(self, file, current=None): 97 r={} 98 print "prepare file:",file 99 filename = file 100 #deep create dir 101 r['succ'] = 'true' 102 try: 103 filename = os.path.normpath(filename) 104 path = os.path.dirname(filename) 105 #file = os.path.basename(filename) 106 #print "path:",path 107 #distutils.dir_util.create_tree(path,file) 108 #distutils.dir_util.mkpath(path) 109 self.mkpath(path) 110 self._fwh = open(filename,'wb') 111 except: 112 #print traceback.print_exc() 113 r['succ']='false' 114 return r 115 116 def syncFileData(self, bytes, current=None): 117 r={'succ':'true'} 118 try: 119 self._fwh.write(bytes) 120 self._fwh.flush() 121 except: 122 r['succ']='false' 123 return r 124 125 def syncFileEnd(self,current=None): 126 r={'succ':'true','digest':''} 127 try: 128 self._fwh.close() 129 except: 130 r['succ']='false' 131 return r 132 133 #执行本地程序 134 def launchApp(self, params, current=None): 135 try: 136 print u"创建进程:%s"%params['app'] 137 print params 138 #win32process.CreateProcess(params['app'], params['param'], None , None , 0 ,win32process.CREATE_NO_WINDOW , None , None , 139 win32process.CreateProcess(None,params['app']+' '+params['param'], None , None , 0 ,win32process.CREATE_NEW_CONSOLE , None , None , 140 141 win32process.STARTUPINFO()) 142 except: 143 print traceback.print_exc() 144 145 def deleteFile(self, file, current=None): 146 r={'succ':'true'} 147 try: 148 if os.path.isdir(file): 149 distutils.dir_util.remove_tree(file) 150 if os.path.isfile(file): 151 os.remove(file) 152 except: 153 r['succ']='false' 154 return r 155 156 def terminateGame(self, game, current=None): 157 r={'succ':'true'} 158 try: 159 #file = "%s/RobotSetting.xml"%game 160 #xmldoc = minidom.parse(file) 161 #root = xmldoc.documentElement 162 #node = root.getElementsByTagName('GameProcess') 163 #n =root.getElementsByTagName('RobotPath') 164 #path = root.getElementsByTagName('RobotPath').nodeName #nodeName,nodeType 165 166 conf = config.SimpleConfig() 167 if not conf.open("%s/RobotSetting.conf"%game): 168 r['succ']='false' 169 r['errmsg']='game config lost!' 170 return r 171 s = conf.getPropertyValue('GameProcess') 172 processes = s.split(',') 173 for p in processes: 174 pid = self.getProcessIdByName(p) 175 if pid != -1: 176 print "Kill Process:(%s)%s"%(pid,p) 177 self.killProcess(pid) 178 except: 179 print traceback.print_exc() 180 r['succ']='false' 181 return r 182 183 #加载游戏 184 #读取游戏目录下的 RobotSetting.xml 配置信息 185 # 186 def launchGame(self, game, current=None): 187 r={'succ':'true'} 188 #self.terminateGame(game) #停止游戏运行 189 try: 190 #file = "%s/RobotSetting.xml"%game 191 #xmldoc = minidom.parse(file) 192 #root = xmldoc.documentElement 193 #path = "%s/%s"%(game,root.getElementsByTagName('RobotPath').nodeName.strip()) # nodeName,nodeType 194 conf = config.SimpleConfig() 195 if not conf.open("%s/RobotSetting.conf"%game): 196 r['succ']='false' 197 r['errmsg']='game config lost!' 198 return r 199 s = conf.getPropertyValue('RobotPath') 200 path = "%s/%s"%(game,s) 201 print 'Execute file:',path 202 win32process.CreateProcess(path, '', None , None , 0 ,win32process.CREATE_NO_WINDOW , None , None , 203 win32process.STARTUPINFO()) 204 except: 205 #print traceback.print_exc() 206 r['succ']='false' 207 #r['errmsg'] = traceback.print_exc() 208 return r 209 210 #执行批处理文件 211 def executeBatchFile(self,file,current=None): 212 r={'succ':'true','verbose':''} 213 try: 214 print 'Exceute Batch File:',file 215 r['verbose']=os.popen("%s"%file).read() 216 except: 217 r['succ']='false' 218 return r 219 220 #杀掉制定进程名称 221 def killProcessByName(self,procname,current=None): 222 try: 223 pid = self.getProcessIdByName(procname) 224 if pid != -1: 225 print "Kill Process:(%s)%s"%(pid,procname) 226 self.killProcess(pid) 227 except: 228 print traceback.print_exc() 229 230 #开始更新 231 def startUpdate(self,current=None): 232 try: 233 upfile = os.path.normpath( self._app.getPropertyValue('endpoint_updir')+'/update.exe') 234 print u'启动更新进程:%s'%upfile 235 win32process.CreateProcess(upfile, '', None , None , 0 ,win32process.CREATE_NO_WINDOW , None , None , 236 win32process.STARTUPINFO()) 237 sys.exit(0) 238 except: 239 print traceback.print_exc() 240 241 def shutdown(self,c=None): 242 print u'停机' 243 sys.exit(0) 244 245 def getRemoteFile(self,remotefile,c=None): 246 pass 247 248 def getFileDesc(self,remotefile,c=None): 249 pass 250 251 def getRemoteFileData(self,remotefile,offset,size,c=None): 252 pass 253 254 #--------------------------------------- 255 def mkpath(self,path): 256 try: 257 #print '>>',path 258 ls = path.split('\\') 259 dirname = ls[0] 260 for n in range(1,len(ls)): 261 dirname = dirname+"\\"+ls[n] 262 #print 'make dir:',dirname 263 try: 264 os.mkdir(dirname) 265 except:pass 266 except: 267 print traceback.print_exc() 268 return False 269 return True 270 271 #def getProcessIdByName(self,pname): 272 # pid = -1 273 # pname = pname.strip() 274 # WMI = win32com.client.GetObject('winmgmts:') 275 # rs = WMI.ExecQuery("select * from Win32_Process where name ='%s'"%pname) 276 # for r in rs: #.ProcessId,n.Name 277 # pid = r.ProcessId 278 # break 279 # return pid 280 281 #def getProcessIdByName(self,pname): 282 # pid = -1 283 # pname = pname.strip() 284 # print pname 285 # allPIDs = win32process.EnumProcesses() 286 # 287 # for PID in allPIDs: 288 # try: 289 # hProcess = win32api.OpenProcess(win32con.PROCESS_TERMINATE, False, PID) 290 # hProcessFirstModule = win32process.EnumProcessModules(hProcess)[0] 291 # currentprocessname = os.path.split(win32process.GetModuleFileNameEx(hProcess, hProcessFirstModule))[1] 292 # #print currentprocessname 293 # if pname.lower() == currentprocessname.lower(): 294 # pid = PID 295 # except Exception, e: 296 # pass#print traceback.print_exc() 297 # return pid 298 299 def getProcessIdByName(self,pname): 300 pid = -1 301 pname = pname.strip().lower() 302 303 try: 304 lines = os.popen('tasklist').read() 305 lines = lines.split('\n') 306 #print len(lines) 307 #return 308 #fp = open('c:/tasklist.txt','w') 309 #fp.write(lines) 310 #fp.close() 311 #fp = open('c:/tasklist.txt') 312 #lines = fp.readlines() 313 #fp.close() 314 for line in lines: 315 try: 316 line = line.strip().lower() 317 pids = re.findall("^%s\s+(\d+).*"%pname,line) 318 if len(pids): 319 pid = pids[0] 320 321 except:pass 322 except: 323 pass 324 print "getProcessIdByName(%s)=>%s"%(pname,pid) 325 return pid 326 327 #检测进程名是否存在 328 def isProcessExist(self,pname): 329 r=True 330 if self.getProcessIdByName(pname) == -1: 331 r = False 332 return r 333 334 def killProcess(self,pid): 335 try: 336 hProcess = win32api.OpenProcess(win32con.PROCESS_TERMINATE, False, int(pid)) 337 win32api.TerminateProcess(hProcess,0) 338 except: 339 print traceback.print_exc() 340 341 #os.system("taskkill /F /PID %s"%pid) 342 ################################################# 343 ################################################# 344 345 class sepApp(Ice.Application): 346 def __init__(self): 347 self._conf = config.SimpleConfig() 348 self.service = SyncServiceEndpoint(self) 349 350 def test(self): 351 #print self.service.getProcessIdByName('Insight3.Exe') 352 #print self.service.terminateGame('c:/aaa') 353 #print self.service.launchGame('c:/a') 354 #print self.service.deleteFile('c:/a/NOTEPAD.EXE') 355 #self.service.getProcessIdByName('hh.exe') 356 #self.service.killProcess(2968) 357 pass 358 359 def run(self, args): 360 #self.test() 361 #return 0 362 #self._adapter = self.communicator().createObjectAdapterWithEndpoints("SyncEndPoint") 363 self._log = log.Logger(self.getPropertyValue('logfile','endpoint.log')) 364 self._adapter = self.communicator().createObjectAdapter("SyncEndPoint") 365 self._adapter.add( self.service, self.communicator().stringToIdentity('endpoint')) #服务入口接口对象 366 self._adapter.activate() 367 self._log.debug( 'endpoint (%s) service start!'%VERSION) 368 self.communicator().waitForShutdown() 369 return 0 370 371 def getPropertyValue(self,propName,default=''): 372 return self.communicator().getProperties().getPropertyWithDefault(propName,default) 373 374 def getPropertyIntValue(self,propName,default=0): 375 try: 376 default = int(default) 377 except: 378 default = 0 379 return self.communicator().getProperties().getPropertyAsIntWithDefault(propName,default) 380 381 382 383 ############################################################## 384 ############################################################## 385 386 if __name__=='__main__': 387 #print win32con.PROCESS_TERMINATE 388 #ps = os.popen('tasklist').read() 389 #f = open('c:/dump.txt','w') 390 #f.write(ps) 391 #f.close() 392 #print ps 393 #sys.exit() 394 if len(sys.argv) >=3: 395 if sys.argv[1] =='-d': 396 delay = 5 397 try: 398 delay = int(sys.argv[2]) 399 except: 400 pass 401 time.sleep(delay) #延迟 402 #print sys.argv 403 server = sepApp() 404 sys.exit(server.main(sys.argv, "../etc/jerry.conf")) 405 406 407 408
sync_server.py 网管服务程序
1 # -*- coding:utf-8 -*- 2 3 # revisions: 4 # 2009.11.05 scott 5 # 1. created 6 7 # 2009.12.26 scott created 8 9 # 2010.1.26 scott 10 # 1. 增加远程文件比对的操作接口 11 12 import sys,os 13 sys.path.insert(0,'C:\Ice-3.3.1-VC90\python') 14 sys.path.append('../lib') 15 import traceback,threading,time,struct,os,os.path,shutil,distutils.dir_util,array,base64,zlib,struct,binascii 16 import copy,socket,select,getopt 17 import codec,log,config 18 19 import Ice 20 21 Ice.loadSlice('-I../idl -IC:\Ice-3.2.1\slice ../idl/sync.ice') 22 from games import * 23 24 DELIMITER='*' 25 ############################################################## 26 27 class syncApp(Ice.Application): 28 def __init__(self): 29 self._conf = config.SimpleConfig() 30 31 32 self._mtx_hosts= threading.Lock() 33 self._game_hosts=[] 34 35 self._gamelist=[] 36 ################################ 37 38 39 def init_hosts(self): 40 #初始化游戏客户机信息 41 #try: 42 # fp = open('../etc/hosts.list','r') 43 # lines = fp.readlines() 44 # fp.close() 45 # for l in lines: 46 # host,game = l.strip().split(',') 47 # host = host.strip() 48 # game = game.strip() 49 # if len(host) == 0 or len(game)==0: 50 # continue 51 # self._game_hosts.append({'host':host,'game':game}) 52 #except: 53 # print 'Error: read game host failed!' 54 # return False 55 return True 56 57 #初始化游戏目录 58 def init_games(self): 59 self._log = log.Logger(self.getPropertyValue('logfile','server.log')) 60 self._errlog = log.Logger(self.getPropertyValue('errlogfile','error.log')) 61 #names = self.getPropertyValue('games') 62 #games=names.split(',') 63 #for g in games: 64 # g = g.strip() 65 # if len(g) == 0: 66 # continue 67 # gi = gameInfoT() 68 # gi.name = g 69 # gi.path = self.getPropertyValue("%s.path"%g).strip().lower() 70 # gi.dest_path = self.getPropertyValue("%s.dest_path"%g).strip().lower() 71 # gi.launch_app = self.getPropertyValue("%s.launch.app"%g) 72 # gi.launch_params = self.getPropertyValue("%s.launch.params"%g) 73 # self._gamelist.append(gi) 74 75 def getLogger(self): 76 return self._log 77 78 def getErrLogger(self): 79 return self._errlog 80 81 def getGameList(self): 82 return self._gamelist 83 84 #return host info ,else none 85 def get_gamehost(self): 86 host =None 87 self._mtx_hosts.acquire() 88 if len(self._game_hosts): 89 host = self._game_hosts.pop(0) 90 self._mtx_hosts.release() 91 return host 92 93 94 def test(self): 95 port = self.getPropertyValue('endpoint.port') 96 servant = self.getPropertyValue('endpoint.servant') 97 uri = "%s:tcp -p %s -h %s"%(servant,port,'localhost') 98 hostprx = ISyncEndpointPrx.uncheckedCast(self.communicator().stringToProxy(uri)) 99 for i in range(100000): 100 print hostprx.getTimestamp() 101 time.sleep(0.5) 102 103 def killapp(self,host,port,procname): 104 try: 105 servant = self.getPropertyValue('endpoint.servant') 106 uri = "%s:tcp -p %s -h %s"%(servant,port,host) 107 hostprx = ISyncEndpointPrx.uncheckedCast(self.communicator().stringToProxy(uri)) 108 print u"终止远程进程: %s-%s"%(host,procname) 109 hostprx.killProcessByName(procname) 110 except: 111 print traceback.print_exc() 112 113 def launchapp(self,host,port,procname,param=''): 114 try: 115 servant = self.getPropertyValue('endpoint.servant') 116 uri = "%s:tcp -p %s -h %s"%(servant,port,host) 117 hostprx = ISyncEndpointPrx.uncheckedCast(self.communicator().stringToProxy(uri)) 118 print u"创建远程进程: %s-%s"%(host,procname) 119 params={'app':'','param':''} 120 params['app'] = procname 121 params['param']=param 122 hostprx.launchApp(params) 123 except: 124 print traceback.print_exc() 125 126 127 128 #执行endpoint的更新操作 129 def shutdown(self,host,port): 130 try: 131 servant = self.getPropertyValue('endpoint.servant') 132 uri = "%s:tcp -p %s -h %s"%(servant,port,host) 133 hostprx = ISyncEndpointPrx.uncheckedCast(self.communicator().stringToProxy(uri)) 134 print u"执行shutdown: %s"%(host) 135 hostprx.shutdown() 136 except: 137 print traceback.print_exc() 138 139 140 #通知远端主机启动游戏 141 def launchGames(self,host,port,gamepath): 142 try: 143 if True: 144 servant = self.getPropertyValue('endpoint.servant') 145 uri = "%s:tcp -p %s -h %s"%(servant,port,host) 146 hostprx = ISyncEndpointPrx.uncheckedCast(self.communicator().stringToProxy(uri)) 147 #检索主机运行游戏的远端存储目录 148 print u"启动游戏(%s) of host(%s)"%(gamepath,host) 149 try: 150 r = hostprx.launchGame(gamepath) 151 except: 152 print "Error: Endpoint Host(%s) Exception Occurred! Skipped"%(host) 153 except: 154 print traceback.print_exc() 155 156 def terminateGames(self,host,port,gamepath): 157 try: 158 if True: 159 servant = self.getPropertyValue('endpoint.servant') 160 uri = "%s:tcp -p %s -h %s"%(servant,port,host) 161 hostprx = ISyncEndpointPrx.uncheckedCast(self.communicator().stringToProxy(uri)) 162 print "stop game(%s) of host(%s)"%(gamepath,host) 163 try: 164 r = hostprx.terminateGame(gamepath) 165 except: 166 print "Error: Endpoint Host(%s) Exception Occurred! Skipped"%(host) 167 except: 168 print traceback.print_exc() 169 170 #执行远程批处理文件 171 def excbat(self,host,port,command): 172 try: 173 hosts = sys.argv[2] 174 logfile = 'excbat.log' 175 servant = self.getPropertyValue('endpoint.servant') 176 uri = "%s:tcp -p %s -h %s"%(servant,port,host) 177 hostprx = ISyncEndpointPrx.uncheckedCast(self.communicator().stringToProxy(uri)) 178 if True: 179 r = hostprx.executeBatchFile(command) 180 if r.has_key('verbose'): #写入执行日志 181 print r['verbose'] 182 except: 183 print traceback.print_exc() 184 185 def update_file(self,hostprx,local,remote,partfile): 186 try: 187 188 file = os.path.normpath(local+'/'+partfile) 189 fp = open(file,'rb') 190 hostprx.syncFileStart("%s%s"%(remote,partfile)) 191 while True: 192 bytes = fp.read(1024*100) 193 if not bytes: 194 break 195 hostprx.syncFileData(bytes) 196 fp.close() 197 hostprx.syncFileEnd() 198 except: 199 print traceback.print_exc() 200 #self._app.getErrLogger().error("update file failed:(%s)"%(path)) 201 return False 202 return True 203 204 def update_game(self,hostprx,local,remote,hostname=''): 205 #路径参数都不携带尾端的分隔符 206 try: 207 #读取忽略文件列表 208 skipfiles=[] 209 try: 210 fp = open("%s/skipfiles.txt"%local,'r') 211 lines = fp.readlines() 212 for line in lines: 213 line = line.strip().lower() 214 if len(line) and line[0]!='#': 215 line= os.path.normpath(line) 216 if line[0]!='\\': 217 line = '\\'+line 218 skipfiles.append(line) #添加进忽略列表 219 fp.close() 220 except: 221 pass 222 #************************************** 223 #请求远程目录文件清单 224 print u'计算远端主机数据摘要' 225 filelist = hostprx.getFileDescList(remote) 226 227 #去除目录前缀 228 for n in range(len(filelist)): 229 idx = filelist[n].filename.index(remote) 230 filelist[n].filename = filelist[n].filename[idx+len(remote):] 231 #访问游戏列表内的文件存储信息,读取摘要信息到内存 232 233 digest = os.path.normpath("%s/digest.md5"%local) 234 #print digest 235 fp = open(digest,'r') 236 lines = fp.readlines() 237 fp.close() 238 ##比对远端主机多余的文件,必须先删除 239 removelist=[] 240 #print filelist 241 #print lines 242 for f in filelist: 243 found = False 244 for line in lines: 245 path,size,digest = line.split(DELIMITER) 246 if path == f.filename: 247 found = True 248 #print path 249 break 250 if found : 251 continue 252 #print f.filename 253 #2009-12-25 忽略删除远端文件 254 skipmatch = False 255 for skfile in skipfiles: 256 if f.filename.find(skfile)!=-1: # found 257 skipmatch = True 258 break 259 if not skipmatch: 260 removelist.append(f.filename) 261 #---------------------------- 262 #if skipfiles.count(f.filename)!=0: 263 # removelist.append(f.filename) 264 #先执行删除远端多余文件 265 #print removelist 266 if len(removelist): 267 print u"远端共有(%s)个文件将被删除."%(len(removelist)) 268 #请求删除远端主机差异文件 269 for file in removelist: 270 hostprx.deleteFile(remote + file) 271 # 272 #print filelist 273 updatefiles=[] 274 for line in lines: 275 path,size,digest = line.split(DELIMITER) 276 #比对文件是否存在和摘要,文件大小不同可即刻判别 277 needup = True 278 for f in filelist: #远端文件 279 if f.filename == path and int(size) == f.size and digest.strip() == f.digest.strip(): #文件同名 280 needup = False 281 break 282 #2009-12-25 忽略更新文件 283 if needup: 284 skipmatch = False 285 for skfile in skipfiles: 286 if path.find(skfile)!=-1: # found 287 skipmatch = True 288 break 289 if skipmatch: #确定是要忽略的文件 290 needup = False 291 292 #if skipfiles.count(path)!=0: 293 # needup = False 294 295 if needup: 296 updatefiles.append(path) 297 298 for n in range(len(updatefiles)): 299 file = updatefiles[n] 300 print "(%s/%s)%s/%s/%s"%( 301 n+1,len(updatefiles),str(hostname),local,file) 302 #file = os.path.normpath(local+"/"+file) 303 if not self.update_file(hostprx,local,remote,file): 304 self._app.getErrLogger().error("update file failed: host(%s),game(%s),file(%s)"%(str(hostname),game.name,file)) 305 return False 306 except: 307 print traceback.print_exc() 308 return False 309 return True 310 311 def diff_game(self,hostprx,local,remote,hostname=''): 312 #路径参数都不携带尾端的分隔符 313 try: 314 #读取忽略文件列表 315 skipfiles=[] 316 try: 317 fp = open("%s/skipfiles.txt"%local,'r') 318 lines = fp.readlines() 319 for line in lines: 320 line = line.strip().lower() 321 if len(line) and line[0]!='#': 322 line= os.path.normpath(line) 323 if line[0]!='\\': 324 line = '\\'+line 325 skipfiles.append(line) #添加进忽略列表 326 fp.close() 327 except: 328 pass 329 #************************************** 330 #请求远程目录文件清单 331 #print u'计算远端主机数据摘要' 332 filelist = hostprx.getFileDescList(remote) 333 334 #去除目录前缀 335 for n in range(len(filelist)): 336 idx = filelist[n].filename.index(remote) 337 filelist[n].filename = filelist[n].filename[idx+len(remote):] 338 339 digest = os.path.normpath("%s/digest.md5"%local) 340 #print digest 341 fp = open(digest,'r') 342 lines = fp.readlines() 343 fp.close() 344 ##比对远端主机多余的文件,必须先删除 345 removelist=[] 346 #print filelist 347 #print lines 348 for f in filelist: 349 found = False 350 for line in lines: 351 path,size,digest = line.split(DELIMITER) 352 if path == f.filename: 353 found = True 354 #print path 355 break 356 if found : 357 continue 358 #print f.filename 359 #2009-12-25 忽略删除远端文件 360 skipmatch = False 361 for skfile in skipfiles: 362 if f.filename.find(skfile)!=-1: # found 363 skipmatch = True 364 break 365 if not skipmatch: 366 removelist.append(f.filename) 367 368 if len(removelist): 369 pass#print u"远端共有(%s)个文件将被删除."%(len(removelist)) 370 for file in removelist: 371 print "[+]"+file 372 # 373 #print filelist 374 updatefiles=[] 375 for line in lines: 376 path,size,digest = line.split(DELIMITER) 377 #比对文件是否存在和摘要,文件大小不同可即刻判别 378 needup = True 379 for f in filelist: #远端文件 380 if f.filename == path and int(size) == f.size and digest.strip() == f.digest.strip(): #文件同名 381 needup = False 382 break 383 #2009-12-25 忽略更新文件 384 if needup: 385 skipmatch = False 386 for skfile in skipfiles: 387 if path.find(skfile)!=-1: # found 388 skipmatch = True 389 break 390 if skipmatch: #确定是要忽略的文件 391 needup = False 392 393 if needup: 394 updatefiles.append(path) 395 396 for n in range(len(updatefiles)): 397 file = updatefiles[n] 398 print "[-]"+file 399 except: 400 print traceback.print_exc() 401 return False 402 return True 403 404 def update(self,host,port,localpath,remotepath): 405 if True: 406 try: 407 408 localpath = os.path.normpath(localpath.strip().lower()) 409 if localpath[-1] =='\\': 410 localpath = localpath[:-1] 411 remotepath = os.path.normpath(remotepath.strip().lower()) 412 if remotepath[-1] =='\\': 413 remotepath = remotepath[:-1] 414 415 servant = self.getPropertyValue('endpoint.servant') 416 uri = "%s:tcp -p %s -h %s"%(servant,port,host) 417 hostprx = ISyncEndpointPrx.uncheckedCast(self.communicator().stringToProxy(uri)) 418 self.getLogger().debug(u"开始同步主机:%s"%host) 419 if not self.update_game(hostprx,localpath,remotepath,host): 420 self.getErrLogger().error("update host(%s),game(%s)failed!"%(host,localpath)) 421 return False 422 except: 423 print traceback.print_exc() 424 self.getErrLogger().error("update host(%s) failed!"%(host)) 425 return False 426 return True 427 428 #本地和远程目录比对 429 #本地必须存在digest.md5,如不存在则先 syncserver2 update 生成 430 def diff(self,host,port,localpath,remotepath): 431 print '='*50 432 print "Diff HOST:%s,PATH:%s"%(host,remotepath) 433 print time.asctime() 434 print '-'*50 435 if True: 436 try: 437 localpath = os.path.normpath(localpath.strip().lower()) 438 if localpath[-1] =='\\': 439 localpath = localpath[:-1] 440 remotepath = os.path.normpath(remotepath.strip().lower()) 441 if remotepath[-1] =='\\': 442 remotepath = remotepath[:-1] 443 444 servant = self.getPropertyValue('endpoint.servant') 445 uri = "%s:tcp -p %s -h %s"%(servant,port,host) 446 hostprx = ISyncEndpointPrx.uncheckedCast(self.communicator().stringToProxy(uri)) 447 448 #self.getLogger().debug(u"开始同步主机:%s"%host) 449 450 if not self.diff_game(hostprx,localpath,remotepath,host): 451 self.getErrLogger().error("update host(%s),game(%s)failed!"%(host,localpath)) 452 return False 453 except: 454 print traceback.print_exc() 455 self.getErrLogger().error("update host(%s) failed!"%(host)) 456 return False 457 print '='*50 458 return True 459 460 def usage(self): 461 msg =\ 462 '''sync_server(v0.2.1) scott 463 usage:\nsync_server.exe [-d filepath | -h host -p port -k [startgame|update|endgame|excbat] ] 464 -d filepath - 计算文件摘要,filepath文件目录 465 -h hostname - 主机ip或者域名 466 -p port - 主机端口 467 -k startgame dest - 加载游戏运行,dest - 远程目录 468 -k update src dest - 执行文件同步,src-本地目录;dest-远程目录 469 -k endgame dest - 终止游戏,dest - 远程目录 470 -k excbat batfile - 执行远程文件,batfile带路径的远程主机批处理文件或者命令[blocked] 471 -k shutdown - 关闭endpoint服务 472 -k killapp procname - 终止指定名称的进程 473 -k launchapp procname - 创建进程 474 -k diff src dest - 比对本地与远程目录,src-本地目录;dest-远程目录 475 ''' 476 print msg.decode('utf-8') 477 478 def run(self, args): 479 self.init_games() 480 # init game hosts 481 self.init_hosts() 482 483 host='localhost' 484 port=5000 485 try: 486 opts, args = getopt.getopt(sys.argv[1:], "h:p:d:k:", ["help", "output="]) 487 except getopt.GetoptError, err: 488 self.usage() 489 return 0 490 for o, a in opts: 491 if o == "-h": 492 host = a 493 elif o =='-p': 494 port = int(a) 495 elif o == '-d': # calc digest 496 if not a: 497 self.usage() 498 else: 499 self.gen_digest(a) #计算摘要 500 return 0 501 elif o =='-k': 502 if a=='startgame': #launch game, -k startgame remote_dir 503 if not host or not port:self.usage();return 0 504 if len(args): 505 self.launchGames(host,port,args[0]) #args[0] - 文件目录 506 else: 507 self.usage() 508 return 0 509 elif a=='endgame': 510 if not host or not port:self.usage();return 0 511 if len(args): 512 self.terminateGames(host,port,args[0]) 513 else: 514 self.usage() 515 return 0 516 elif a == 'update': 517 if not host or not port:self.usage();return 0 518 if len(args) >=2: 519 self.update(host,port,args[0],args[1]) 520 else: 521 self.usage() 522 return 0 523 elif a == 'diff': 524 if not host or not port:self.usage();return 0 525 if len(args) >=2: 526 self.diff(host,port,args[0],args[1]) 527 else: 528 self.usage() 529 return 0 530 elif a == 'excbat': 531 if not host or not port:self.usage();return 0 532 if len(args): 533 self.excbat(host,port,args[0]) 534 else: 535 self.usage() 536 return 0 537 elif a == 'shutdown': 538 if not host or not port:self.usage();return 0 539 self.shutdown(host,port) 540 return 0 541 elif a == 'killapp': 542 if not host or not port:self.usage();return 0 543 if len(args): 544 self.killapp(host,port,args[0]) 545 else: 546 self.usage() 547 return 0 548 elif a == 'launchapp': 549 if not host or not port:self.usage();return 0 550 if len(args): 551 param='' 552 553 if len(args) >1: 554 param = args[1] 555 self.launchapp(host,port,args[0],param) 556 else: 557 self.usage() 558 return 0 559 560 else: 561 self.usage() 562 return 0 563 self.usage() 564 return 0 565 566 def getPropertyValue(self,propName,default=''): 567 return self.communicator().getProperties().getPropertyWithDefault(propName,default) 568 569 def getPropertyIntValue(self,propName,default=0): 570 try: 571 default = int(default) 572 except: 573 default = 0 574 return self.communicator().getProperties().getPropertyAsIntWithDefault(propName,default) 575 576 #在游戏目录下计算并存放摘要信息,一个游戏目录支持在一个摘要文件 577 def gen_digest(self,filepath): 578 NOCARE_FILES=['digest.md5','skipfiles.txt'] 579 #gamelist = self.getGameList() 580 #for game in gamelist: 581 if True: 582 try: 583 filepath = filepath.strip().lower() 584 filepath = os.path.normpath(filepath) 585 if filepath[-1]=='\\': #rid of terminated character 586 filepath=filepath[:-1] 587 file = "%s/digest.md5"%filepath 588 589 fp = open(file,'w') 590 self.getLogger().debug(u"扫描文件目录(%s)"%(filepath)) 591 #不能记录game.path前缀 592 for root, dirs, files in os.walk(filepath, topdown=False): 593 for name in files: 594 if NOCARE_FILES.count(name)!=0: 595 continue 596 file = os.path.join(root, name).lower().strip() 597 size = os.stat(file).st_size 598 digest = codec.calcFileMd5Digest(file) 599 if size == 0: 600 #print file,size,digest 601 #return 602 pass 603 idx = file.index(filepath) 604 file = file[idx+len(filepath):] #保留\ 605 fp.write(file+DELIMITER+str(size)+DELIMITER+digest+"\n") 606 fp.close() 607 except: 608 print traceback.print_exc() 609 pass 610 611 ############################################################## 612 613 ############################################################## 614 if __name__=='__main__': 615 616 server = syncApp() 617 sys.exit(server.main(sys.argv, "../etc/server.conf")) 618 619 620 621
|