一 : A4纸大小及A4纸详细介绍
今天办公室新进了一个文员妹纸,是由我负责对她的工作进行安排。二 : firefly代码流程超详细介绍
原地址:
不懂后台的前端不是一个好美工!
1、目前的全部学习代码已经上传: htt空格ps://github.com/chenee/firefly_study;后面的笔记会参考这份学习代码。[www.61k.com]
2、附件为完整文档的word版本。
3、文章同发我的csdn博客。htt空格p://blog.csdn.net/chenee543216,有任何问题欢迎给我留言
最近在看firefly的代码,想想不如精读一下,果断入门server编程吧。
我会分步post关于firefly的代码阅读和学习笔记。
首先,firefly是国人开源的server,非常值得推介。网站: ww空格w.9miao.com, QQ群:153643834
firefly是基于twisted开发的,关于twisted和firefly的详细内容,由于我从来不打草稿,所以想到那里写到那里。
另外由于我压根是入门菜菜,所以必须有很多错误需要大家来批评,鞭策,指正。在这里先谢过。
游戏服务器学习笔记 2———— 准备工作
我看的firefly版本是 for 暗黑世界的, 服务器版本 1.6 ,客户端版本 1.4; 下载地址为: ww空格w.9miao.com ,官网里面找,需要铜板 ? 。
客户端是用cocos2dx-V2.1.4实现,由于是分析server代码所以,client端我会在需要的时候简单提一下。不做重点。
服务端代码实际上分成2个部分,需要安装的firefly python库和直接运行的暗黑世界python 代码。
这里是官方的wiki: htt空格p://firefly.9miao.com/diabloworld_wiki/index.htm
搭建环境问题,我就不赘述了。到这里假设大家server和client都跑了起来,并且功能正常。(如果有安装问题可以去9秒论坛问)
ok,由于是要研究server代码,所以我们直接把firefly从系统安装目录copy到当前的Diablo目录。然后删掉firefly安装包。(假设大家已经知道python 库的安装相关知识)
我用的是mac系统,终端ls看的话,目录结构应该是这样。
app appmain.py config.json firefly memcached.sh startmaster.py tags tool
这两个是我自己加的。
memcached.sh : (memcache 命令自己google)
$ cat memcached.sh
memcached -d -m 100 -c 1000 -u root -p 11211
tags: ctags –R的生成文件(ctags自己google)
OK,下面可以开始读代码了。
这里推荐一下pyCham,这个python编辑器非常好用
游戏服务器学习笔记 3———— firefly 的代码结构,逻辑
注:以下所有代码都是拿暗黑来举例,由于本人能力有限很多地方还没有看透彻,所以建议大家只是参考。有不对的地方非常欢迎指正。
一、结构
系统启动命令是,python statmaster.py,启动以后可以通过ps看到系统启动了以下几个进程:
1、master:管理模块,通过subprocess.Popen()来启动其它模块,该模块启动一个webserver,简单的通过监听本机9998端口,用get方法来获取用户管理命令,目前默认的是2条命令,stop和reload,负责其它模块的stop,reload功能。
只要在本机浏览器输入:htt空格p://localhost:9998/stop 或者htt空格p://localhost:9998/reload 即可。
2、dbfront:数据库前端模块,负责管理DB和Memcache。比如load用户信息到memcache中,定期(系统写死了1800秒)刷新并同步memcache.
3、gate:这个其实是真正的center,其它模块(除了dbfront)都会和这个模块挂接(通过twisted.pb 后面会抽空详细说明)。
4、net:网络模块,负责监听客户端tcp/ip连接,转发相应的命令数据包给gate。
5、game1:暗黑世界的游戏模块,这个模块里面会处理几乎所有的游戏逻辑,存储所有的游戏数据:比如角色升级的经验等级,各种npc信息,各种掉落信息,各种战斗阵型。这些数据在系统启动前都是保存在mysql里面,game1模块负责load到自己的内存里面(注意,不是memcache里面,而是直接内存)
6、admin:系统管理员模块,其实这个模块对于游戏本身来说,可有可无,主要作用就是导出游戏统计数据,比如在线人数,每天充值数量等等。。。。无非就是简单的load数据库内容在简单做些计算而已,我们不做重点。
二、逻辑
系统启动的过程是:(只看firefly,不管memcache,mysql等)
$ python startmaster.py
startmaster.py 这个python脚本会实例化class master;
按照顺序启动
一个pb.root
一个webserver
然后是subprocess.Popen(cmd) 来启动其它子模块。
cmd命令打印出来为:
python appmain.py dbfront config.json
python appmain.py gate config.json
python appmain.py net config.json
python appmain.py game1 config.json
python appmain.py admin config.json
全部启动起来以后,逻辑关系如下:
<ignore_js_op>
master虽然通过红线连接每个模块,但是实际上的工作只是启动和管理,并没有很大的数据交互。
db模块虽然只是和master连接看上去会和其它节点断开,但是它负责memcache的逻辑。
Admin模块虽然挂在这里,但是其基本上只负责统计和“管理员”功能。
大的数据交流,网络服务,游戏逻辑等工作只发生在gate,net,game1 这几个节点。并且根据游戏内容不一样,完全可以进行扩展。(由于俺没有研究过,先不在这里讨论)。
OK,架构简介到这里,后面我们每个模块分开详细介绍。
游戏服务器学习笔记 4———— master 模块介绍
(模块的介绍方法都是先说大体功能,在捡一些细节详细讨论。)
master 类很简单,就3个函数,一个init,设置配置信息,并调用masterapp,然后还有一个循环启动子进程的start函数。 这里只有masterapp函数值得我们关注。 代码如下:
36 def masterapp(self): 37 config = json.load(open(self.configpath,'r')) 38 mastercnf = config.get('master') 39 rootport = mastercnf.get('rootport') 40 webport = mastercnf.get('webport') 41 masterlog = mastercnf.get('log') 42 self.root = PBRoot() 43 rootservice = services.Service("rootservice") 44 self.root.addServiceChannel(rootservice) 45 self.webroot = vhost.NameVirtualHost() 46 self.webroot.addHost('0.0.0.0', './')
47 GlobalObject().root = self.root 48 GlobalObject().webroot = self.webroot 49 if masterlog: 50 log.addObserver(loogoo(masterlog))#日志处理 51 log.startLogging(sys.stdout) 52 import webapp 53 import rootapp 54 reactor.listenTCP(webport, DelaySite(self.webroot)) 55 reactor.listenTCP(rootport, BilateralFactory(self.root))
实际上我不喜欢这种编码风格,感觉有点乱,有些过度使用import和python的修饰符。 仔细看,这里首先通过config.json读取配置信息,然后根据配置信息,起一个pb.root,和一个webserver,然后给pb.root 加一个services,这个services类是个非常重要的类,贯穿整个系统。我们下面会详细介绍它。 这里还通过import webapp 和修饰符@xxx的方法来实现给webserver添加stop 和reload 2个child。实现的功能,我前面其实已经是说过。就是在浏览器里面输入 http://localhost:9998/stop 或者http://localhost:9998/reload 来调用对于的类。具体实现的方法是: webroot = vhost.NameVirtualHost() webroot.putChild(cls.name, cls()) ;
这个vhost.NameVirtualHost().putChild()函数也是twisted的函数,和前面pb.root一样,大家如果等不及我后面的解说可以自己google到twisted网站,上面有详细的doc、samples。
由于看的实在不习惯(可能自己是python、server的新手),所以我就自己按照功能实现改了一下结构,如下,希望大家对比可以更加清晰。(我改动后的所有代码都会抽空上传到github。地址为:https://github.com/chenee 如果没有说明我还没来得及上传,在等等,或者直接M我要。)
22 class Master: 23 def init(self, configpath, mainpath): 24 """ 25 """ 26 self.configpath = configpath 27 self.mainpath = mainpath 28 29 config = json.load(open(self.configpath, 'r')) 30 mastercnf = config.get('master') 31 self.rootport = mastercnf.get('rootport') 32 self.webport = mastercnf.get('webport') 33 self.masterlog = mastercnf.get('log') 34 35 def __startRoot(self): 36 root = PBRoot("rootservice") 37 GlobalObject().root = root 38 reactor.listenTCP(self.rootport, BilateralFactory(root)) 39 40 41 def __startWeb(self): 42 webroot = vhost.NameVirtualHost() 43 webroot.addHost('0.0.0.0', './') 44 GlobalObject().webroot = webroot 45 webapp.initWebChildren()
46 reactor.listenTCP(self.webport, DelaySite(webroot)) 47 48 49 def startMaster(self): 50 51 self.__startRoot() 52 self.__startWeb() 53 54 if self.masterlog: 55 log.addObserver(loogoo(self.masterlog))#日志处理 56 log.startLogging(sys.stdout) 57 58 # reactor.run() 59 60 def startChildren(self): 61 """ 62 """ 63 print "start children ......" 64 config = json.load(open(self.configpath, 'r')) 65 sersconf = config.get('servers') 66 for sername in sersconf.keys(): 67 cmds = 'python %s %s %s' % (self.mainpath, sername, self.configpath) 68 subprocess.Popen(cmds, shell=True) 69 reactor.run()
我把原先通过addServiceChannel()添加services的过程放到PBRoot类的init里面了,这样改动也适合后面其它模块,反正root逻辑上肯定是需要一个services的。而且这个services就是普通services。(后面还会提到一些services的子类)
另外,把原先通过import webapp 加用修饰类实现的putChild()功能,直接写到一个注册函数里面。 45 webapp.initWebChildren()
addToWebRoot(stop) addToWebRoot(reloadmodule)
改动以后的功能和原先一模一样,改动后的代码对我等新手来说可以清晰的看到master模块的结构
OK,下面我们来看刚才提到的services。客户端所有的命令最终都是通过services的
callTarget(self, targetKey,*args,**kw) 函数来分发。 比如client端发一条编号为01的命令,或者一条“login”命令,server端到底执行什么处理函数,就是通过services来实现的,具体实现实际上就是在services类里面通过 self._targets = {} # Keeps track of targets internally 这个字典来保存命令ID/名称 和具体命令实现函数的对应关系。 注册、和注销这个对应关系的函数为services类的:mapTarget() 、unMapTarget()。
每个模块(master,gate,net。。。)都有对应的services,但是可能不止一个。 模块之间提供服务,也是通过实现一个services实例,并注册一批相应处理函数来实现的。
OK,到这里master基本介绍完毕。 由于master的webserver功能比较简单,而且和系统的其它模块基本无关。大家可以通过twisted官网的DOC和sample来了解,我就不赘述了。
API:
Twisted Web In 60 Seconds:
下篇文章我尽力介绍twisted的PB(Perspective Broker,透明代理)
游戏服务器学习笔记 5———— twisted Perspective Broker 透明代理
实际上这章压根不需要我来说,twisted官网的Doc里面有专门介绍的章节。写的非常详细。
我只能肤浅的说说firefly里面对PB的运用。
首先firefly使用PB的目的是实现各个模块之间的通信,做到“分布式”,逻辑分离。
比如master模块专门负责控制,gate做分发,game1做游戏逻辑,net做网络相关。如果不用twisted.pb的话,我们就要自己写复杂socket逻辑,来实现各个节点之间的数据通讯。 现在有了twisted.pb,“妈妈再也不用担心我的学习”。
Firefly所有的分布式相关代码都在firefly/distribute/目录 init.py child.py manager.py node.py reference.py root.py
root.py 实现PB的server功能 node.py 实现PB的client功能。 child.py 每个client连接上root都会初始化一个child来保存该client的相关信息,并且将这些child通过manager来管理。 manager.py 管理root的child,通过一个字典self._childs = {},实现一个增删改的小管理器。 reference.py 如果你看了前面twisted官网的介绍就会知道,node只要实例化一个 pb.Referenceable 类,并把它传递给root,那么root就能够把这个pb.Referenceble当成句柄来远程调用client的函数。
前面章节提到master模块实现了一个PBRoot作为server等待client端的连接。我们这里先拿DB模块来说明。(DB模块的其它功能,和我改写的部分后面会详细介绍。)
master模块里面实现的代码如下(这个是我改过的代码,稍后上传git): 38 def __startRoot(self): 39 GlobalObject().root = PBRoot("rootservice") 40 reactor.listenTCP(self.rootport, BilateralFactory(GlobalObject().root))
其中PBRoot类有2个关键函数。
def remote_register(self,name,transport): """设置代理通道 @param addr: (hostname,port)hostname 根节点的主机名,根节点的端口 """ log.msg('node [%s] registerdd' % name) child = Child(name,transport) # child.setTransport(transport) self.childsmanager.addChild(child)
def remote_callTarget(self,command,*args,**kw): """远程调用方法 @param commandId: int 指令号 @param data: str 调用参数 """ data = self.service.callTarget(command,*args,**kw) return data
remote_register(),这个函数名称被我改了,原先好像叫做remote_takeproxy()。大家理解的角度不一样,原先作者lan可能是认为这个函数的功能是root取得其它模块提供给他的代理。我认为,这个函数是其它模块注册到root。
PB的约定是,本地函数起名remote_xxx(),远程函数调用 直接callremote(“XXX”),所以按照习惯,大家看到的remote_xxx()函数都是提供给对方调用的。
另外,这里补充一下,twisted官网提到,PB一旦建立好连接以后,server和client的行为其实是对等的,大家权限,调用都一样。
再看一下上面2个函数,regist可以看出就是用child类来保存一下注册过来的client。callTarget函数就是通过services来执行远程的调用命令。具体的callTarget逻辑后面有空再介绍。
下面 我们看client端,拿DB来说。和master模块不一样,其它模块,包括dbfront,启动过程依赖配置文件config.json的设定后面详细讨论。这里我们只关注PB相关。
下面的代码取自firefly/server/server.py (实际上已经被我整理过,但具体代码逻辑还是一样) 59 if masterconf: #这里一定为True 60 masterport = masterconf.get('rootport') 61 self.master_remote = RemoteObject(servername) 62 addr = ('localhost',masterport) 63 self.master_remote.connect(addr) 64 GlobalObject().masterremote = self.master_remote
这里的RemoteObject类的初始化init函数如下:(firefly/distribute/node.py) def init(self,name): 23 """初始化远程调用对象 24 @param port: int 远程分布服的端口号 25 @param rootaddr: 根节点服务器地址 26 """ 27 self._name = name 28 self._factory = pb.PBClientFactory() 29 self._reference = ProxyReference()#这个就是pb.Referenceable的子类 30 self._addr = None
可以看出我们实现了一个RemoteObject类,这个类包括了pb.PBClientFactory 和pb.Referenceble。在line 63对应的代码里面,我们connect的时候 reactor.connectTCP(addr[0], addr[1], self._factory) 就建立了一个root和node的连接。然后再调用下面的函数。
def register(self): """把本节点注册到RootNode,并且向RootNode发送代理通道对象 """ deferedRemote = self._factory.getRootObject()#取得root的调用句柄。 deferedRemote.addCallback(callBack,'register',self._name,self._reference)#callBack函数会调用pb.callRemote()
这个函数就2行,第一行是twisted.pb的client取得root的句柄,有了这个句柄,我们就能够通过callRemote来调用root的相应函数。这里调用的regist,对应root的remote_regist()函数,并且把自己的referenceble传递给root,那么后面root就可以通过这个referenceble来调用自己(node)了。
OK,firefly对twisted.pb的封装和实现就介绍到这里。PB的介绍先告一段落,由于俺能力实在有限,可能大家还没有看清楚。 别担心,我们后面接着介绍各个模块的过程中也会穿插firefly的PB运用的细节介绍。之后如果有时间精力我们再对各个模块中运用PB实现的功能做个总结。
游戏服务器学习笔记 6———— db模块
前面介绍过master模块,现在我们看看dbfront模块,源码在firefly/dbentrust和app/defront 目录。 顾名思义 entrust 就是数据库托管的意思。这个模块实现的功能就是负责从数据库读取数据,并且缓存到memcache。然后定期的检查缓存并写入更新到DB。
刚刚看到9秒论坛里面有篇文章介绍这个dbentrust库的左右。写的很详细。地址如下: ht空格tp://www.9miao.com/thread-44002-1-1.html 既然文章已经写了很详细的说明,我就偷懒了:)
下面我主要介绍一下db整体模块的结构,流程,逻辑。 前面的章节应该提到过,除了master模块以外,其它模块(db,gate,net,game1,admin)都是通过master的子进程方式启动。启动代码如下:
def startChildren(self): """ """ print "start children ......" config = json.load(open(self.configpath, 'r')) sersconf = config.get('servers') for sername in sersconf.keys(): cmds = 'python %s %s %s' % (self.mainpath, sername, self.configpath) subprocess.Popen(cmds, shell=True) reactor.run()
通过简单加打印便可以发现,这里其实就是“python appmain.py db config.json”
OK,那么我们可以抛开master,单独命令行启动这个db模块。 为了更加清晰的学习代码,我已经把每个模块单独分离开,具体分离后的代码请看github。 地址为:htt空格ps://github.com/chenee/firefly_study
我们下面自己那这份代码解说,大家可以对照源代码进行学习。 (说明,这份代码只是为了学习才拆分开,会存在很多冗余,甚至不一致的地方。仅供参考) 代码目录如下: 1 .
2 ├── app #原先的游戏逻辑目录,这个和firefly库目录对应,存放游戏具体实现。但是这里被我打乱了。 3 │ ├── init.py 4 │ ├── dbfront #数据库操作相关文件目录 5 │ │ ├── McharacterManager.py #角色管理操作文件,从数据库读取所有角色信息,缓存到memcache 6 │ │ ├── init.py 7 │ │ ├── initconfig.py #db模块中游戏部分的初始化文件,负责app目录的内容的加载。 8 │ │ ├── madminanager.py #MAdmin 类的管理类。Madmin下面会提到。 9 │ │ ├── mcharacter.py #角色类,角色在memcache中的映射。 10 │ │ └── memmode.py #几个Madmin类的初始化工作 11 │ ├── dbfrontserver.py #启动接口,唯一作用就是调用initconfig.py 12 │ ├── logs 13 │ │ └── dbfront.log #log 文件 14 │ └── share 15 │ ├── init.py 16 │ └── dbopear #数据库操作文件,对于db模块来说就只使用了一个文件,typo! 17 │ ├── init.py 18 │ └── dbCharacter.py # tb_character角色表的select,update封装类。 19 ├── appmain.py #启动脚本,读config.json配置文件然后初始化DB模块类 20 ├── config.json #配置文件,非常重要的文件 21 ├── dbpool.py #db连接池,原先的文件只提供初始化和取连接池的2个函数。感觉很多dbopear目录的的sql操作完全可以封装,具体见我game1模块里面的改动,其它几个模块的文件可能不同步。最终会按照game1的模式整合。 22 ├── dbserver.py #db模块的类文件,这个对于原先FFServer。针对每个模块我把他改成对应名称,便于理解 23 ├── globalobject.py #全局类,这里的全局指每个模块内部的全局,而不是整个系统的全局。每个模块自己的globalobject类完全可以不同。 24 ├── leafnode.py #就是原先的node.py,在PB那个章节我们介绍过。 25 ├── logobj.py #log 类 26 ├── memclient.py #memcache的客户端实现,提供对memcache的访问操作接口 27 ├── memobject.py # memcached 关系对象通过key键的名称前缀来建立 各个key-value 直接的关系; 比如memobject.name = “tbl_role”, 那么memobject.get(“id”)得到的就是tbl_role:id的值。 28 ├── mmode.py #里面包括2个重要的类,MMode,MAdmin;都是memobject的子类,逻辑上MMode代表内存中的一条数据,MAdmin,代表内存中的一张表。而前面madminanager.py就是这些表的管理类。 MAdmin对应memcache的前缀是表名称:如 tb_item MMode对应memcache的前缀是pk(primary key,主键ID)。如 tb_item:1001 那么基本的一条数据组织的格式是:tbl_item:1001 {id:10001, name:chenee , money:10000};也就是memcache的key是 “ 表名称:该条的主键值”,value是这条内容的json格式。
验证方式,可以telnet到memcache打印出来看结果。(以前做的,现在记不清了,可能有误,此刻我自己还木有验证)
29 ├── reference.py #PB 相关,看前面一章介绍 30 ├── run.sh #shell启动脚本,为了方便,我自己写的。 31 ├── serviceControl.py #对应原先的一个叫做admin.py的文件,其实就是给leafnode加2条命令(stop,reload)这个在PB章节也说过了。 32 ├── services.py #服务类,前面提过 33 ├── singleton.py #单例类,我blog上面有相关阐述,后面一章我粘贴过来。 34 └── util.py #大部分都是sql查询操作的封装函数。 35
仔细看完上面目录介绍,基本上应该对DB的结构有个大致掌握了。下面我们分析一下源码。 启动db模块的命令: $cat run.sh python appmain.py appmain.py 便于学习被我改动过了,如下: if name == "main": servername = "dbfront" config = json.load(open("config.json", 'r'))
dbconf = config.get('db') memconf = config.get('memcached') sersconf = config.get('servers',{}) masterconf = config.get('master',{}) serconfig = sersconf.get(servername) ser = DBServer() ser.config(serconfig, dbconfig=dbconf, memconfig=memconf,masterconf=masterconf) ser.start()
实际上就是实例化DBServer类,把从config.json文件读取的信息传递过去。DBServer就是原先firefly/server/server.py文件。改个名字好看。 config.json 也被我改了一下,“services”里面只保留“dbfront”,其它都services内容都无关。就不贴出来了,占地方。
现在看DBServer(FFServer)类: class DBServer:
def __init__(self): """ """ self.leafNode = None self.db = None self.mem = None self.servername = None def config(self,config,dbconfig = None,memconfig = None,masterconf=None): """配置服务器 """ servername = config.get('name')#服务器名称 logpath = config.get('log')#日志 hasdb = config.get('db')#数据库连接 hasmem = config.get('mem')#memcached连接 app = config.get('app')#入口模块名称 self.servername = servername if masterconf: masterport = masterconf.get('rootport') addr = ('localhost', masterport) self.leafNode = leafNode(servername) self.leafNode.connect(addr) GlobalObject().leafNode = self.leafNode if hasdb and dbconfig: log.msg(str(dbconfig)) dbpool.initPool(**dbconfig) if hasmem and memconfig: urls = memconfig.get('urls') hostname = str(memconfig.get('hostname')) mclient.connect(urls, hostname) if logpath: log.addObserver(loogoo(logpath))#日志处理 log.startLogging(sys.stdout) if app: reactor.callLater(0.1,__import__,app) def start(self): """启动服务器 """ log.msg('%s start...'%self.servername) log.msg('%s pid: %s'%(self.servername,os.getpid())) reactor.run()
根据config.json的解析结果,我们精简掉所有无关内容。发现,DB模块包括以下几个功能模块: mastconfig #说明我们需要连接一个root,也就是前面提到的master模块 db #有数据库操作,需要建立数据池 mem #有memcache操作,要连接memcache。
所有连接信息,如ip、port等都是从config.json里面取得。 1、masterconfig部分,就是前面PB章节的介绍,这里实现leafNode去连接master模块的root,就不再赘述了。 2、db pool部分也很简单,就是建立一个pool,提供一个connection的接口。大家去了解DBUtils.PooledDB这个库就可以了。 3、mem部分,也没有啥可说,纯memclient就是调用python的Memcache而已,memcache的结构又超级简单,就是get,set。不含任何逻辑的。想要实现逻辑关系,都要自己去构建,就是上面我们提到的MMode和MAdmin等文件来实现。
OK,firefly库部分的调用完毕,这个时候DB模块已经建立了,和master的PB连接,数据池,memcache连接。下面就是游戏内容部分的实现了。
除了master模块,其它所有模块的游戏部分(app目录下面的内容)都是通过 if app: reactor.callLater(0.1,import,app) 这种方式来import进来的。对我这种python新手还真的迷惑的半天。实际上就是根据config.json里面对于app项的内容。对于db这里展开是: reactor.callLater(0.1,import, app.dbfrontserver) 就是过0.1秒执行 import app.dbfrontserver。其内容如下: GlobalObject().stophandler = initconfig.doWhenStop initconfig.loadModule() loadModule()干3件事情: def loadModule(): register_madmin() initData() CheckMemDB(1800) 注册几个表,初始化角色数据到内存,同步内存数据到数据库
注册表的代码在mmode.py中,过程就是实例化几个MAdmin来表示相应表的结构,然后添加到MAdminManager这个单例管理类中。 MAdmin有几个属性代表表的主键,外键,表名称等信息。 MAdmin的insert函数会调用父类的Memobject的insert函数。 nowdict = dict(self.dict) del nowdict['_client'] newmapping = dict(zip([self.produceKey(keyname) for keyname in nowdict.keys()], nowdict.values())) self._client.set_multi(newmapping) 实际上就是根据self的所有属性(除了_client,这个属性指的是memclient)来生成一个字典,然后把这个字典的内容缓存到memcache中。 比如tb_item表对应的MAdmin,生成的memcache内容就包括(不限于) Key value tb_item:_name xxxx tb_item:_lock xxxx tb_item:_fk xxxx tb_item:_pk xxxxx 这里其实只是把表结构给缓存到memcache了,压根没有碰表的数据。MAdmin有几个个函数可以取数据, load()#这个是根据表名称,select * 并且一条一条生成MMode,然后缓存进memcache,MMode前面提到过,代表一条数据的内存对应数据结构。
getObj(self,pk): #先判断pk这条数据是否在memcache,是否有效,如果没有再从数据库取出来并同步到memcache中。
这两条函数其实在db模块启动过程中都没有被调用,(可以加断点或者打印验证)
OK,分析到这里下面在看角色初始化initData()的部分就简单了 def initData(self): allmcharacter = dbCharacter.getALlCharacterBaseInfo() for cinfo in allmcharacter: pid = cinfo['id'] mcha = Mcharacter(pid, 'character%d' % pid, mclient) mcha.initData(cinfo) Mcharacter也是MemObject的子类,做的就是根据数据库中的角色信息实例化Mcharacter内存数据,然后调用memobject的insert同步到memcache。 取角色信息的过程相反。调用mcharacterinfo()函数,唯一一点不同是,这个函数有@property修饰,我查了一下,表示这个函数可以当成属性来用,python真酷!
这里吐槽一下注释: 摆明是从啥地方copy过来的,注释的牛头不对马嘴,害的我看了老半天,都木有想明白。 """初始化城镇要塞对象 @param territoryId: int 领地的ID @param guard: int 殖民者的ID @param guardname: str 殖民者的名称 @param updateTime: int 领地被更新的时间 """ 最后再唠叨一下checkAdmins();这个函数负责每隔1800(magic number)秒刷一边MAdminManager类管理的所有MAdmin(表)。调用这些MAdmin对应的checkAll(); 这个checkAll函数会取得memcache中所有缓存数据,比较是否以本表前缀开头,如果是,则判断这些是否有效,是否过期,是否需要写入数据库。。。。
在我看来,这里有些可以优化的逻辑。比如把取memcache所有数据的步骤提到MAdminManager层面,这样每个MAdmin就不用单独执行一遍。 但是如果是多个memcache服务器,又该怎么办?各种头疼,问题太多,智商不够用。
这个函数是魔鬼,我暂时没有敢去动它,等我多学习学习相关内容再去做优化。
游戏服务器学习笔记 : 番外篇———Python 单例介绍
metaclass方式实现
1 首先网上有很多实现方式,而且stackflow里面有大神详细介绍了各种实现。自己google吧,就不贴URL了。
我这里简述原理,放个简单demo帮助理解。 1 class Singleton(type):
2 def init(self, name, bases, dic): 3 print ".... Singleton.init ...." 4 super(Singleton, self).init(name, bases, dic) 5 self.instance = None 6 7 def call(self, *args, **kwargs): 8 print ".... Singleton.call ...." 9 if self.instance is None: 10 self.instance = super(Singleton, self).call(*args, **kwargs) 11 return self.instance 12 13 print "====================== 1 =======================" 14 class test: 15 metaclass = Singleton #call Singleton.init 16 def init(self): 17 print "test.init ..." 18 def call(self): 19 print "test.call ..." 20 21 class test2: 22 metaclass = Singleton #call Singleton.
输出结果:
1 bash-3.2$ python test.py 2 ====================== 1 ======================= 3 .... Singleton.init .... 4 .... Singleton.init .... 5 ====================== 2 ======================= 6 .... Singleton.call .... 7 test.init ... 8 .... Singleton.call .... 9 test2.init ... 10 ====================== 3 ======================= 11 .... Singleton.call .... 12 .... Singleton.call .... 13 4476624848 4476624848 4476670096 4476670096 14 ====================== 4 ======================= 15 test.call ... 16 test2.call ... 17 ====================== 5 ======================= 18 100 19 111
demo中建了2个单例类,test,test2。都是通过metaclass = Singleton的方式。 测试结果: 通过测试输出可以看出,id(a) = id(a1),id(b)=id(b1); 而且创建一个c attribute给a,a1同时也会得到一个一模一样的c,包括c的值。 说明单例OK。
原理: 这里单例是用metaclass方式实现。metaclass的意思就是class的class。 所以一旦设定test和test2的metaclass == Singleton,就会利用Singleton类的实例来作为test,test2的类。(稍微有点拗口)
请注意line 14和line 22,这2个地方对应输出结果里面line 2,line3的打印。说明:我们一旦设定test,test2的metaclass = Singleton了,Singleton就立刻init ,而且对于每个单例类(这里的test或test2)只在定义的时候做一次。
后面我们实例话test,test2的时候:a = test(),b=test2();则系统会调用Singleton的call;实例化几次test,test2,就调用几次Singleton的call 。 所以,我们只要在Singleton的call里面做单例的实现即可。
具体实现方式很简单,看源码即可。
补充一下: test类的init 函数对应 a=test()调用 call函数对应 a()
游戏服务器学习笔记 7 ———— gate模块
前面说过db模块,子模块的启动部分基本都差不多。所以我只介绍不同的地方。gate模块和db模块不同的地方是,gate即作为master的leafnode,同时自己也作为其它模块(net,game1,admin)的root,代码如下。 if rootport: self.root = PBRoot("rootservice") reactor.listenTCP(rootport, BilateralFactory(self.root)) GlobalObject().root = self.root 大家如果看过前面的章节会发现这段代码也很熟悉,对! 它就是master模块的__startRoot函数一样的功能。我们前面在PB章节也详细介绍过。
OK,gate启动代码算介绍完了(怎么感觉什么都没有说呢?),大家瞅一眼我在github上的代码就一目了然了。
下面介绍gate的游戏部分内容,即app目录下面的内容。
这部分代码和db对应部分的调用过程一模一样,我们直接运行到app/gateserver.py。 就一行代码, initconfig.loadModule() 跟进去发现代码如下:(代码我改过) def loadModule(): rservices.init() lservices.init() 实际上gate的app部分工作只是注册root services和local services的命令。 前面我们强调过services,是非常重要的类,系统通过这个类来实现具体功能。一个模块可能有多个services。这里我们就碰到了这种情况。
gate模块有3个services,他们分别是: 1、与master通讯的leafNode的services,包括2条命令:stop和reload。
2、自己作为root的services,通过上面loadModule()里面的rservices.init()注册以下几个命令。代码在:app/gate/rootservice/rservices.py (见我github的目录结构) def init(): addToRootService(forwarding) addToRootService(pushObject) addToRootService(opera_player) addToRootService(netconnlost) 3、一个挂接在 GlobalObject().localservice 的services,这个services通过lservices.init()注册3条命令,如下: addToLocalService(loginToServer_101) addToLocalService(activeNewPlayer_102) addToLocalService(roleLogin_103)
其中 services 1 是leafnode的services,所以会被作为root的master调用。 services 2 是自己作为root的services,所以会被gate的leafnode(net,game1,admin等)调用。 而services 3并没有挂接到某个PB端(root,或者node),它是在services 2的forwarding()函数中通过以下方式调用: def forwarding(key,dynamicId,data): """ """ if key in GlobalObject().localservice._targets: return GlobalObject().localservice.callTarget(key,dynamicId,data) else: xxxxxx xxxxx xxxx #其它代码逻辑 所以,实际上services 3 可以看做services 2的一部分,只是实现上独立出来成为一个services而已。
我们前面PB章节已经说过firefly的分布式调用逻辑。这里补充一下。 net模块(后面会介绍)会receive客户端(cocos2dx)的数据包,解析出具体命令,然后通过PB的方式,把命令当成参数来远程调用gate的PBRoot的remote_callTarget()函数。 而这个remote_callTarget()会进一步调用自己的services,也就是上面提到的services 2来处理命令。
For example: (在firefly源码目录下面有一个tool目录,里面有测试代码clienttest.py可以用来做测试,我们后面介绍net模块的时候尽量详细扒数据,这里只是简单说一下示意流程)
1、客户端登陆,会发送用户名密码的json串给net模块 2、net模块解析出命令为:101, 参数为{username:xxx,password:xxx}; 3、net远程调用gate的remote_callTarget()函数。 4、remote_callTarget()调用PBRoot.services.callTarget,通过解析命令101取得对应的命令函数loginToServer_101()并执行。(这个解析命令过程,就是前面提到mapTarget和unMapTarget函数实现);
OK,数据流我们就简单提一下,帮助大家理解模块功能。后面在net,game1模块流程都介绍完毕以后,我们会专门用1,2个章节来仔细分析分析暗黑的数据格式和各种流程。如果有时间和精力,我们尝试反推一下策划,设计内容。
下面一章我们介绍net,再下一章介绍game1,admin就留给读者自己看吧。
游戏服务器学习笔记 8 ———— net模块
net顾名思义,就是网络模块,负责接受客户端的连接,处理客户端发送过来的数据,解包转发给其它模块。整个firefly系统里面,和用户打交道的也只有这个模块(admin和master虽然提供web操作接口,但是都是服务管理员的)。
我们前面提到,子模块的功能是由config.json来配置驱动的。那么我们看看这个模块的json文件定义了哪些功能。 20 "servers": { 21 "net": { 22 "app": "app.netserver", #具有游戏功能模块,在框架启动完毕后需要import app/netserver.py 文件 23 "log": "app/logs/net.log", #日志 24 "name": "net", #该模块的名称 25 "netport": 11009, #!!重要!! 定义了netport,则firefly会为其启动一个网络服务,来监听客户端连接。 26 "remoteport": [ #需要连接其它node,这里的其它node定义的是gate。实际上我们可以有不止一个“gate”来实现分布式逻辑。 27 { 28 "rootname": "gate", 29 "rootport": 10000 30 } 31 ] 32 }
33 }
那么对应的启动代码如下:(init 和 start函数省略,只列出关键部分)
40 def config(self, config, dbconfig = None,memconfig = None,masterconf=None): 41 """配置服务器 42 """ 43 netport = config.get('netport')#客户端连接 44 gatelist = config.get('remoteport',[])#remote节点配置列表 45 servername = config.get('name')#服务器名称 46 logpath = config.get('log')#日志 47 app = config.get('app')#入口模块名称 48 self.servername = servername 49 50 if masterconf: 51 masterport = masterconf.get('rootport') 52 addr = ('localhost', masterport) 53 leafnode = leafNode(servername) 54 serviceControl.initControl(leafnode.getServiceChannel()) 55 leafnode.connect(addr) 56 GlobalObject().leafNode = leafnode 57 58 if netport: 59 self.netfactory = LiberateFactory() 60 netservice = services.CommandService("netservice") 61 self.netfactory.addServiceChannel(netservice) 62 reactor.listenTCP(netport, self.netfactory)
63 GlobalObject().netfactory = self.netfactory 64 65 for cnf in gatelist: 66 rname = cnf.get('rootname') 67 rport = cnf.get('rootport')
68 self.gates[rname] = leafNode(servername) 69 addr = ('localhost', rport) 70 self.gates[rname].connect(addr) 71 72 GlobalObject().remote = self.gates 73 74 if logpath: 75 log.addObserver(loogoo(logpath)) #日志处理 76 log.startLogging(sys.stdout) 77 78 if app: 79 reactor.callLater(0.1, import, app) 80
可以看出主要启动3个功能: 1、masterconfig部分,是说明本模块作为master模块的leafnode要连接master模块,这个和gate模块一样,可以参考前面PB的介绍章节。
2、连接gate模块,这里代码假定了不止一个需要连接的gate;但是实际上我们config.json里面只设定了一个。 这个步骤和连接master一样。
3、真正新鲜的东西是netport部分,这里 LiberateFactory实际上是protocol.ServerFactory的子类。它使用的协议是LiberateProtocol,这个是protocol.Protocol的子类。
如果大家了解twisted,那么我提到ServerFactory,和Protocol,大家就已经明白了netport部分的原理,压根不需要我废话。所以对于新人,不了解twisted,我们的主要任务是学习,并熟悉它。如下:
1、官网的介绍,这个比较简单,但是对于有经验的开发者来说,已经足够了解了(官网上面其它samples,也有不少是用protocol的) ht空格t空格ps://twistedmatrix.com/documents/current/core/howto/servers.html
2、专门介绍twisted的书籍。有中文版的,内容我记得好像是比较浅显易懂。 h空格ttp://www.amazon.com/exec/obidos/ASIN/1449326110/jpcalsjou-20 第二章专门介绍protocol我就不多说了。
到这里我们假设大家已经熟悉twisted.protocol,那么我们看看firefly的实现部分。 大家知道twisted是事件驱动的,所以整个框架看起来很简单,我们即使对整体不熟悉,只要关注我们关心的事件处理函数部分也是ok的。
首先, reactor.listenTCP(netport, self.netfactory) 这里建立一个服务,等待客户端connect。 每次接收到一个client连接,Factory都会调用 Factory.protocol 也就是 LiberateProtocol来处理,类似fork的概念。Factory充当了一个LiberatePtotocol的管理者的角色,干活的还是LibrateProtocol。所以我们主要关心LiberatePtotocol,代码如下: class LiberateProtocol(protocol.Protocol): """协议"""
buff = "" def connectionMade(self): """连接建立处理 """ log.msg('Client %d login in.[%s,%d]' % (self.transport.sessionno, self.transport.client[0], self.transport.client[1])) self.factory.connmanager.addConnection(self) self.factory.doConnectionMade(self) self.datahandler = self.dataHandleCoroutine() self.datahandler.next() def connectionLost(self, reason): """连接断开处理 """ log.msg('Client %d login out.'%(self.transport.sessionno)) self.factory.doConnectionLost(self) self.factory.connmanager.dropConnectionByID(self.transport.sessionno) def safeToWriteData(self,data,command): """线程安全的向客户端发送数据 @param data: str 要向客户端写的数据 """ if not self.transport.connected or data is None: return senddata = self.factory.produceResult(data,command) reactor.callFromThread(self.transport.write,senddata) def dataHandleCoroutine(self): """ """ length = self.factory.dataprotocl.getHeadLenght()#获取协议头的长度 while True: data = yield self.buff += data while self.buff.__len__() >= length: unpackdata = self.factory.dataprotocl.unpack(self.buff[:length]) if not unpackdata.get('result'): log.msg('illegal data package --') self.transport.loseConnection() break command = unpackdata.get('command') rlength = unpackdata.get('lenght') request = self.buff[length:length+rlength] if request.__len__() < rlength: log.msg('some data lose') break self.buff = self.buff[length+rlength:] d = self.factory.doDataReceived(self, command, request) if not d: continue d.addCallback(self.safeToWriteData, command) d.addErrback(DefferedErrorHandle) def dataReceived(self, data): """数据到达处理 @param data: str 客户端传送过来的数据 """ self.datahandler.send(data)
可以看到主要处理了3个事件。 connectionMade #客户端连接我们 connectionLost(self, reason) #连接丢失 dataReceived(self, data) #数据到达 当客户端连接的时候,我们会调用Factory的connectmanager(也是简单的一个管理类)来保存当前连接信息。 整个系统只有一个Factory类的实例,但是每当一个连接来的时候,都会fork一个Protocol的实例。所以信息都会保存在Factory的属性里面,比如Factory.connectionManager。 同理,当连接丢失的时候,我们调用connectionManager处理一下即可。没有什么复杂的。
我们主要关注的是数据到来的处理逻辑: 1、数据到达,我们简单的发送给处理函数: self.datahandler.send(data) 2、这个处理函数包含了yield 语句所以它是一个生成器,generate。具体大家可以google python yield,会有很详细的介绍文档。 这里只是告诉大家 2.1、datahandler函数,每次调用到yield的地方就会暂停,等待下次被next,或者send唤醒,然后从yield的地方继续执行。 2.2、data = yield,这条语句在被唤醒后 data就被赋值为 send传递进来的参数。 2.3 那么这个函数改写成这样,大家就很容易理解了: def dataHandleCoroutine(self,sendData): ########改动地方 """ """ length = self.factory.dataprotocl.getHeadLenght()#获取协议头的长度 data = sendData ########改动地方 self.buff += data while self.buff.len() >= length: unpackdata = self.factory.dataprotocl.unpack(self.buff[:length]) if not unpackdata.get('result'): log.msg('illegal data package --') self.transport.loseConnection() break command = unpackdata.get('command') rlength = unpackdata.get('lenght') request = self.buff[length:length+rlength] if request.len() < rlength: log.msg('some data lose') break self.buff = self.buff[length+rlength:] d = self.factory.doDataReceived(self, command, request) if not d: return ########改动地方 d.addCallback(self.safeToWriteData, command) d.addErrback(DefferedErrorHandle) 3、剩下了就简单了,利用python struct库来解包数据,
ud = struct.unpack('!sssss3I',dpack) 然后调用自己的services来处理解包后的command和参数。至于如何处理命令,就要看这个services挂什么样的处理函数了。
启动流程介绍完毕,下面就是app里面,暗黑游戏部分的具体处理内容了。
调用流程和前面一样,我们直接到关键函数: def loadModule(): netapp.initNetApp() import gatenodeapp 这里有些代码冗余和重复,但是没有什么太大影响,我们不提了。 这个函数的第一行代码是我改动过的(原先是import+修饰符方式),大家跟进去看,其实就是初始化一个services的子类:NetCommandService,然后给它挂上Forwarding_0 这个处理函数。
而我们仔细看NetCommandServices发现它override了原先的callTargetSingle,主要是这句改动: target = self.getTarget(0) 说明不论啥命令过来,它都写死了调用Forwarding_0来处理。 而Forwarding_0这个函数就一句话,功能是转发前面解析的command和参数给gate模块。
OK到这里结合前面的gate模块,我们应该就可以理出一条client登录的主线。 0、暗黑客户端一启动,就会连接net模块,net模块建立一个对应的connection,并由connectionManager保存。 1、暗黑客户端发送用户名、密码和登陆命令号101: (会封包,这里忽略) Json::FastWriter writer; Json::Value person; person["username"]=userName; person["password"]=password; std::string json_file=writer.write(person); CCLog("%s",json_file.c_str()); SocketManager::getInstance()->sendMessage(json_file.c_str(), 101);
2、net模块接收并解析出命令号 101,参数{name:xxx,pwd:xxx}。然后调用自己的services(实际是NetCommandServices)处理。 3、NetCommandServices,不管37 21,直接写死调用Forwarding_0函数。 4、Fowarding_0函数写死了,用命令fowarding转发给gate模块。 4、gate模块收到它leafNode的调用请求,所以调用自己作为root的services。而这个services在gate/app/gate/rootservice/rservices.py里面注册了forwarding函数,所以就调用它。 5、md,fowarding函数发现这个命令号注册在loacalservices里面,见 gate/app/gate/localservice/lservices.py中: def init(): initLocalService()
addToLocalService(loginToServer_101) ############## 这里 addToLocalService(activeNewPlayer_102) addToLocalService(roleLogin_103)
于是调用loginToServer_101来处理这个命令 6、这个命令就不展开了,很简单,就是取数据,比对是否匹配,check是否有效。然后逐层返回,最后送给客户端
对于登录部分,流程压根没有跑到db、game1和admin模块,所以我们可以直接利用master,gate,net这3个模块做测试 顺序启动 master gate net 然后启动tool/clienttest.py 可以看到net模块的终端会有信息打印。
我自己也写了一个测试程序。已经放在github上面,tool/ clientTestLogin.py; 用的是twisted,很简单。如下:
1 from twisted.internet.protocol import Protocol, ClientFactory
2 3 class Echo(Protocol): 4 def connectionMade(self): 5 a = 'N%&0\t\x00\x00\x00\x00\x00\x00\x00.\x00\x00\x00e{"password":"chenee","username":"chenee"}\n' 6 self.transport.write(a) 7 8 def dataReceived(self, data): 9 print data 10 #self.transport.loseConnection() 11 12 13 class EchoClientFactory(ClientFactory): 14 def startedConnecting(self, connector): 15 print 'Started to connect.' 16 17 def buildProtocol(self, addr): 18 print 'Connected.' 19 return Echo() 20 21 def clientConnectionLost(self, connector, reason): 22 print 'Lost connection. Reason:', reason 23 24 def clientConnectionFailed(self, connector, reason): 25 print 'Connection failed. Reason:', reason 26 27 from twisted.internet import reactor 28 reactor.connectTCP("localhost", 11009, EchoClientFactory()) 29 reactor.run()
很简单,就30行代码,而且都是框架 。就是在连接上server后,事件connectionMade里面往server写一条命令。这条命令是我直接copy暗黑客户端的打印,就是一条封装好的数据包。 5 a = 'N%&0\t\x00\x00\x00\x00\x00\x00\x00.\x00\x00\x00e{"password":"chenee","username":"chenee"}\n'
运行后结果如下:
bash-3.2$ python clientTestLogin.py Started to connect. Connected. N%&0 We{"data": {"userId": 1915, "characterId": 1000001, "hasRole": true}, "result": true} 可以看出,server成功处理,并且返回我们登录后的角色信息。
OK,这章介绍结束,下面介绍game1模块。
游戏服务器学习笔记 9 ———— game1模块
game1 囊括了几乎所有游戏逻辑,内容很多。但是多也只是app内容多, 前面的firefly框架启动流程没有什么差别。 如果看官是一直看下来的,扫一眼代码就一目了然,这里不提。直接跳到app部分。
def loadModule(): """ """ load_config_data() #加载数据 registe_madmin() #注册几个表到memcache from gatenodeapp import * #实际上是注册各种services的命令
逻辑上game/app启动逻辑分3个部分。 Load_config_data();里面东西虽然多,但是并不复杂,如下: def getExperience_Config(): """获取经验配置表信息""" global tb_Experience_config sql = "select * from tb_experience" result = dbpool.fetchAll(sql) ########我改过了 for _item in result: tb_Experience_config[_item['level']] = _item 只是取一些游戏中常用的数据表的内容,然后直接保存在game1的内存数据中,不是memcache,因为这是常驻内存,而不是缓存起来的。
其中 result = dbpool.fetchAll(sql) 是我改动了一下,只是把原先copy paste的代码风格整理一下。10几个地方全部是同样复制的代码看起来非常不舒服。 不过这里不但是体力活,而且是细致活,分3中fetch,具体看我改动后的github代码,没有啥技术含量就不提了。
然后是用MAdminManager,来注册管理几个表,这个和前面db章节提到的一模一样。需要注意的是,下面的代码: def registe_madmin(): """注册数据库与memcached对应 """ MAdminManager().registe(memmode.tb_character_admin) MAdminManager().registe(memmode.tb_zhanyi_record_admin) MAdminManager().registe(memmode.tbitemadmin) MAdminManager().registe(memmode.tb_matrix_amin) 看上去做的只是注册到MAdminManager,并没有初始化。但是其实在文件开头有一个 import memmode 而memmode.py里面是直接裸写的全局变量。所以实际上在这个python文件开头就已经初始化了。 建议把import memmode放到registe_madmin()函数开头部分。这样逻辑上清晰一些。 或者干脆吧memmode里面的内容全部封装到一个init函数里面更好。
大家明白就好,我懒得改了。
下面看第三行 from gatenodeapp import * 这个就是利用import+修饰符方式,添加一批services的命令处理函数。跟进去看:
remoteservice = CommandService("gateremote") GlobalObject().remote["gate"].setServiceChannel(remoteservice)
def remoteserviceHandle(target): """ """ remoteservice.mapTarget(target) 可以看出,实际上是给连接gate模块的leafNode的services添加的。这样gate转发过来的命令,都会被这些函数解析,处理。然后把结果返回给gate,再返回给net,最终到client端。
前面net章节已经分析了用户登录时,net到gate的数据流。这里唯一不同的是gate的消息处理不再是由localservices处理,而是有gate的root转发出去。(下面是gate模块中对应的代码) node = VCharacterManager().getNodeByClientId(dynamicId) return GlobalObject().root.callChild(node, key, dynamicId, data)
我们可以看出,firefly中是由gate模块来维护一个虚拟角色管理类,并由这个管理类来管理一批登陆的虚拟角色。 这些VirtualCharacter中会记录用户的虚拟角色到底是在那个game模块上;虽然我们这里只有game1一个游戏内容处理模块(游戏逻辑服务器),但是可以看出firefly的逻辑是支持多个game模块的。 同时也确定了,这些分发管理是由gate模块(或者叫gate服务器)来负责的。 这里的命令虽然很多,但是相互之间比较独立,同事也和firefly的系统架构关联性不大,独立理解起来很方便。我们就不在一一介绍了。
这个部分其实是和系统策划,游戏逻辑密切相关的,所以,如果有空,我们在后面反推暗黑的游戏逻辑的时候,结合cocos2dx客户端再详细说明上面提到的所有的services处理函数的各个功能,以此来展现一个完整的暗黑游戏框架。而不是这里讨论的firefly服务器框架。
OK,OK 这个系列基本告一段落,大概花费我3天时间码字,如果有什么疏忽的地方,冒犯的地方,或者错误的地方,还请各位看官,9miao的朋友多担待。 也请大家积极反馈,批评指点,帮助我改正错误。
最后再次感谢9miao的开源精神,毫无疑问,firefly和暗黑的代码给我提供了极大的帮助!
三 : 天海祐希:天海祐希-个人详细介绍,天海祐希-宝冢歌剧团时代
天海祐希,著名日本女演员,从《女王的教室》,《女王直播室》等作品中显露出她成熟逼真的演技,其演技被称之为日本第一。
天海祐希 (1967年8月8日-),原名中野 祐里,日本女演员,
籍贯日本富山县,东京市台东区上野出身,前宝冢歌剧团成员。2005年,其主演日剧《女王的教室》播出后人气急升,同年连夺数个演艺奖项,身价晋升至6000万日圆。从《女王的教室》,《女王直播室》,《Around40》显露出她成熟逼真的演技,其演技被称之为日本第一,而她良好印象经常于日本女性好感度调查名列前茅。因她《女王的教室》一剧令她在香港,台湾,中国大陆等亚洲地区拥有庞大粉丝。
(www.61k.com)天海佑希宝冢_天海祐希 -个人详细介绍
原名 中野祐里
罗马拼音 Amami Yūki / Nakano Yuri
昵称 ゆりちゃん
国籍 日本
日本人
籍贯 日本日本富山县
出生 1967年8月8日 (1967-08-08) (41岁)
日本日本东京都台东区上野
职业 女演员
语言 日语
三围 B83?W58?H88
教育程度 宝冢音乐学校第一名入学,并于1987年毕业
音乐类型 舞台剧、电视剧、电影
出道日期 1987年进入宝冢歌剧团
出道作品 《撒马尔罕的的红玫瑰》
(暂,サマルカンドの赤いばら)
代表作品 《花扇抄/门的一边/百万个梦》
(暂,花扇抄/扉のこちら/ミリオン?ドリームズ)
活跃年代 1987年至今
经纪公司 研音
网站http://www.ken-on.co.jp
相关团体 宝冢歌剧团
奖项 日本电影金像奖新人奖
1997年《圣诞启示录》
天海佑希宝冢_天海祐希 -宝冢歌剧团时代
1985年入读宝冢音乐学校,1987年毕业,同年3月サマルカンドの赤いばら初舞台,同年分配至月组,同期生有姿月あさと?絵麻绪ゆう?匠ひびき
1987年11月,月组公演Me And My Girl,本剧的新人公演担任主角比尔,成为宝冢歌剧团有史以来最早新人公演主演的团员
1990年12月未,月组主演男役剑幸退团,由凉风真世接任,天海祐希同时升格为二番手男役,相比4期之上的久世星佳的升格为快
1991年3月,升格后的第一套大剧场作品- ‘ベルサイユのばら’-オスカル编(凡尔赛玫瑰-奥斯卡编)饰演安德烈
1993年7月,月组主演男役凉风真世退团,天海祐希接任,由1987年初舞台至升任至主演男役只用了6年半的时间,成为宝冢歌剧团有史以来最早成为主演男役的团员
1993年9月,接任主演男役后的第一套大剧场作品(披露目)- ‘花扇抄/扉のこちら/ミリオン?ドリームズ’(花扇抄/门的一边/百万个梦)
1995年8月,月组公演Me And My Girl,天海祐希再度主演比尔,同时这套为天海祐希的退团作
1995年12月,东京宝冢剧场公演Me And My Girl退团
天海佑希宝冢_天海祐希 -演出作品
舞台剧(宝冢歌剧团时期)
1987年 Me And My Girl (新人公演主演)
1988年 南の哀愁(南的哀愁)(新人公演主演)
1988年 恋と雾笛と银时计(恋与雾笛与银表)(新人公演主演)
1989年新源氏物语(新源氏物语)(新人公演主演)
1990年 大いなる遗产(徫大的遗产)(新人公演主演)
1993年 花扇抄/扉のこちら/ミリオン?ドリームズ(花扇抄/门的一边/百万个梦)
1994年 风と共に去りぬ(乱世佳人)
1994年 エールの残照/TAKARAZUKA?オーレ!((爱尔兰的夕阳/TAKARAZUKA OLE!)
1995年 ハードボイルド?エッグ/EXOTICA!(破煮蛋/EXOTICA!)
1995年 Me And My Girl
电视剧
1996年 家族注意报 饰 川田步
1998年 大女生日记
1999年 恋爱诈欺师第一话 饰 筱原裕美
2001年 Fighting Girl饰 三井祥子
2001年水曜日的爱情饰佐藤爱
2002年 利家与松 饰 阿春(佐佐成政之妻)
2002年 蔷薇的十字架饰 高畑晓
2003年 溥杰与王妃(流转的王妃?最后的皇弟) 饰 李香兰
2004年 离婚女律师 饰 间宫贵子
2004年 最后的礼物 饰 平木明日香
2005年离婚女律师2饰 间宫贵子
2005年 女王的教室 饰阿久津真矢
2005年 女人一代记 饰 越路吹雪
2006年 女王直播室 饰 椿木春香
2007年 演歌女王 饰 大河内向日葵
2008年 Around40 饰 绪方聪子
电影
1996年 圣诞启示录饰 杉村叶子
1998年迷离花劫(又名:迷雾)
1999年 黑天使Vol.2饰 魔世
2001年 源氏物语饰 光源氏
2001年 犬神饰 坊之宫美希
2001年 连弹饰 佐佐木美奈子
2004年 在世界中心呼唤爱
2005年狗狗心事饰 美春
2007年 野球少年饰 原田真纪子
2007年 南方大作战饰上原樱
配音
2008年 崖上的波妞Gran Manmare(波妞的妈妈)
天海佑希宝冢_天海祐希 -奖项
2001年 第44回 蓝带赏 主演女优赏 (源氏物语)天海佑希宝冢_天海祐希 -外部链结
本文标题:小时代3剧情详细介绍-A4纸大小及A4纸详细介绍61阅读| 精彩专题| 最新文章| 热门文章| 苏ICP备13036349号-1