{packet, line} and {active, once} options. Each protocol implementation has a process which reads incoming packets, does preliminary decoding, and forwards the data to a finite state machine.The language and VM are great, but one of the major benefits to writing programs in Erlang is OTP which provides a large standard library and design principles that have been developed and tested in real-world use. One example of this is gen_fsm, a generic behavior for event-based FSMs which is particularly useful for implementing network protocols. The gen_fsm module handles initialization, synchronous and asynchronous event delivery, error reporting, debugging, etc. All the programmer must do is implement a callback module which handles events and makes state change decisions.
Execution of a gen_fsm begins with a call to
gen_fsm:start or gen_fsm:start_link, which invokes the callback module's init/1 function. init performs any necessary initialization and determines the FSM's starting state. Here is msnp_fsm:init/1:
init([Username, Password, Client, Opts]) ->
process_flag(trap_exit, true),
Host = proplists:get_value(host, Opts, ?MSNP_HOST),
Port = proplists:get_value(port, Opts, ?MSNP_PORT),
State = #state{client = Client,
user = #user{username = Username, password = Password},
contacts = [],
pending = dict:new(),
sessions = []},
gen_fsm:send_event(self(), {connect, Host, Port}),
{ok, protocol_negotiation, State}.
The FSM begins in state
protocol_negotiation, and immediately receives an asynchronous event {connect, Host, Port} telling it to connect to the server. Next, gen_fsm will call msnp_fsm:protocol_negotiation/2, passing it the event and FSM state:
protocol_negotiation({connect, Host, Port}, State) ->
{ok, Sock} = msnp_sock:start_link(self(), Host, Port),
msnp_sock:send_cmd(Sock, "VER", ["MSNP15", "CVR0"]),
{next_state, protocol_negotiation, State#state{sock = Sock}};
The FSM remains in
protocol_negotiation until it receives a matching VER command from the server, then transitions through sso_auth, waiting_for_profile, synchronizing, and eventually to the ready state. Each state function is quite simple, and with multiple function clauses to handle different events the code is easy to read. Here are two ready/1 event handlers that handle contact status changes:
ready(#cmd{cmd = "ILN", args = [_, Code, Name, _, Nick | _]}, State) ->
State2 = send_status_update(Name, url_util:decode(Nick), Code, State),
{next_state, ready, State2};
ready(#cmd{cmd = "NLN", args = [Code, Name, _, Nick | _]}, State) ->
State2 = send_status_update(Name, url_util:decode(Nick), Code, State),
{next_state, ready, State2};
msnp_sock parses incoming packets and sends the resulting event to the msnp_fsm as a cmd record. Erlang's pattern matching provides a very concise way to determine which clause to invoke and binds the parameters needed to variables such as Name and Nick.OTP behaviors like gen_fsm also provide much useful debugging functionality. When an event handler fails for any reason a log message is generated with the FSM's state, the last message received, the cause, etc.
gen_fsm:start_link can also be called with the option {debug, [trace]} which will log every incoming event and state change. Running systems can be inspected in real-time thanks to functions such as sys:get_status/1 which displays the state of running processes.With the combination of Erlang's bit-syntax and OTP library it is pretty easy to implement an IM client. Indeed most of the difficulty is due to lack of official documentation and incomplete reverse engineering. In the next post I'll cover one of the most interesting and important aspects of the gateways: scalability.
1 comments:
Hi Will,
Many, many thanks for the article about Erlangs gen_fsm. They are somewhat poorly documented (or I am too stupid to read the documents) so it was very interesting. Another good read is this one.
Cheers, Philipp.
Post a Comment