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 p;
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);
return 0;
} 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為索引值的哈希鏈表
return p;
}
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;
return p;
}
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數據結構