SKYNET設(shè)計綜述講到模塊被稱為服務(wù)
SKYNET設(shè)計綜述講到模塊被稱為服務(wù)。“服務(wù)間可以自由發(fā)送消息。每個模塊可以向 Skynet 框架注冊一個 callback 函數(shù),用來接收發(fā)給它的消息。”還提到“把一個符合規(guī)范的 C 模塊,從動態(tài)庫(so 文件)中啟動起來,綁定一個永不重復(fù)(即使模塊退出)的數(shù)字 id 做為其 handle 。Skynet 提供了名字服務(wù),還可以給特定的服務(wù)起一個易讀的名字,而不是用 id 來指代它。id 和運行時態(tài)相關(guān),無法保證每次啟動服務(wù),都有一致的 id ,但名字可以?!苯裉煲治龅膬蓚€文件skynet_handle.c和skynet_handle.h就是實現(xiàn)名字服務(wù)的。
而WIKI中的CLUSTER講到的是harbor相關(guān)的內(nèi)容,“每個 skynet 服務(wù)都有一個全網(wǎng)唯一的地址,這個地址是一個 32bit 數(shù)字,其高 8bit 標識著它所屬 slave 的號碼。即 harbor id 。在 master/slave 網(wǎng)絡(luò)中,id 為 0 是保留的。所以最多可以有 255 個 slave 節(jié)點。”
前面寫這么一大段東西都是分析代碼所需要的,不是為了湊字數(shù),也不是為了別的原因。下面開始分析代碼。
#include?"skynet.h" #define?DEFAULT_SLOT_SIZE?4 #define?MAX_SLOT_SIZE?0x40000000 //名字服務(wù)結(jié)構(gòu),一個名字對應(yīng)一個handle struct?handle_name?{ ????char?*?name;?//服務(wù)名字 ????uint32_t?handle;?//服務(wù)ID,下面以handle來稱呼 }; //保存handle/name列表的數(shù)據(jù)結(jié)構(gòu),skynet_handle_init會初始化它 struct?handle_storage?{ ????struct?rwlock?lock; ????uint32_t?harbor;//這就是wiki里提到的harbor!! ????uint32_t?handle_index;?//必須從1開始 ????int?slot_size;?//數(shù)組長度 ????struct?skynet_context?**?slot;?//數(shù)組,實際上里面存的是服務(wù)的上下文 ???? ????int?name_cap;?//容量 ????int?name_count;?//長度或者說個數(shù) ????struct?handle_name?*name;?//數(shù)組 }; static?struct?handle_storage?*H?=?NULL; //注冊服務(wù),返回給它一個handle uint32_t skynet_handle_register(struct?skynet_context?*ctx)?{ ????struct?handle_storage?*s?=?H; ????rwlock_wlock(&s->lock); ????//死循環(huán),所以服務(wù)數(shù)量如果太大了你懂得 ????for?(;;)?{ ????????int?i; ????????for?(i=0;islot_size;i++)?{?//遍歷服務(wù)列表,找個空位 ????????????uint32_t?handle?=?(i+s->handle_index)?&?HANDLE_MASK;?//只取后24位 ????????????int?hash?=?handle?&?(s->slot_size-1);?//&操作符,就是取到一個小于s->slot_size-1的值,slot_size-1源碼下方有詳細解釋。 ????????????if?(s->slot[hash]?==?NULL)?{?//沒有hash碰撞,好巧 ????????????????s->slot[hash]?=?ctx; ????????????????s->handle_index?=?handle?+?1;?//handle_index增加了 ????????????????rwlock_wunlock(&s->lock); ????????????????handle?|=?s->harbor;?//?位操作或,把harbor?id附上去,實在不理解你就把它當加法吧 ????????????????return?handle; ????????????} ????????} //不幸的事情發(fā)生了,一直都在HASH碰撞,也就是說坑已經(jīng)填滿了 ????????assert((s->slot_size*2?-?1)?slot_size?*?2?*?sizeof(struct?skynet_context?*)); //老套路,數(shù)據(jù)清零 ????????memset(new_slot,?0,?s->slot_size?*?2?*?sizeof(struct?skynet_context?*)); //把老數(shù)據(jù)拷過來,要重新hash,但是handle_index沒增加 ????????for?(i=0;islot_size;i++)?{ ????????????int?hash?=?skynet_context_handle(s->slot[i])?&?(s->slot_size?*?2?-?1); ????????????assert(new_slot[hash]?==?NULL); ????????????new_slot[hash]?=?s->slot[i]; ????????} ????????skynet_free(s->slot); ????????s->slot?=?new_slot;?//直接替換指針 ????????s->slot_size?*=?2;?//容量擴大一倍 ????} //容量都擴一倍了,再試試會不會hash碰撞吧,再碰我們就再擴大一倍 } //退休某個服務(wù),反注冊 int skynet_handle_retire(uint32_t?handle)?{ ????int?ret?=?0; ????struct?handle_storage?*s?=?H; ????rwlock_wlock(&s->lock); //取有效位hash ????uint32_t?hash?=?handle?&?(s->slot_size-1); //取服務(wù)上下文,一會兒要釋放的 ????struct?skynet_context?*?ctx?=?s->slot[hash]; //較驗這個服務(wù)是存在的,而且確實對應(yīng)的就是這個handle ????if?(ctx?!=?NULL?&&?skynet_context_handle(ctx)?==?handle)?{ ????????s->slot[hash]?=?NULL;?//把空位讓出來 ????????ret?=?1; ????????int?i; ????????int?j=0,?n=s->name_count; ????????for?(i=0;?iname[i].handle?==?handle)?{ ????????????????skynet_free(s->name[i].name);?//釋放內(nèi)存 ????????????????continue; ????????????}?else?if?(i!=j)?{?//這里在做數(shù)組元素刪除操作,把后面的都往前移一下 ????????????????s->name[j]?=?s->name[i]; ????????????} ????????????++j;//元素刪除輔助 ????????} ????????s->name_count?=?j; ????}?else?{ ????????ctx?=?NULL; ????} ????rwlock_wunlock(&s->lock); //這里就釋放服務(wù)了 ????if?(ctx)?{ ????????//?release?ctx?may?call?skynet_handle_*?,?so?wunlock?first. ????????skynet_context_release(ctx); ????} ????return?ret; } //全部退休了 void? skynet_handle_retireall()?{ ????struct?handle_storage?*s?=?H; ????for?(;;)?{ ????????int?n=0; ????????int?i; ????????for?(i=0;islot_size;i++)?{ ????????????rwlock_rlock(&s->lock); ????????????struct?skynet_context?*?ctx?=?s->slot[i];?//取服務(wù)上下文 ????????????uint32_t?handle?=?0; ????????????if?(ctx) ????????????????handle?=?skynet_context_handle(ctx); ????????????rwlock_runlock(&s->lock); ????????????if?(handle?!=?0)?{?//對服務(wù)上下文的handle進行“退休” ????????????????if?(skynet_handle_retire(handle))?{ ????????????????????++n; ????????????????} ????????????} ????????} ????????if?(n==0) ????????????return; ????} } struct?skynet_context?*? skynet_handle_grab(uint32_t?handle)?{ ????struct?handle_storage?*s?=?H; ????struct?skynet_context?*?result?=?NULL; ????rwlock_rlock(&s->lock); ????uint32_t?hash?=?handle?&?(s->slot_size-1); ????struct?skynet_context?*?ctx?=?s->slot[hash]; ????if?(ctx?&&?skynet_context_handle(ctx)?==?handle)?{ ????????result?=?ctx; ????????skynet_context_grab(result);?//引用計數(shù)+1 ????} ????rwlock_runlock(&s->lock); ????return?result; } //通過name找handle //算法是二分查找法 //二分查找法請自行g(shù)ooge/bing/baidu uint32_t? skynet_handle_findname(const?char?*?name)?{ ????struct?handle_storage?*s?=?H; ????rwlock_rlock(&s->lock); ????uint32_t?handle?=?0; ????int?begin?=?0; ????int?end?=?s->name_count?-?1; ????while?(beginname[mid]; ????????int?c?=?strcmp(n->name,?name);?//strcmp是個c系統(tǒng)函數(shù) ????????if?(c==0)?{?//找到匹配的名字 ????????????handle?=?n->handle; ????????????break; ????????} ????????if?(c<0)?{?//當前位置的名字?<?要查找的名字,到后半部分去找 ????????????begin?=?mid?+?1; ????????}?else?{?//當前位置的名字?>要查找的名字,到前半部分去找 ????????????end?=?mid?-?1; ????????} ????} ????rwlock_runlock(&s->lock); ????return?handle; } //把name插入到name數(shù)組中,再關(guān)聯(lián)handle static?void _insert_name_before(struct?handle_storage?*s,?char?*name,?uint32_t?handle,?int?before)?{ //擴容 ????if?(s->name_count?>=?s->name_cap)?{ ????????s->name_cap?*=?2;?//擴容 ????????assert(s->name_cap?name_cap?*?sizeof(struct?handle_name));?//開一個新數(shù)組,容量是老數(shù)據(jù)的2倍 ????????int?i; ????????for?(i=0;iname[i]; ????????} ????????for?(i=before;iname_count;i++)?{?//復(fù)制before及后面的數(shù)據(jù) ????????????n[i+1]?=?s->name[i]; ????????} ????????skynet_free(s->name);?//把老數(shù)據(jù)內(nèi)存回收了 ????????s->name?=?n;?//把新數(shù)組設(shè)進來 ????}?else?{?//空間夠用 ????????int?i; ????????for?(i=s->name_count;i>before;i--)?{?//從后往前,一次一個移動數(shù)組元素,把before位置空出來 ????????????s->name[i]?=?s->name[i-1]; ????????} ????} //賦值了 ????s->name[before].name?=?name;?//名字 ????s->name[before].handle?=?handle;?//handle ????s->name_count?++;?//數(shù)量+1 } //給handle綁定一個name //name是由小到大順序排列的 //二分查找法,數(shù)據(jù)結(jié)構(gòu)很重要啊,少年 static?const?char?* _insert_name(struct?handle_storage?*s,?const?char?*?name,?uint32_t?handle)?{ ????int?begin?=?0; ????int?end?=?s->name_count?-?1; ????while?(beginname[mid]; ????????int?c?=?strcmp(n->name,?name); ????????if?(c==0)?{?//名字已經(jīng)存在了,不能再綁或者重復(fù)綁 ????????????return?NULL; ????????} ????????if?(c<0)?{?//當前字符串?<?要插入的字符串 ????????????begin?=?mid?+?1;?//二分查找后半部分 ????????}?else?{?//當前字符串?>要插入的字符串 ????????????end?=?mid?-?1;?//二分查找前半部分 ????????} ????} ????char?*?result?=?skynet_strdup(name);?//字符串復(fù)制 //把name插入到數(shù)組中 ????_insert_name_before(s,?result,?handle,?begin); ????return?result; } //給handle綁定一個name const?char?*? skynet_handle_namehandle(uint32_t?handle,?const?char?*name)?{ ????rwlock_wlock(&H->lock); ????const?char?*?ret?=?_insert_name(H,?name,?handle); ????rwlock_wunlock(&H->lock); ????return?ret; } //初始化handle_storage void? skynet_handle_init(int?harbor)?{ ????assert(H==NULL); ????struct?handle_storage?*?s?=?skynet_malloc(sizeof(*H)); ????s->slot_size?=?DEFAULT_SLOT_SIZE;?//初始數(shù)組大小,slot_size是會變大的 ????s->slot?=?skynet_malloc(s->slot_size?*?sizeof(struct?skynet_context?*));?//分配內(nèi)存 ????memset(s->slot,?0,?s->slot_size?*?sizeof(struct?skynet_context?*));?//數(shù)據(jù)清零 ????rwlock_init(&s->lock); ????//?reserve?0?for?system ????s->harbor?=?(uint32_t)?(harbor?&?0xff)?<<?HANDLE_REMOTE_SHIFT;?//這就是wiki中提到的,前8位是harbor?id,HANDLE_REMOTE_SHIFT為24 ????s->handle_index?=?1;?// ????s->name_cap?=?2;?//暫時只讓放2個 ????s->name_count?=?0;?//空的 ????s->name?=?skynet_malloc(s->name_cap?*?sizeof(struct?handle_name)); ????H?=?s; ????//?Don't?need?to?free?H }
代碼中有個很巧妙的設(shè)計,就是s->slot_size-1,它的低位二進制永遠都是1。不信你看,剛開始slot_size是4,4-1就是3,擴了以后是8,8-1就是7,然后16,32....。這樣的話,和任何數(shù)字與操作,都不會丟失“有效的”低位。
好了,到此為止,代碼也看完了??偨Y(jié)一下,skynet_handle.c實際上就做了兩個核心的事情,一是給服務(wù)分配一個handle,二是把handle和name關(guān)聯(lián)起來。
把handle和name關(guān)聯(lián)起來比較容易懂,實際上使用一個數(shù)組,關(guān)聯(lián)的時候使用二分查找到數(shù)組里查名字,如果名字不存在,就插入一個元素,然后把名字和handle關(guān)聯(lián)起來。插入元素的時候,如果數(shù)組空間不足了,就擴容為原來的2倍。
而給服務(wù)分配handle稍復(fù)雜一些,實際上也是使用一個slot數(shù)組,數(shù)組下標使用的是一個hash,數(shù)組元素指向服務(wù)的上下文。這個hash的算法是比較簡單粗暴的,就是看從handle_indx開始累計到slot_size,看中間有沒有空閑的下標(也就是下標指向為null的),如果遍歷完了還是沒有,就把slot擴大一倍,還是沒有就再擴大一倍,直到找到空位為止,或者是slot長度超出限制為止。
取到了handle以后呢,還要將harbor id附到handle的高8位。
作者:shihuaping0918@163.com