[Erlang] tcp_server.erl 详细注释
首先请注意下 receive 关键字出现的次数
Copyright (C) 2002, Joe Armstrong
File : tcp_server.erl
Author : Joe Armstrong (joe@sics.se)
Purpose : Keeps track of a number of TCP sessions
Last modified: 2002-11-17
-module(tcp_server).
-export([start_raw_server/4, start_client/3,
stop/1, children/1]).
-define(KILL_DELAY, 1000).
%% -export([start_child/3]).
%% start_raw_server(Port, Fun, Max)
%% This server accepts up to Max connections on Port
%% The *first* time a connection is made to Port
%% Then Fun(Socket) is called.
%% Thereafter messages to the socket result in messsages to the handler.
%% a typical server is usually written like this:
%% To setup a lister
%% start_server(Port) ->
%% S = self(),
%% process_flag(trap_exit, true),
%% tcp_server:start_raw_server(Port,
%% fun(Socket) -> input_handler(Socket, S) end,
%% 15,
%% 0)
%% loop().
%% The loop() process is a central controller that all
%% processes can use to synchronize amongst themselfves if necessary
%% It ends up as the variable "Controller" in the input_handler
%% A typical server is written like this:
%% input_handler(Socket, Controller) ->
%% receive
%% {tcp, Socket, Bin} ->
%% ...
%% gen_tcp:send(Socket, ...)
%%
%% {tcp_closed, Socket} ->
%%
%%
%% Any ->
%% ...
%%
%% end.
start_client(Host, Port, Length) -> % 做客户端连别人
gen_tcp:connect(Host, Port,
[binary,
{active, true},
{packet, 2},
{packet_size, Length}], 30000).
%% Note when start_raw_server returns, it should be ready to
%% Immediately accept connections
%% 模块的入口函数
start_raw_server(Port, Fun, Max, Length) -> % FUN=>Handler Max=>最大连接数 Lengt=>最大包长
Name = port_name(Port), % 用端口号来生成进程名
case whereis(Name) of % 判断是不是 已经定义过了 ,注意:这里Name是已经绑定的
undefined -> % 如果端口还未定义的话,则启动
Self = self(), % 这是一个惯用法
Pid = spawn_link(fun() -> % 产生一个新进程
cold_start(Self, Port, Fun, Max, Length) % 冷启动,注意下FUN的调用位置,这个FUN是用户传入的一个函数,用来处理新的Socket,Self 在cold_start里面将被称为Master
end),
receive
{Pid, ok} -> % 这个消息是期待 cold_start 发来的
register(Name, Pid), % 注册进程成为知名进程,方便通过名字来发消息
{ok, Pid};
{Pid, Error} -> % 出错了,其实可以省略,嘿嘿
Error
end;
_Pid -> % 如果端口已定义,则报错
{error, already_started} % 把错误返回给调用者, 开端口失败
end.
stop(Port) when integer(Port) ->
Name = port_name(Port),
case whereis(Name) of
undefined ->
not_started;
Pid ->
exit(Pid, kill), % 这里是强制进程退出,注意: 服务器是跑在另外的一个进程里面的
(catch unregister(Name)), % 去注册进程名
stopped
end.
children(Port) when integer(Port) ->
port_name(Port) ! {children, self()}, % 这个是通过进程名来发消息
receive
{session_server, Reply} -> Reply % Reply应该去的活动链接列表 Active
end.
port_name(Port) when integer(Port) ->
list_to_atom("portServer" ++ integer_to_list(Port)). % 一个简单的助手函数,根据端口号生成进程名
% 监听进程的入口函数
cold_start(Master, Port, Fun, Max, Length) -> % 这个函数是端口监听进程的入口函数
process_flag(trap_exit, true), % 设置退出陷阱,不设置进程会被EXIT信号终止
io:format("Starting a port server on ~p...~n",[Port]),
% 绑定到端口,进行监听
case gen_tcp:listen(Port, [binary, % 这里才开始监听端口
%% {dontroute, true},
{nodelay,true}, % 非阻塞
{packet_size, Length}, % 由使用者指定的包最大大小,可以为0
{packet, 2},
{backlog, 1024}, % 未经过处理的连接请求队列可以容纳的最大数目
{reuseaddr, true}, % 可以快速重用地址
{active, false}]) of
{ok, Listen} ->
%% io:format("Listening on:~p~n",[Listen]),
Master ! {self(), ok}, % 发送成功消息
New = start_accept(Listen, Fun), % 开始接受连接 ,注意: Listen就是侦听Socket,Fun用户传入的Handler函数 , start_accept/2 开了一个进程!!! New是新进程的Pid
%% Now we're ready to run
socket_loop(Listen, New, [], Fun, Max); % 接受连接后,开始网络通信了
Error ->
Master ! {self(), Error} % 不认识的消息都转发给主进程处理
end.
%% Don't mess with the following code uless you really know what you're
%% doing (and Thanks to Magnus for heping me get it right)
%% 作用
%% 接受连接后,开始运作
socket_loop(Listen, New, Active, Fun, Max) -> % 服务器循环函数,这个函数凸显的FP风格的精髓
receive % 从进程的消息队列收消息
{istarted, New} -> % 接受了一个新连接
Active1 = [New|Active], % 参见start_child,把新连接加入到活动列表中
possibly_start_another(false, Listen, Active1, Fun, Max);
{'EXIT', New, _Why} -> % 注意: New是已经在参数中绑定成 start_accpet/2 返回的accpet进程Pid了
%%io:format("Child exit=~p~n",[Why]),
possibly_start_another(false, Listen, Active, Fun, Max);
{'EXIT', Pid, _Why} -> % 由用户断开了连接
%%io:format("Child exit=~p~n",[Why]),
Active1 = lists:delete(Pid, Active),
possibly_start_another(New, Listen, Active1, Fun, Max);
{children, From} ->
From ! {session_server, Active}, % 这里对 children 消息做个简单的回应Active是活动链接列表
socket_loop(Listen, New, Active, Fun, Max);
Other ->
io:format("Here in loop:~p~n",[Other])
end.
possibly_start_another(New, Listen, Active, Fun, Max) when pid(New) ->
socket_loop(Listen, New, Active, Fun, Max); % 继续提供服务
possibly_start_another(false, Listen, Active, Fun, Max) ->
case length(Active) of
N when N < Max ->
New = start_accept(Listen, Fun), % 用户已近断开了,等待新连入请求
socket_loop(Listen, New, Active, Fun, Max);
_ -> % 应该是N>=Max了,length的返回值很简单
error_logger:warning_report(
[{module, ?MODULE},
{line, ?LINE},
{message, "Connections maxed out"},
{maximum, Max},
{connected, length(Active)},
{now, now()}]),
socket_loop(Listen, false, Active, Fun, Max) % 继续循环
end.
start_accept(Listen, Fun) ->
S = self(),
spawn_link(fun() -> start_child(S, Listen, Fun) end). % 一个进程处理一个连接 返回进程Pid
start_child(Parent, Listen, Fun) -> % Listen 是侦探Socket, Parent 是调用start_accept/2的进程,也就是侦听进程
case gen_tcp:accept(Listen) of % 接受一个连接
{ok, Socket} ->
Parent ! {istarted,self()}, % 参见socket_loop, tell the controller 新用户连入
inet:setopts(Socket, [{nodelay,true}, % 非阻塞
{packet, 2},
{active, true}]), % before we activate socket
Fun(Socket); % 这里应用了Handler函数Fun
_Other ->
exit(oops) % 其实,不用写这句 :)
end.
本文基于 http://coderplay.javaeye.com/blog/92804 的成果.在此表示感谢.