天天看點

online遊戲伺服器架構—使用者登入資料組織

sprite_t類型的資料結構是核心資料結構,每一個登入使用者對應一個,它的初始化在使用者登入的時候,此後一直到使用者退出或者離線一直儲存在系統記憶體當中,在此過程中該sprite_t資料結構被儲存在兩個哈希表當中,一個是以使用者的id為索引,這個是邏輯相關的,另一個是以此連接配接的套結字描述符為索引,這個是邏輯無關的:

int parse_protocol(uint8_t *data, int rcvlen, fdsession_t* fdsess)

{

protocol_t pkg;

sprite_t *p, tmp; //tmp是個局部變量,配置設定于棧上,由于此後的執行續是串行的,也就是說在動态配置設定sprite_t資料結構于堆上之前并不清除此函數的調用棧幀,是以這裡使用局部變量很安全。

int i;

i = 0;

//此處用UNPKG_XX系列解析pkg.len, pkg.ver, pkg.cmd, pkg.id, pkg.ret

p = get_sprite_by_fd(fdsess->fd); //以套結字描述符查找sprite_t資料結構,如果該使用者已經登入,那麼一定能查找到的,因為使用者和online的互動是長連接配接,如果是登入包,那麼肯定查不到,因為這是第一個包

if ((pkg.cmd != PROTO_LOGIN && !p) || (pkg.cmd == PROTO_LOGIN && p) …){

ERROR_RETURN(("pkg error”);

}

if (pkg.cmd == PROTO_LOGIN) {

sprite_t* old = get_sprite(pkg.id); //以id為索引查找該使用者是否已經登入

if (old) notify_user_exit(old, -ERR_multi_login, 1); //如果已經登入,那麼踢出已經登入的使用者

p = &tmp;

memset(p, 0, sizeof(*p));

p->id = pkg.id; //設定使用者的id,登入期間一定唯一

p->item_cnt = 0;

p->fd = fdsess->fd; //設定fd,套結字描述符,一定是唯一的

p->fdsess = fdsess;

return dispatch_protocol(p, pkg.cmd, data + sizeof (pkg), pkg.len - sizeof (pkg));

由此可見,一個sprite_t資料結構連接配接在兩個哈希清單中,一個是套結字描述符為索引的,另一個是使用者id為索引的,注意這兩個索引都是唯一的索引。每每配置設定一個sprite_t資料結構都要将之插入到兩個哈希表當中,以套結字描述符為索引的查找函數如下:

sprite_t* get_sprite_by_fd(int fd)

sprite_t* p = g_hash_table_lookup(all_sprites, &fd);

if ( !p || IS_NPC_ID(p->id) )

return 0;

return p;

以使用者id為索引的查找函數如下:

static inline sprite_t *get_sprite (uint32_t id)

sprite_t *p;

list_for_each_entry (p, &idslots[id % HASH_SLOT_NUM], hash_list)

if (p->id == id)

return NULL;

對于登入包,最終dispatch_protocol會進入到auth_cmd,該函數對使用者的一些資訊進行一些如MD5之類的驗證,然後進入到do_auth函數:

static inline int

do_auth(sprite_t* v)

sprite_t* p = add_sprite(v); //該函數将新配置設定的sprite_t資料結構插入到兩個哈希連結清單當中

notify_user_login(p, 1);

ADD_TIMER_EVENT(p, long_time_min45_in_game, 0, now.tv_sec + 45*60);

ADD_TIMER_EVENT(p, long_time_min10_in_game, 0, now.tv_sec + 10*60);

if (IS_GUEST_ID(p->id)) { //如果是訪客的話進入下面流程

enter_map(p, 1, 0); //為訪客直接設定地圖

rsp_proto_login(p);

} else {

return db_get_sprite_with_mail(p);

sprite_t* add_sprite(sprite_t* v)

sprite_t* p = alloc_sprite(v->fd);

*p = *v;

p->stamp = now.tv_sec;

INIT_LIST_HEAD(&p->hash_list);

INIT_LIST_HEAD(&p->map_list);

INIT_LIST_HEAD(&p->timer_list);

list_add_tail(&p->hash_list, &idslots[p->id % HASH_SLOT_NUM]); //插入到以使用者id為索引值的哈希連結清單

static inline sprite_t* alloc_sprite(int fd)

sprite_t* p = g_slice_alloc(SPRITE_STRUCT_LEN); //配置設定一個sprite_t資料結構

p->fd = fd; //初始化一個套結字索引值

g_hash_table_insert(all_sprites, &(p->fd), p); //以套結字描述符為索引插入到全局的哈希連結清單

++sprites_count;

enter_map是一個很重要的函數,它設定了玩家的地圖資訊,每一個地圖都是一個資料結構map_t,裡面包含一個list_head類型的資料結構sprite_list_head,而每一個sprite_t資料結構都有一個list_head類型的map_list,每次初始化完了一個sprite_t之後最終都要調用一個list_add_tail (&p->map_list, &tile->sprite_list_head)将該sprite_t加入到一個map的list當中,這裡的list_head就是linux核心中的最常見的list_head資料結構

 本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1274108

繼續閱讀