上一篇文章大概捋了一下游戲服務器啓動的時候對應模塊的動作,現在我們來仔細研究一下其中的start_tcp/0和start_client/0部分。
在start_tcp/0中啓動了sd_tcp_listener_sup監控樹,並掛到sd_sup下。其後啓動的進程樹形關係如下:
這是遊戲服務器啓動後使用observer觀察到的遊戲內進程關係圖,<0.68.0>爲start_tcp/0啓動的sd_tcp_listener_sup監控樹,<0.70.0>爲啓動的sd_tcp_listener進程。跟隨在sd_tcp_acceptor_sup後的十個進程爲sd_tcp_acceptor進程。下面結合代碼詳細解釋一下tcp連接的建立過程。
sd_tcp_listener:掛在sd_tcp_listener_sup監控樹下的gen_server,用於監聽tcp端口。看下它的初始化函數init/1:
init({AcceptorCount, Port}) ->
process_flag(trap_exit, true),
case gen_tcp:listen(Port, ?TCP_OPTIONS) of
{ok, LSock} ->
lists:foreach(fun (_) ->
{ok, _APid} = supervisor:start_child(
sd_tcp_acceptor_sup, [LSock])
end,
lists:duplicate(AcceptorCount, dummy)),
{ok, LSock};
{error, Reason} ->
{stop, {cannot_listen, Reason}}
end.
使用gen_tcp:listen/2監聽指定的端口,建立ListenSocket後通過lists:duplicate/2創建10個sd_tcp_acceptor_sup的子進程(此處傳入的AcceptorCount爲10)。
sd_tcp_acceptor:掛在sd_tcp_acceptor_sup下的gen_server,在建立ListenSocket之後被創建。看一下它的初始化函數init/1:
init({LSock}) ->
gen_server:cast(self(), accept),
{ok, #state{sock=LSock}}.
進程在初始化的時候給自己cast了一條消息,內容爲原子accept,在hande_cast/2中接收到消息後調用accept/1函數,函數的實現:
accept(State = #state{sock=LSock}) ->
case prim_inet:async_accept(LSock, -1) of
{ok, Ref} -> {noreply, State#state{ref=Ref}};
Error -> {stop, {cannot_accept, Error}, State}
end.
此處沒有使用gen_tcp:receive或者gen_tcp:accept來接收客戶端消息,而是使用了prim_inet:async_accept/2來進行異步的消息接收,如果有消息傳來,此時進程本身會收到一條格式爲{inet_async,S,Ref,Status}的消息,由於進程爲一個gen_server,我們在handle_info裏面處理:
handle_info({inet_async, LSock, Ref, {ok, Sock}}, State = #state{sock=LSock, ref=Ref}) ->
case set_sockopt(LSock, Sock) of
ok -> ok;
{error, Reason} -> exit({set_sockopt, Reason})
end,
start_client(Sock),
accept(State);
建立了與客戶端的Socket連接後,調用start_client/1函數:
start_client(Sock) ->
{ok, Child} = supervisor:start_child(sd_tcp_client_sup, []),
ok = gen_tcp:controlling_process(Sock, Child),
Child ! {go, Sock}.
在sd_tcp_client_sup監控樹下新建客戶端進程sd_reader,使用gen_tcp:controlling_process/2將建立的Socket控制進程修改爲該客戶端進程,並給該進程發送一條{go,Socket}的消息,客戶端進程接收到這條消息後便開始接手與客戶端的通信。
之後sd_tcp_acceptor調用accept/1函數,進入循環等待客戶端連接的過程。