KBS - Rediscover the BBS 论坛已读标识的一个实现

| No Comments | No TrackBacks

http://dev.kcn.cn/

0. 序
  0.0 声明
  0.1 本文说明
1. 版面及文章
  1.1 文章的存储
  1.2 版面文章索引,fileheader 结构
  1.3 id, groupid, reid 三个字段
  1.4 附件结构
  1.5 .BOARDS 文件,boardheader 结构
2. 用户
  2.1 什么样的 ID 在 kbs 系统中是合法的
  2.2 用户相关的文件系统结构
  2.3 userec 结构
  2.4 userdata 结构
  2.5 密码
  2.6 用户 home 目录下面其它一些文件的说明
    2.6.1 .boardrc.gz 已读记录
    2.6.2 favboard 收藏版面
    2.6.3 friends 好友列表
    2.6.4 plans 个人说明档
    2.6.5 signatures 签名档
3. 用户站内信件
  3.1 信件目录及数据结构
  3.2 自定义信箱
  3.3 信箱属性
4. 共享内存结构
  4.1 publicshm 共享内存结构 public_data
  4.2 uidshm 用户信息共享内存结构 UCACHE
    4.2.1 UCACHE 内 hash 表结构
  4.3 utmpshm 登录状态信息 UTMPFILE
  4.4 utmphead 登录表共享内存结构 UTMPHEAD
  4.5 wwwguest_shm:wwwguest 在线用户表结构 WWW_GUEST_TABLE
  4.6 bcache,brdshm:版面状态共享内存结构 BCACHE
5. 代码结构


$Id: kbsIntro.txt 10425 2008-10-24 15:45:35Z fancy $

发信人: atppp
标  题: [kbsIntro] kbs 系统入门
发信站: http://dev.kcn.cn/

0. 序  [ 回目录 ]

0.0 声明  [ 回目录 ]

尽管 kbs 系统基础数据结构一般很少变化,但是本文档只针对 kbs SVN 主分支目前的代码,不保证对其它时间或其它分支代码的正确性,也不对可能造成的任何后果负责。本文可以在网络上自由转载,但请保留全文的完整性。

0.1 本文说明  [ 回目录 ]

文中有一些对数据结构的说明。大多数结构定义在 src/struct.h 中,char[] 的字段若非特别说明都是以 '\0' 结束的字符串。对本文或相关问题的疑问,欢迎到水木社区 (newsmth.net) BBSMan_Dev 版讨论。

 

1. 版面及文章  [ 回目录 ]

1.1 文章的存储  [ 回目录 ]

每个版面都是一个目录。比如 SYSOP 版的目录是 $BBSHOME/boards/SYSOP/。在这个目录下面有这个版面的文章及索引,每一篇文章都是一个文件,文件名大致是这样子:

M.1085385291.w0
             ^^ 用于区分同一个时间点的多个帖子
  ^^^^^^^^^^ 10 位数的 timestamp,帖子发表时间
^ M 是文件名前缀,不同性质的文件,前缀就不一样

下面是各版面索引文件和相应文件名的前缀:(通常情况下)
版面文章   .DIR        M
置顶文章   .DINGDIR    Z (硬连接到相应 M 前缀文件)
文摘区     .DIGEST     G (硬连接到相应 M 前缀文件)
回收站     .DELETED    D
废纸篓     .JUNK       J

同主题模式依赖于版面目录下面的一个 .ORIGIN 文件。这个文件就是 .DIR 文件里面所有原作(id==groupid,参考 1.3 节)的 fileheaders。

另外,在 $BBSHOME/vote/ 下面每个版面也会有一个目录,主要用于储存投票等数据,按照 KCN 的精神,今后最好不要再在 vote 目录下储存版面信息。


1.2 版面文章索引,fileheader 结构  [ 回目录 ]

每个索引文件就是多个 fileheader 结构。这个 fileheader 定义在 src/default.h 或者 src/site.h 里面,具体解释如下:

typedef struct fileheader {
    char filename[FILENAME_LEN];
        帖子的文件名,比方 1.1 节那个例子,这个字段就是 M.1085385291.w0。
        kbs 也可以采用版面目录下 52 个子目录分散储存帖子的方法(参考
        feeling.h 内 GET_POSTFILENAME 宏定义),这种情况下,本字段的形式
        就是 A/M.1085385291.w0。
    unsigned int id, groupid, reid;
        这三个 id 字段非常重要,下面一节具体说明。
    int o_bid;
    unsigned int o_id;
    unsigned int o_groupid;
    unsigned int o_reid;
        以上四个字段用于需审核的文章和推荐文章,o_bid 是原文的版面 bid。
        后面三个是原文在原版面 .DIR 中相应的三个 id 字段值。
    char innflag[2];
        帖子是否转信。"LL" 表示本地发表,"SS" 表示转信,"\0M" 表示外面
        转进来的文章。
    char owner[OWNER_LEN];
        发文作者。
    unsigned int eff_size;
        帖子的有效字节数。
    time_t posttime;
        帖子发表时间的 timestamp。在使用 52 个子目录分散储存帖子的情况下该
        字段尤其重要。
    long attachment;
        附件位置偏移量,参考下面的附件格式。
    char title[ARTICLE_TITLE_LEN];
        帖子的标题。注意超长标题需要截短。
    unsigned short replycount;
        主题帖的回复数,在 .DIR 和 .ORIGIN 中同步更新,对于其他模式和非主题
        贴,该字段无用。
    char unused[114];
        没有用到,为了将来需要升级该结构体时省却转换索引的麻烦,所以留点地方。
    unsigned char accessed[4];
        一些帖子的属性,前两个元素是 bitwise-OR 的属性,包括:
        accessed[0]: FILE_SIGN     (0x01) 版主设置的 # 标记
                     FILE_TOTAL    (0x02) 已被制作合集
                     FILE_PERCENT  (0x04) 版主设置的 % 标记
                     FILE_MARKED   (0x08) 被 m 的帖子
                     FILE_DIGEST   (0x10) 文摘区贴子(被 g 的帖子)
                     FILE_IMPORTED (0x80) 已被收录精华
        accessed[1]: FILE_READ     (0x01) 不可 re
                     FILE_DEL      (0x02) 版主标记删除 X
                     FILE_MAILBACK (0x04) 回帖抄送信箱
                     FILE_COMMEND  (0x08) 已被推荐文章
                     FILE_CENSOR   (0x20) FILTER 版面需审核文章
                     FILE_TEX      (0x80) tex 方式发表帖子
        accessed[3]: 回收站的 .DIR 中这个字段为:
                     删除时间 timestamp / (3600 * 24) % 100
                     主要用于定时删除老的删帖。
} fileheader;


1.3 id, groupid, reid 三个字段  [ 回目录 ]

这三个字段是帖子的索引 ID 和同主题信息。.DIR 里面 fileheader 结构的 id 字段依次递增。注意一定是递增,否则 web 下浏览会不正常!另外两个字段的作用举例如下:

有人新发表了帖子 A,这个帖子系统自动给了 id = 10
然后有人回复帖子 A,我们叫它帖子 B;
再有人回复了帖子 B,我们叫它帖子 C;
最后有人回复帖子 A,称为帖子 D。这四个帖子的三个字段会是这样:

帖子    id      groupid     reid
================================
 A      10      10          10
 B      11      10          10
 C      12      10          11
 D      13      10          10

其中,groupid 就是用来判断帖子同主题的,注意,帖子同主题和帖子标题无关。reid 用来产生回复树结构。


1.4 附件结构  [ 回目录 ]

附件的内容就添加在它所属帖子的那个文件的末尾。有附件的帖子文件其组成是:帖子正文(最后是一个回车符),附件一,附件二,...其中每个附件段是由四个部分组成的:
*) 第一部分:八个 '\0' 字节。
*) 第二部分:附件的原始文件名字符串,可以含有中文字符,长度不应该超过 40 个字
   符。文件名的后缀将决定附件类型。本部分长度不定,所以千万不要忘记最后的 '\0'
   字符串结束符。
*) 第三部分:四个字节,unsigned int 二进制格式整数,表示本附件的长度;
   注意这个整数是网络字节序(big-endian)存储,也即 MSB 在先。
*) 第四部分:二进制格式储存的这个附件,本部分长度由第三部分决定。
另外,这个帖子在 .DIR 里面相应的那个 fileheader 结构的 attachment 字段应该设置为第一个附件段的起始偏移量(ftell)。参考:
libBBS/article.c upload_post_append() 和 get_mimetype() 函数


1.5 .BOARDS 文件,boardheader 结构  [ 回目录 ]

$BBSHOME/.BOARDS 文件是所有版面的信息,实际上是 MAXBOARD 个 boardheader 结构。在 kbs 系统内部每个版面都有一个版面号 bid,这个 bid 就是该版面在 .BOARDS 里面的位置,注意版面号是 1-based 的。

struct boardheader {
    char filename[STRLEN];
            版面的英文名称,STRLEN 是 80
    char BM[BM_LEN];
            版主列表,BM_LEN 是 60。多个版主用空格隔开
    char title[STRLEN];
            版面的说明,格式是 a[bbbb]ccccccdddd...
                a: 讨论区分区
                bbbb: 讨论区分类
                cccccc: 转信标签,一般设置为六个空格
                dddd....: 讨论区说明,也即通常所说的版面中文名称
            比方:
                0[站务]      测试用版
    unsigned level;
            版面存取权限。
    unsigned int idseq;
            当前已经使用到的 id 值,参考 boardstatus 结构 nowid 字段。
    unsigned int clubnum;
            俱乐部序号。0 表示这个版面不是俱乐部。
    unsigned int flag;
            版面的一些属性比方是否参与转信。
    union {
    unsigned int adv_club;
    unsigned int group_total;
    } board_data;
    time_t createtime;
            版面创建时间,"新开启的讨论区"会用到。
    int unused;
    char ann_path[128];
            精华区路径。实际精华区绝对路径是:
            $BBSHOME/0Announce/groups/<ann_path>
    int group;
            所属目录。
    char title_level;
            设定用户需要什么身份可见这个版面。0 表示没有限制。
    char des[195];
            版面描述,用于 www 的版面说明和版面超级搜索。
#ifdef FLOWBANNER
    int bannercount;
    char banners[MAXBANNER][BANNERSIZE];
#endif
};

 

2. 用户  [ 回目录 ]

2.1 什么样的 ID 在 kbs 系统中是合法的  [ 回目录 ]

kbs 系统合法 ID 的规则是:至少 2 个字符,至多 12 个字符。第一个字符必须是字母,后面的字符必须是字母或者数字。


2.2 用户相关的文件系统结构  [ 回目录 ]

$BBSHOME/.PASSWDS 是用户帐号基本信息,实质上是 MAXUSERS 个 userec 结构,结构说明后面详细写。另外,每个用户还有一个 home 目录和一个信件目录,比方 atppp 用户这两个目录分别是 $BBSHOME/home/A/atppp/ 和 $BBSHOME/mail/A/atppp/。用户 home 目录下有一些杂七杂八的文件,其中有一个文件叫做 .userdata,这个是用户帐号的补充信息,实际上就是一个 userdata 结构,结构说明后面详细写。信件目录到后面一大章节再说。


2.3 userec 结构  [ 回目录 ]

struct userec {
    char userid[IDLEN + 2];
        用户名。IDLEN 是 12,不要轻易修改。
    char flags;
        一些标志,戒网,版面排序之类的。转换用户建议设置成 0x81,也就是
            PAGER_FLAG | CURSOR_FLAG
        参考源代码 contrib/fb2k2smth/README 相关说明。
            #define PAGER_FLAG   0x1        /* true if pager was OFF last session */
            #define CLOAK_FLAG   0x2        /* true if cloak was ON last session */
            #define BRDSORT_FLAG 0x20       /* true if the boards sorted alphabetical */
            #define CURSOR_FLAG  0x80       /* true if the cursor mode open */
            #define GIVEUP_FLAG  0x4        /* true if the user is giving up  by bad 2002.7.6 */
            #define PCORP_FLAG   0x40       /* true if have personalcorp */
    unsigned char title;
        用户身份
    time_t firstlogin;
        注册时间或者第一次登录的时间戳。
    char lasthost[16];
        最后登录的 IP。
    unsigned int numlogins;
        登录次数。
    unsigned int numposts;
        发帖数。
#ifdef CONV_PASS
    char passwd[OLDPASSLEN];
    char unused_padding[2];
#endif
    char username[NAMELEN];
        用户昵称。
    unsigned int club_read_rights[MAXCLUB>>5];
    unsigned int club_write_rights[MAXCLUB>>5];
        这两个是俱乐部读写权限。如果该用户的
            club_read_rights[(clubnum-1)>>5]&(1<<((clubnum-1)&0x1f))
        为真,则这个用户可以读取俱乐部号为 clubnum 的版面。参考函数
            check_read_perm() haspostperm()
        注:这里的数据结构决定了 MAXCLUB 必须定义为 32 的倍数。
    unsigned char md5passwd[MD5PASSLEN];
        md5 消化过的密码。密码问题后面会一并详细说明。
        此字符串定长 16,最后不以 '\0' 结束!
    unsigned userlevel;
        用户权限。
    time_t lastlogin;
        上次登录的时间戳。
    time_t stay;
        总上线时间。单位是秒。
    int signature;
        当前使用的签名档号码。
    unsigned int userdefine[2];
        用户自定义参数。新注册用户是 0xffffffff,但是 wForum 的标准应该是默认
        关闭公布详细信息,userdefine[0] 的实际初始值是
            0xFFFFFFFF & (~DEF_SHOWREALUSERDATA)
        也就是 0xBFFFFFFF。
    time_t notedate;
    int noteline;
        上面两个都和查看留言板相关。
    int unused_atppp;
    time_t exittime;
        上次退出登录的时间戳。
    unsigned int usedspace;
        用户信件使用的磁盘空间。
    int unused[7];
};

2.4 userdata 结构  [ 回目录 ]

userdata 是用户 home 目录下 .userdata 文件的结构。另外用户 home 目录下还有一个文件是 usermemo(用来 mmap 的),这个文件的内容要和 .userdata 严格一致,如果 usermemo 文件不存在系统会自动从 .userdata 建立,所以如果 usermemo 存在且和 .userdata 不一致的话系统就会出错。

userdata 结构:

struct userdata
{
    char userid[IDLEN + 2];
        用户名。
    char __reserved[2];
    char realemail[STRLEN - 16];
        真实 email。
    char realname[NAMELEN];
        真实姓名。
    char address[STRLEN];
        通讯地址。
    char email[STRLEN];
        email。
#ifdef HAVE_BIRTHDAY
    char            gender;
        性别,写 'M' 或者 'F'。如果不是这两个字符可能出错。
    unsigned char   birthyear;
        出生年的后两位。
    unsigned char   birthmonth;
        出生月。
    unsigned char   birthday;
        出生日。上面三个字段注意类型是 unsigned char。
#endif
    char reg_email[STRLEN];
        注册使用的 email。
/*#ifdef SMS_SUPPORT*/
    bool mobileregistered;
    char mobilenumber[MOBILE_NUMBER_LEN];
/*#endif*/
/* add by roy 2003.07.23 for wForum */
    char OICQ[STRLEN];
    char ICQ[STRLEN];
    char MSN[STRLEN];
    char homepage[STRLEN];
    int userface_img;
        设置成 0。如果有自定义头像,设置成 -1。
    char userface_url[STRLEN];
        这个是自定义头像的完整 URL 地址。
    unsigned char userface_width;
    unsigned char userface_height;
        上面两个字段是自定义头像的长和宽。必须是 0~120 之间的整数。
    unsigned int group;
    char country[STRLEN];
    char province[STRLEN];
    char city[STRLEN];
    unsigned char shengxiao;
    unsigned char bloodtype;
    unsigned char religion;
    unsigned char profession;
    unsigned char married;
    unsigned char education;
    char graduateschool[STRLEN];
    unsigned char character;
    char photo_url[STRLEN];
        个人相片的完整 URL 地址。
    char telephone[STRLEN];
    char smsprefix[41];
    char smsend[41];
    unsigned int smsdef;
        上面这堆 "add by roy" 的东西目前只在 wForum 里面用到。
    int signum;
        签名档个数。
    int this_field_is_reserved_by_atppp;
    time_t lastinvite;
};

2.5 密码  [ 回目录 ]

kbs 的用户密码使用 md5 加密储存于 userec 结构的 md5passwd 字段内,但是 kbs 系统并不是对用户密码直接 md5 加密处理,md5passwd 字段是下面这四个字符串顺序连接起来的字符串的 md5:
    passmagic 密码 passmagic 用户名
其中 passmagic 是(不包括前后两个引号):
    "wwj&kcn4SMTHBBS MD5 p9w2d gen2rat8, //grin~~, 2001/5/7"
相关代码请看 libBBS/pass.c igenpass() 函数。注意 md5passwd 字段的类型是 unsigned char md5passwd[16],也就是所谓的 raw-binary format,而不是有些 md5 程序返回的 32 个字符的字符串。注:由于这个 md5passwd 消化了用户名,所以用户名更改大小写之后必须重新给该用户设置密码。


2.6 用户 home 目录下面其它一些文件的说明  [ 回目录 ]

2.6.1 .boardrc.gz 已读记录  [ 回目录 ]

用户 home 目录下面的 .boardrc.gz 存储用户的已读记录,它是一个使用 gzip 压缩的文件,解压后的长度是
    BRC_FILESIZE = MAXBOARD * BRC_MAXNUM * sizeof(unsigned int)。
BRC_MAXNUM 默认是 50,这个文件分为 MAXBOARD 段,第 i 段就是 bid = i 的那个版面的已读记录;每个版面的已读记录就是 BRC_MAXNUM 个非负整数:
    n1 n2 n3 ... np 0 ... 0
其中 n1 > n2 > n3 > ... > np > 0。这组已读记录的意义是,该版面 id > n1 的文章都是未读的,id < np 的文章都是已读的;而 np <= id <= n1 的文章中,只有
    id = n1,n2,n3,...,np
的文章才是已读的,其余全部未读。已读记录用这个方法来存储是有利有弊的,最大的好处就是比较有效的记录了用户最需要的那部分已读记录,

2.6.2 favboard 收藏版面  [ 回目录 ]

用户自定义了收藏版面之后会在用户 home 目录下创建文件 favboard。该文件的格式可以参考 libBBS/boards.c load_myboard1() save_favboard1() 函数。favboard 文件有多种允许的格式,而且 kbs 支持复杂的多目录层次收藏夹结构。下面只说明其中一种格式。favboard 文件可以是这样一个数据结构:

struct {
    int magic_version_number;
        写 0x8081
    int favbrd_list_t;
        收藏目录个数,写 1
    struct favbrd_struct fav_boards;
        具体的收藏版面
};

favbrd_struct 具体的数据结构如下:

struct favbrd_struct {
    int bnum;
        本目录中收藏版面的个数,决定下一个数组字段中多少个元素是有效的
    int bid[MAXBOARDPERDIR];
    /* bid >= 0: 版面
       bid < 0: 目录, 表示子目录是 favbrd_list[-bid]
       */
       在不涉及多层目录结构的情况下,bid[i] 表示本目录下第 i 个收藏版面,
       这里千万注意,bid[i] 是相应版面的 bid - 1,而不是 bid!也就是说,
       这里 bid[i] 是有可能为 0 的。
    char title[61];
    char ename[20];
    int father;
        根目录这个字段写 -1。
    int level;
};

另外,$BBSHOME/etc/initial_favboard 是新注册用户默认的收藏版面,格式是每行一个版名。如果该文件不存在,默认的收藏版面是 .BOARDS 文件里面的第一个版面。

2.6.3 friends 好友列表  [ 回目录 ]

这是 n 个 friends 结构的文件,每个结构都是一个好友:

struct friends {
    char id[13];
        好友 id
    char exp[LEN_FRIEND_EXP];
        好友说明,可以留空。
};

2.6.4 plans 个人说明档  [ 回目录 ]

这个没什么好说的,就是个人说明档。查询用户的时候会显示出来。

2.6.5 signatures 签名档  [ 回目录 ]

这个文件是用户签名档,每六行是一个单位,支持 ansi 控制符,wForum 额外支持少量 ubb。userdata 结构的 signum 字段存储用户签名档个数,如果出现错误可以用recalc_signum 程序来纠正。

 

3. 用户站内信件  [ 回目录 ]

用户信件,包括信件索引和具体信件内容,全部位于用户信件目录下(参考 2.2 节)。

3.1 信件目录及数据结构  [ 回目录 ]

用户信件的总体构架基本类似于讨论区文章。每封信件都是一个文件,文件名的规则和讨论区普通文章的文件名相同。索引文件除了 .DIR 还有两个,如下:

    .DIR        收件箱
    .SENT       发件箱
    .DELETED    垃圾箱

这三个索引文件的结构和讨论区文章索引 .DIR 的结构很类似,也是 n 个 fileheader 结构,少数几个字段的意义略有不同,具体解释如下:

typedef struct fileheader {
    char filename[FILENAME_LEN];
        帖子的文件名,注意第 3 个字节到第 12 个字节是帖子的发表时间戳。
    unsigned int id, groupid, reid;
    int o_bid;
    unsigned int o_id;
    unsigned int o_groupid;
    unsigned int o_reid;
    char innflag[2];
        以上八个字段没用。
    char owner[OWNER_LEN];
        对方 ID。.DIR 中表示发件人 ID,.SENT 中表示收件人 ID,.DELETED 里面
        既有可能是发件人也可能是收件人 :(
    unsigned int eff_size;
        信件大小。
    time_t posttime;
        信件发送时间的 timestamp,好像没用,除非信件也用 52 个子目录存储...
    long attachment;
        附件偏移量。
    char title[ARTICLE_TITLE_LEN];
        帖子的标题。注意超长标题需要截短。
    unsigned char accessed[4];
        一些属性,bitwise-ORs flags:
        accessed[0]: FILE_READ      (0x01) 已读
                     FILE_REPLIED   (0x20) 已回复
                     FILE_MARKED    (0x08) 被 m 的信件
                     FILE_FORWARDED (0x40) 已转发
} fileheader;

信件在索引文件中的排序没有特定的规则(并不按照时间排序)。

3.2 自定义信箱  [ 回目录 ]

kbs 系统除了上面提到的三个预定义信箱外,还支持用户自定义信箱。载入自定义信箱的代码可以参考 libBBS/record.c load_mail_list() 函数。具体来说,在用户 home 目录下有一个 maildir 文件是自定义信箱的记录,文件结构是:
struct {
    int mail_list_t;
        自定义信箱的个数
    char mail_list[MAILBOARDNUM][40];
        每个自定义信箱的具体配置,每个配置是一个 40 个字节的字符串。
        0~29 字节是信箱名称。30~39 字节是该信箱索引文件的后半段名称。
        比如,这个字符串前半段是"KCN 情书",30~39 字节是"MAILBOX1",
        那么这个自定义信箱的显示名称就是"KCN 情书",而索引文件的名称
        就是 .MAILBOX1,注意文件名第一个字符是附加上去的点。
};

3.3 信箱属性  [ 回目录 ]

在用户 home 目录下有一个文件 .mailbox.prop 是用户信箱的选项配置。该文件就是一个 int 变量,bitwise-ORs 以下属性:

#define MBP_SAVESENTMAIL      0x00000001 //发信时保存信件到发件箱
#define MBP_FORCEDELETEMAIL   0x00000002 //删除信件时不保存到垃圾箱
#define MBP_MAILBOXSHORTCUT   0x00000004
        //版面按 'v' 时进入: 收件箱(OFF) / 信箱主界面(ON)

如果用户 home 目录下 .mailbox.prop 文件不存在,系统自动使用 MBP_DEFAULT 作为信箱选项配置。

 


4. 共享内存结构  [ 回目录 ]

共享内存在 kbs 系统中主要用于进程间通信。比方,在 web 注册了之后,马上就能在 telnet 下登录了,这是因为 web 注册的那个代码修改了相应的共享内存数据,当在 telnet 试图登录的时候,代码就能在共享内存中找到这个信息。当然,这些工作完全可以用文件系统来做,但是用共享内存来做进程间通信效率就会高很多。BBS 的很多重要数据都在共享内存里面,比如两个重要的系统文件:

$BBSHOME/.PASSWDS 这个是用户的帐号信息,包括密码。
$BBSHOME/.BOARDS  所有版面的信息。

当系统正常启动之后,这两个文件的信息在共享内存里面,系统会定时写磁盘同步数据。当系统正常运行的时候,直接打开这两个文件修改是不对的!BBS 程序如有基础结构变动的更新,一般必须要停掉 BBS 服务,清除掉共享内存数据。kbs 系统使用的主要共享内存列举如下,其中标识是可以在 sysconf.ini 定义的共享内存 key,如无定义则使用默认。

程序变量          说明         要处理代码文件   标识及默认
======================================================================
bcache        .BOARDS 的 mmap     bcache.c      只是一个 mmap
brdshm        版面状态            bcache.c      BCACHE_SHMKEY   0xe6d
uidshm        用户信息            ucache.c      UCACHE_SHMKEY   0xe70
utmpshm       登录用户状态信息    utmp.c        UTMP_SHMKEY     0xe73
utmphead      登录表              utmp.c        UTMPHEAD_SHMKEY 0xe72
wwwguest_shm  wwwguest 登录信息   bbslib.c      WWWGUEST_SHMKEY 0x1194
publicshm     全局信息            stuff.c       0xe74 (*)

(*) 援引 bbs.h 注释:
#define PUBLIC_SHMKEY   3700
/*这个是唯一一个定义死的SHMKEY,因为sysconf_eval需要
public shm,而attach shm又需要sysconf_eval,ft*/


4.1 publicshm 共享内存结构 public_data  [ 回目录 ]

struct public_data {
    time_t nowtime;
        当前时间。BBS 系统有大量取当前时间的调用,全部从这里取可以提高效率。
        这个时间由 miscd 中的 timed 进程负责和系统时钟同步。从这里可以知道
        如果 timed 进程不正常的话,bbs 的时间就会停止,比方 web 登录就可能
        会提示登录过于频繁。
    int sysconfimg_version;
        当前最新 sysconf 版本号,新登录用户会读入 sysconf.img.版本号
        映像文件作为菜单。站务在主菜单按 shift+~ 会将这个字段加 1,并从
        sysconf.ini 和 menu.ini 生成新的 sysconf.img.版本号 映像文件。
        注意 web 不会自动读取新的 sysconf,如果有涉及 web 的参数修改(比方
        BLOG MYSQL 密码),必须重新启动 web。
    int www_guest_count;
        当前登录的 wwwguest 数目
    unsigned int max_user;
        系统曾经到过的最高登录人数(包括 wwwguest)
    unsigned int max_wwwguest;
        系统到最高登录人数时,wwwguest 登录的数量。
        注意这不是系统曾经到过的最高 wwwguest 登录人数。

    /* etnlegend, 2006.03.06, userscore twice sampling for high score users ... */
    unsigned int us_sample_high[8];

    unsigned int logincount;
    unsigned int logoutcount;
    uint64_t staytime;
    unsigned int wwwlogincount;
    unsigned int wwwlogoutcount;
    unsigned int wwwguestlogincount;
    unsigned int wwwguestlogoutcount;
    uint64_t wwwstaytime;
    uint64_t wwwgueststaytime;

    /* etnlegend, 2006.03.06, userscore sampling ... */
    unsigned int us_sample[32];

    /* etnlegend, 2006.05.28, 阅读十大 ... */
    unsigned int top_version;
    struct top_header{
        int bid;
        unsigned int gid;
    } top[10];

    char unused[712];
   
#ifdef FLOWBANNER
    int bannercount;
    char banners[MAXBANNER][BANNERSIZE];
#endif
   
#ifdef FB2KENDLINE
    time_t nextfreshdatetime;
    char date[60];
#endif
};

4.2 uidshm 用户信息共享内存结构 UCACHE  [ 回目录 ]

uidshm 的结构 UCACHE 定义在 ucache.c 内,相关 hash 表常量在 uhashgen.h。

struct UCACHE {
    ucache_hashtable hashtable;
    ucache_hashtable hashusage;
    int hashhead[UCACHE_HASHSIZE + 1];
    int next[MAXUSERS];
        前面四个字段和用户 hash 表相关,后面一节具体说明
    time_t uptime;
        这个好像没用
    int number;
        有效用户容量,一般应该等于 MAXUSERS
    char user_title[255][USER_TITLE_LEN]; //定义用户的身份字符串。
        用户身份,user_title[0] 是 title 为 1 的用户的身份
        信息和 $BBSHOME/etc/title 文件同步
    struct userec passwd[MAXUSERS];
        这是 .PASSWDS 文件的内容,定时和磁盘文件同步。
        一个特定用户的用户号(uid)就是该用户在 .PASSWDS 文件中的位置,
        注意 uid 是 1-based。
};

4.2.1 UCACHE 内 hash 表结构  [ 回目录 ]

参考 doc/userid 文档。每个用户的用户名都有一个 hash 值(1 ~ UCACHE_HASHSIZE 之间),后面简称用户 hash 值。hashhead 数组是 hash 表头,存放的是该 hash 值的第一个用户的 uid。如果多个用户的 hash 值相同则用 next 字段的数据构成一条链,链中下一个用户的用户号是 next[uid - 1],如果该值为 0 表示已经到链尾。例如,系统中 hash 值都是 h 的用户一共有三个,uid 分别为 uid_1 uid_2 uid_3,那么 hash 表里可能会有这样的结构:
uidshm->hashhead[h] = uid_1
uidshm->next[uid_1 - 1] = uid_2
uidshm->next[uid_2 - 1] = uid_3
uidshm->next[uid_3 - 1] = 0

另外,hashhead[0] 存储第一个空用户的 uid,便于下次分配新用户,所有的空用户 hash 值都是 0,所以也类似的通过 next 字段组成一条链。

hashtable 和 hashusage 字段和 hash 函数相关,系统初始化时从 uhashgen.dat 读入。


4.3 utmpshm 登录状态信息 UTMPFILE  [ 回目录 ]

utmpshm 的类型是 UTMPFILE 结构,用来存储登录的状态信息。注意 wwwguest 和这个结构完全没有关系。

#define USHM_SIZE       (MAXACTIVE + 10)
struct UTMPFILE {
    struct user_info uinfo[USHM_SIZE];
        登录状态信息。每个登录都有一个登录号(utmpnum),他就是该登录在
        uinfo 数组中的位置,注意 utmpnum 是 1-based。
};

uinfo 数组的每一个元素都可以用来存储一个登录的状态信息,其结构 user_info 定义:

struct user_info {              /* Structure used in UTMP file */
    int active;                 /* When allocated this field is true */
        本结构当前是否代表一个登录用户。
    int uid;                    /* Used to find user name in passwd file */
        登录用户的 uid。
    int pid;                    /* kill() to notify user of talk request */
        telnet 登录表示其进程号。www 登录设置为 1。
    int invisible;              /* Used by cloaking function in Xyz menu */
        是否隐身。
    int sockactive;             /* Used to coordinate talk requests */
    int sockaddr;               /* ... */
    int destuid;                /* talk uses this to identify who called */
    int mode;                   /* UL/DL, Talk Mode, Chat Mode, ... */
        状态,应该赋值为 modes.h 里面的常数。
    int pager;                  /* pager toggle, true, or false */
        呼叫器状态,bitwise-OR 以下属性
            ALL_PAGER       0x1
            FRIEND_PAGER    0x2
            ALLMSG_PAGER    0x4
            FRIENDMSG_PAGER 0x8
    int in_chat;                /* for in_chat commands   */
    char chatid[16];            /* chat id, if in chat mode */
    char from[IPLEN + 4];       /* machine name the user called in from */
        登录 IP。
    time_t logintime;
        登录时间戳。
    int lastpost;
        上次发文的时间戳。
    char unused[32];
    time_t freshtime;
        上次活动的时间戳,用来计算发呆时间。
    int utmpkey;
        登录 key,用于 www cookie 验证保持用户身份。
    unsigned int mailbox_prop;  /* properties of getCurrentUser()'s mailbox */
        用户信箱选项,登录时从用户 .mailbox.prop 文件读取,参考 3.3 节
    char userid[20];
        用户名
    char realname[20];
        真实姓名,登录时从用户 userdata 结构读取
    char username[40];
        用户昵称,登录时从 uidshm 共享内存去读取,修改临时昵称就是修改这个字段
    int friendsnum;
        好友数量
    int friends_uid[MAXFRIENDS];
        每个好友的 uid,前 friendsnum 个有效。
#ifdef FRIEND_MULTI_GROUP
    unsigned int friends_p[MAXFRIENDS];
#endif
    int currentboard;
        当前所在版面的 bid 号
    unsigned int mailcheck;     /* if have new mail or new msg, stiger */
        当前登录是否有新信或新消息
};


4.4 utmphead 登录表共享内存结构 UTMPHEAD  [ 回目录 ]

utmphead 的结构是 UTMPHEAD,该结构定义在 var.h 中。注意 wwwguest 和这个结构完全没有关系。

#define UTMP_HASHSIZE  (USHM_SIZE*4)
struct UTMPHEAD {
    int next[USHM_SIZE];
    int hashhead[UTMP_HASHSIZE + 1];
    int number;
        当前登录数,注意这个数字不包括 wwwguest 数量。
    int listhead;
    int list_prev[USHM_SIZE];   /* sorted list prev ptr */
    int list_next[USHM_SIZE];   /* sorted list next ptr */
    time_t uptime;
        一个标记时间,登录 telnet 新用户时如果当前时间和这个标记时间相差
        120 秒以上,就遍历所有登录踢掉发呆时间过长的登录,同时将 uptime
        设置成当前时间。
};

utmphead 里面也有一个 hash 表,结构类似于 uidshm 里面的 hash 表。这里 hash 值是从该登录用户的 hash 值计算(参见 utmp_hash() 函数),我们把它称为 utmphash 值(1 ~ UTMP_HASHSIZE 之间)。hashhead[h] 存储的就是第一个 utmphash 值为 h 的那个登录的 utmpnum。如果多个登录有相同的 utmphash 值则用 next 字段组成一条链(utmphash 链),具体来说,如果 utmpnum 是一个 active 登录,next[utmpnum - 1] 存储该登录在这条 utmphash 链中下一个登录的 utmpnum,链尾的 next[utmpnum - 1] == 0。

所有没有登录(即 utmpshm->uinfo[utmpnum - 1].active = 0)的 utmphash 值都是 0,其中第一个 utmpnum 储存在 hashhead[0] 中,其余的通过 next 字段类似的组成一条链便于分配新登录。系统启动时没有用户登录,所以初始化为:
hashhead[0] = 1
next[0] = 2
next[1] = 3
... ...
next[USHM_SIZE - 1] = 0

utmphead 内除了 hash 表外还有一个头尾相接的环状双向链表,链表是所有 active 的登录按照登录用户名从小到大排列(case-insensitive)。listhead 是用户名最小的那个登录 utmpnum,list_prev[utmpnum - 1] 和 list_next[utmpnum - 1] 分别存储该链表中登录号为 utmpnum 的元素的前一个和后一个登录的 utmpnum。

4.5 wwwguest_shm:wwwguest 在线用户表结构 WWW_GUEST_TABLE  [ 回目录 ]

kbs 系统中的 wwwguest 登录处理是几乎完全独立的一部分,数据存储在 wwwguest_shm 共享内存区,结构为 WWW_GUEST_TABLE

#define MAX_WWW_MAP_ITEM (MAX_WWW_GUEST/32)

struct WWW_GUEST_TABLE {
    int hashtab[16][256][256];
    int use_map[MAX_WWW_MAP_ITEM + 1];
        use_map[i] 的从低到高第 j 个 bit 表示当前 guest_entry[i * 32 + j] 是否被使用
    time_t uptime;
        一个标记时间,wwwguest 新登录时如果当前时间和这个标记时间相差
        240 秒以上,就遍历所有 wwwguest 登录踢掉发呆时间过长的登录,同时将
        uptime 设置成当前时间。
    struct WWW_GUEST_S guest_entry[MAX_WWW_GUEST];
        每个 wwwguest 登录的具体信息,结构定义如下
};

struct WWW_GUEST_S {
    int key;
    time_t freshtime;
        上次活动的时间戳,用来计算发呆时间。
    time_t logintime;
    int currentboard;
        当前所在版面的 bid
    struct in_addr fromip;
        来源 IP
};


4.6 bcache,brdshm:版面状态共享内存结构 BCACHE  [ 回目录 ]

全局变量 bcache 指向 .BOARDS 文件的 mmap 数据。bcache[bid - 1] 是版面号为 bid 的那个版面的 boardheader

如此之外,每个版面还有一些状态信息,即全局变量 brdshm 指向的共享内存(标识 BCACHE_SHMKEY),其结构 BCACHE 为:

struct BCACHE {
    int numboards;
        所有版面版面号中最大的那个 bid。主要是遍历等工作可以只循环到 numboards
        为止而无须到 MAXBOARD,以提高效率。
    struct BoardStatus bstatus[MAXBOARD];
        具体版面状态数据
};

其中 BoardStatus 的定义是:

struct BoardStatus {            /* use this to speed up board list */
    int total;
        总文章数。如果版面文章数显示和实际不符就是因为这个字段没同步。
    int lastpost;
        本版最后一篇文章的 id 号,用户看到版面是否有新文章就是从这个字段计算。
    bool updatemark;
        是否被 m 的文章有变动,决定下次用户看被 m 文章列表是否需要重新产生索引
    bool updatetitle;
        是否置底文章有变动,决定下次使用 .DINGDIR 时是否需要重新产生
    bool updateorigin;
        是否原作文章有变动,决定下次使用 .ORIGIN 时是否需要重新产生
    int currentusers;
        当前该版面在线用户数,包括 wwwguest。
    int nowid;
        该版面文章当前已经使用到的 id 号,注意这个字段不一定等于 lastpost,比如
        有人发表了两片文章 id=79,80,然后删掉 id=80 的文章,这时候 lastpost=79,
        但是 nowid=80。在刷新 .BOARDS 时,该字段写回 .BOARDS 文件相应版面的
        idseq 字段以便下次启动时读入。发表帖子的时候系统会自动根据 nowid 分配下
        一个 id 值。如果这个字段出错,web 下浏览会不正常。
    int toptitle;
        本版面置顶文章个数
    struct fileheader topfh[MAX_DING];
        .DINGDIR 的内容,注意 MAX_DING 默认是 10,所以 .DINGDIR 内超过 10 条的
        置顶只会被读入 10 条,更多的显示不出来。
#ifdef HAVE_WFORUM
    int todaynum;
        这个字段没用,呵呵
#endif
};


5. 代码结构  [ 回目录 ]

kbs 系统的核心代码按照目录解释如下:
libsystem/  系统最底层的函数库,以下所有程序都基于它
libBBS/     一些核心函数组成的库 libBBS.so
src/        telnet 服务器程序,静态连接 libBBS.so
sshbbsd/    ssh 服务器程序,其实就是 src/ 下的程序套了一个 ssh 的壳
daemon/     系统守护进程,应该启动服务时最先启动,静态连接 libBBS.so
php/        输出供 PHP 页面使用的函数,动态连接 libBBS.so
local_utl/  各类小实用程序,动态连接 libBBS.so

--EOF--

No TrackBacks

TrackBack URL: http://www.guduo.net/cgi-bin/mt/mt-tb.cgi/261

Leave a comment

Pages

May 2016

Sun Mon Tue Wed Thu Fri Sat
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        

About this Entry

This page contains a single entry by 谷多 published on February 5, 2010 12:59 AM.

ET128 TD经常断的问题 was the previous entry in this blog.

堂堂移动的每晚例行维护公告 is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.