從頭開發MUDLIB


跟Akuma一起從頭打造mudlib--【第一講】

第一講:讓它跑起來
注:每一講我都會上傳一個相符的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版本(哪位好心的老大提供一個)

=================分了又割===================================================
小結和預告:
第一次寫這種東西(聲明一下,我沒寫過語言類的書,所以不太會寫,可能結構太亂,內容也很分散,抱歉),大家有啥建議和意見歡迎跟帖,我盡量改進。
附件是兩個文件:
bin.tar.gz 是linux版的mudos和config
newlib.0.1.tar.gz是和本講座同步的lib。可能有個問題,就是里邊涉及到的中文是utf8格式的,大家用起來可能是亂碼,這個倒是可以用uedit轉一下碼(主要是我習慣了在shell下直接敲代碼,也就不改編碼了,好在作為一個講解用的lib,不會有太多中文內容在里邊)。
下一講,我想我會把登錄驗證和指令系統補全。這樣我們就可以有空間做一些實驗了(用指令來寫測試代碼很方便的說)。
另外,如果可能,也許會補一個不那么完善的權限系統,也許不會,我看進度把。。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM