第一讲:让它跑起来
注:每一讲我都会上传一个相符的lib,有些文件是旧的,有些是新的,我尽可能在lib里写清楚注释。更详细的内容则在每讲的正文里写。
一个最简单的能跑的lib应该长成什么样子?每个基于mudos写lpc的人可能都会给出不同的答案。我记得曾经有个朋友释出过一个不到5k的lib。
我这个则还要小一点,tgz之后是1851个字节。嗯。。。还好。
我们对这个lib基本上不会有什么期待,但是他至少应该完成如下两个事情:
1.能跑起来,并且接受用户的连接(你用zmud也好,telnet也好,总之是可以连到端口上)
2.连接后的用户可以输入,并且lib应该给予一定的反应(那么最简单的做法就是完成一个所谓的echo server了---你输入什么,server就给你返回什么)。
【配合本讲的lib版本为0.1,文件名则是newlib.0.1.tar.gz,见附件】
以下是目录结构:
.
|-- adm
| `-- obj
| |-- master.c
| `-- simul_efun.c
|-- include
| `-- globals.h
|-- log
`-- obj
`-- user.c
首先说目录结构,一个好的清晰的目录结构用起来很舒服,我自己一般推崇单层目录结构,但是考虑到习惯问题,我在/adm下采用了和xkx类似的方法,即把master和simul放到了/adm/obj/下。
大致讲一下:
目前我们只创建了四个基本目录:/adm /include /log /obj。
adm放的都是“独一无二”的东西,比如最关键的两个物件master.c和simul_efun.c,还有以后会慢慢出现的各种daemons。
include是所有头文件所在的目录,目前只有一个globals.h。
log是用来存放各种log输出的,这没啥可讲的。
obj是所有lib当中最终被用到的物件的实体文件,也就是会被最终new()或者load_object()出来的东西。暂时只有一个连线物件,也就是user.c。(你看,现在我们连login.c都省掉了,因为我们的当前目标仅仅是能让mud接受连接而已,断线重连、帐号认证等工作要放到以后完成)
这里我们先讲一点基础的东西:mudos如何启动?
我们不讲mudos自己,只说和lib有关的部分。
有人认为,mudos最先载入的是master.c(请注意,这个master.c不是那个可以用来拜师的master),其实这个看法是有点问题的。
一般来说,mudos最先载入的文件是simul_efun.c。
我们知道,要想跑起一个mud,除了lib以外,还需要driver和一个给driver用的配置文件config.cfg。在config.cfg里,有三个文件是要被定义的:simul_efun.c,master.c和globals.h。如果愿意的话,我们当然可以给这三个文件起个其他的名字,比如master.c改名叫core.c,globals.h可以叫做dangzhongyang.h之类的,随意。。。不过大家都习惯了,我就不改了。
重点1.关于simul_efun.c
刚才我们说到,mudos启动之后,会先尝试载入这个simul_efun.c。
这里有一个概念就是所谓的simul。众所周知,mudos为了游戏的目的,提供了大量的函数(efun)来帮助我们完成工作,比如说像判断是否为玩家的userp(),像是对字符串做操作的一系列函数等等。
然而mudos也不是万能的,他不可能为预先想到所有可能的需求,有时候我们必须自己动手封装一些功能(比如说,log_file()这么方便而好用的功能,mudos就没有提供),如果应用范围很窄,我们当然可以直接把他写到代码里,但是像log_file这么大众化而且常用的功能,我们不可能每次用到就c/p到对应的文件里去,一个是不方便,一个也容易出错不是?
于是,mudos提供了一个方法来解决这个问题,这就是simul efun,简单说就是“模拟的efun”,这个功能可以帮助我们封装合心意的函数,并且在lib的任何地方像使用efun一样的使用他(虽然说慢点吧。。。。当然从效率上说,最快的办法是把efun移植到mudos里,真正做成一个efun,然而这是另外一个话题,不是我们讨论的范畴)。
完成这个工作所需要干的事儿不多,也很简单,就是把函数定义和体写到config.cfg指定的simul_efun.c当中就好了。(具体请参考我的lib当中simul_efun.c里的log_file()函数),这样我们就能让他像个efun一样的工作了。
【题外话】很多mudlib经过了很长时间的发展之后,积累了大量的simul,统统塞到simul_efun.c当中也很困扰,不美观也不便于管理,因此他们会采用另外一个做法:继承。
简单说就是把simul_efun.c当作一个入口文件,把所有的simul函数分门别类的写到其他文件当中去,在主文件里只用继承(inherit)的方法来把这些“其他文件”包含进来。(用include貌似也可吧。。。我不是很确定)
============分割线=====题外话2纯熟废话,有兴趣的同学可以自己参考OOP===============
【题外话2】关于lpc的OOP特性。
lpc这门语言出现的时间较早,虽然一般来说我们认为他是面向对象的,但是也并不完全符合面向对象的所有观念。
比方说,他只支持对对象的函数引用,而不支持对对象的元素引用,当我们想获取或者改变一个对象中的变量时,我们只能通过函数来实现;
再比方说,对于封装来讲,LPC并不能完好的实现多态,并且只有限的支持变长参数表(varargs,lpc只支持一个变长参数)。
============分割线=====题外话2纯熟废话,有兴趣的同学可以自己参考OOP===============
重点2.master.c当中的connect()函数
我们本讲开篇就说了,当下最重要的是让mudos能够接受连接。所以这里我们有必要讲讲mudos接受用户连接的机制。
我们知道,LPC最有趣的地方就在于他是OOP的(不完全无所谓,反正了解到lib里所有的东西都是对象就ok了),那么给每个用户连线分配一个object是很正常也是很方便的管理方法。
问题来了,如何让mudos知道“这是一个连线物件”呢?
这里就用到了一个很重要的master_apply(这玩意儿我们下边讲到),master_apply::connect()
他的原型是:
object connect()
这个函数的作用就在于,当mud接受到一个连接之后(不管你是用zmud还是telnet,总之你连上了mudos提供的端口服务),mudos会调用master的connect()函数,并且期望返回一个对象。这个对象就相当于在mudos里挂了号了。mudos会把它当作一个用户连线对象来对待,比如说会认为他是userp()和interactive()之类的(这几个函数有一些细微而且诡异的差别,有空我们再说)。
请看我的lib里的connect():
object connect()
{
log_file("new_user_login",time()+"\n");
return new("/obj/user.c");
}
很简单,master.c只要被动的等待mudos的呼叫,并且在合适的时候造一个user_ob就ok了。
这里多讲一句:
一个完整的登录过程,在mudos当中包括两步:
a.调用master apply的connect()获取连线物件;
b.调用这个连线物件身上的另外一个apply函数:logon()
其中第二步是为了给登录验证过程一个合适的调用接口用的。目前我们0.1版的lib还用不到这么牛b的技术,所以他就悬空了。
未来适当的时候我们再补充上。
=======================分割线===============================================
重点3.master的apply
实际上master.c当中除了构造函数create()之外,基本都是apply。
什么是apply呢?
大概说一下,所谓apply,就是那种由mudos隐形调用的函数接口,这些接口是为mudos提供服务的,我们通常在lib当中,只是被动的通过这个接口告诉mudos在某些情况下“可以”或者“不可以”,或者“应该是谁”这样子。
在普通对象身上的这种接口我们就把他叫做apply,在master身上的就是所谓的master apply了。
通常大家见到的比较多的apply包括像 id()(被present()隐含调用),像reset()(被reset机制调用)。
master里更多是跟权限有关的master apply,比如valid_xxx一族~~~
ok,这里我们大概知道有这么个事情就ok了,具体以后碰到需要用的apply,我们再像这次的connect()一样讲解。
=======================分割线===============================================
重点4.globals.h
大家会不会遇到这样的问题,我定义了一个宏,却忘了他在哪个头文件里?或者是写一个.c的时候经常要包含无数的头文件,其实就为了他当中一两个宏而已?
感谢mudos的作者,他通过globals.h帮我们在一定程度上解决了这个问题:
当我们有些宏定义是非常全句化的(比如说对目录的定义,对一些重要ob的定义等等),我们可以把它们丢到globals.h里。
并且,更方便的在这里:我们不需要显性的在程序里include这个globals.h,mudos自动的帮我们在每个.c当中都包含了它。
所以,当你有一些宏很全局的时候,尽管塞给globals.h吧。。。
另外一点,请看现在这个非常简单的globals.h
#ifndef __GLOBALS_H__
#define __GLOBALS_H__
#define LOG_DIR "/log/"
#endif
发现有什么不一样没有?
我们使用了#ifdnef #define #endif的方式。
这样做的好处是,避免出现由于.h嵌套而导致的redefine(比如说a.c包含了a.h和b.h,不幸的是a.c同时继承了b.c,而b.c自己也包含了b.h,这样就出现了实际上的嵌套)
=======================分割线===============================================
重点5.user.c
我们的user.c现在功能很简单。在正常的lib当中,为了方便登录认证和断线重连,这个文件被分成了两个。即login.c和user.c(也许有人直接是用body.c?我不太确定,总之是这么个意思)。一个负责密码认证,一个负责真正的用户行为和数据。并且通过exec()函数把mudos认为的用户标签在两个之间切换。
现在我们这里非常简单,不认证,所以直接就只有一个user.c,未来再细化这里。
接下来,mudos如何获取用户的指令呢?
在正常的mudlib当中,我们通常通过一系列复杂的行为来实现之,例如通过一个“全时的”的add_action(command_hook)钩子来获取用户输入,并且通过commandd.c之类名字的一个daemon来找到恰当的指令文件,并执行之。
未来我们也会完善这样的一套结构,不过现在么。。。既然我们只想完成一个echo server。我们暂时用一个更简单的方式来处理他:string process_input(string arg)
这又是一个apply,在“正常的”mudlib里,我们通常用这个apply来实现global alias的解析。
这里我们就直接通过
string process_input(string arg)
{
write(arg+"\n");
}
这样的模式来完成“echo server”的工作了。
这边有个问题,大家有没有发现一个string型的函数我没有return,这直接导致跑起来之后,我们每个指令敲进去都会返回一个>what?
不要紧,反正这个版本我们只是大概的演示,下一次我们修好他。
好了,这次一共就四个文件,我们构建了一个可以跑起来并且有反应的lib了。大家可以跑一下看看(附件里我提供了一个bin文件,包含了driver和config.cfg,是linux版的,由于我在我的服务器上跑的driver非常多,所以我给driver改名叫mud以避免不幸被killall掉。)
如果您想在linux下跑,只要chmod +x mud,并且重新配置一下config.cfg里的mudlib和bin的目录就ok了。
如果您想在win下测试,那么很抱歉,我手头没有合适的mudos.exe版本(哪位好心的老大提供一个)