Erlang編程IRC程序學習

% 輸入輸出窗口
-module(io_widget).

-export([get_state/1,
     start/1, test/0, 
     set_handler/2, 
     set_prompt/2,
     set_state/2,
     set_title/2, insert_str/2, update_state/3]).

start(Pid) ->
    gs:start(),
    spawn_link(fun() -> widget(Pid) end).

get_state(Pid)          -> rpc(Pid, get_state).
set_title(Pid, Str)     -> Pid ! {title, Str}.
set_handler(Pid, Fun)   -> Pid ! {handler, Fun}.
set_prompt(Pid, Str)    -> Pid ! {prompt, Str}.
set_state(Pid, State)   -> Pid ! {state, State}.
insert_str(Pid, Str)    -> Pid ! {insert, Str}.
update_state(Pid, N, X) -> Pid ! {updateState, N, X}. 

rpc(Pid, Q) ->    
    Pid ! {self(), Q},
    receive
    {Pid, R} ->
        R
    end.

widget(Pid) ->
    Size = [{width,500},{height,200}],
    Win = gs:window(gs:start(),
            [{map,true},{configure,true},{title,"window"}|Size]),
    gs:frame(packer, Win,[{packer_x, [{stretch,1,500}]},
              {packer_y, [{stretch,10,100,120},
                      {stretch,1,15,15}]}]),
    gs:create(editor,editor,packer, [{pack_x,1},{pack_y,1},{vscroll,right}]),
    gs:create(entry, entry, packer, [{pack_x,1},{pack_y,2},{keypress,true}]),
    gs:config(packer, Size),
    Prompt = " > ",
    State = nil,
    gs:config(entry, {insert,{0,Prompt}}),
    loop(Win, Pid, Prompt, State, fun parse/1). 

loop(Win, Pid, Prompt, State, Parse) ->   
    receive
    {From, get_state} ->
        From ! {self(), State},
        loop(Win, Pid, Prompt, State, Parse);
    {handler, Fun} ->
        loop(Win, Pid, Prompt, State, Fun);
    {prompt, Str} ->
        %% this clobbers the line being input ...
        %% this could be fixed - hint
        gs:config(entry, {delete,{0,last}}),
        gs:config(entry, {insert,{0,Str}}),
        loop(Win, Pid, Str, State, Parse);
    {state, S} ->
        loop(Win, Pid, Prompt, S, Parse);
    {title, Str} ->
        gs:config(Win, [{title, Str}]),
        loop(Win, Pid, Prompt, State, Parse);
    {insert, Str} ->
        gs:config(editor, {insert,{'end',Str}}),
        scroll_to_show_last_line(),
        loop(Win, Pid, Prompt, State, Parse);
    {updateState, N, X} ->
        io:format("setelemtn N=~p X=~p State=~p~n",[N,X,State]),
        State1 = setelement(N, State, X),
        loop(Win, Pid, Prompt, State1, Parse);
    {gs,_,destroy,_,_} ->
        io:format("Destroyed~n",[]),
        exit(windowDestroyed);
    {gs, entry,keypress,_,['Return'|_]} ->
        Text = gs:read(entry, text),
        io:format("io_widget:input text:~p~n",[Text]),
          io:format("io_widget:send text to pid:~p~n",[Pid]),
        gs:config(entry, {delete,{0,last}}),
        gs:config(entry, {insert,{0,Prompt}}),
        try Parse(Text) of
        Term ->
            Pid ! {self(), State, Term}
        catch
        _:_ ->
            self() ! {insert, "** bad input**\n** /h for help\n"}
        end,
        loop(Win, Pid, Prompt, State, Parse);
    {gs,_,configure,[],[W,H,_,_]} ->
        gs:config(packer, [{width,W},{height,H}]),
        loop(Win, Pid, Prompt, State, Parse);
    {gs, entry,keypress,_,_} ->
        loop(Win, Pid, Prompt, State, Parse);
    Any ->
        io:format("Discarded:~p~n",[Any]),
        loop(Win, Pid, Prompt, State, Parse)
    end.

scroll_to_show_last_line() ->
    Size       = gs:read(editor, size),
    Height     = gs:read(editor, height),
    CharHeight = gs:read(editor, char_height),
    TopRow     = Size - Height/CharHeight,
    if  TopRow > 0 -> gs:config(editor, {vscrollpos, TopRow});
    true       -> gs:config(editor, {vscrollpos, 0})
    end.

test() ->
    spawn(fun() -> test1() end).

test1() ->
    W = io_widget:start(self()),
    io_widget:set_title(W, "Test window"),
    loop(W).

loop(W) ->
    receive
    {W, {str, Str}} ->
        Str1 = Str ++ "\n",
        io_widget:insert_str(W, Str1),
        loop(W)
    end.

parse(Str) ->
    {str, Str}.
% 聊天客戶端
-module(chat_client).

-import(io_widget, 
    [get_state/1, insert_str/2, set_prompt/2, set_state/2, 
     set_title/2, set_handler/2, update_state/3]).

-export([start/0, test/0, connect/5]).

start() ->
	connect("localhost", 2223, "AsDT67aQ", "general", "joe").

test() ->
    connect("localhost", 2223, "AsDT67aQ", "general", "joe"),
    connect("localhost", 2223, "AsDT67aQ", "general", "jane"),
    connect("localhost", 2223, "AsDT67aQ", "general", "jim"),
    connect("localhost", 2223, "AsDT67aQ", "general", "sue").

%創建一個並行進程
connect(Host, Port, HostPsw, Group, Nick) ->
    spawn(fun() -> handler(Host, Port, HostPsw, Group, Nick) end).

                 
handler(Host, Port, HostPsw, Group, Nick) ->
    process_flag(trap_exit, true),
    % 把自己轉化成系統進程,用於捕獲退出信號
    Widget = io_widget:start(self()),
    % 標題
    set_title(Widget, Nick),
    set_state(Widget, Nick),
    % 提示符
    set_prompt(Widget, [Nick, " > "]),
    set_handler(Widget, fun parse_command/1),
    %創建連接進程(這個進程會去嘗試連接服務器)
    start_connector(Host, Port, HostPsw),    
    %等待一個連接事件
    disconnected(Widget, Group, Nick).

%%接收服務器的連接反饋信息,連接上後進行登錄
disconnected(Widget, Group, Nick) ->
    receive
    {connected, MM} ->
        insert_str(Widget, "connected to server\nsending data\n"),
        lib_chan_mm:send(MM, {login, Group, Nick}),
        wait_login_response(Widget, MM);
    {Widget, destroyed} ->
        exit(died);
    {status, S} ->
        insert_str(Widget, to_str(S)),
        disconnected(Widget, Group, Nick);
    Other ->
        io:format("chat_client disconnected unexpected:~p~n",[Other]),
        disconnected(Widget, Group, Nick)
    end.

%%接收服務器的登錄反饋信息
wait_login_response(Widget, MM) ->
    receive
    {chan, MM, ack} ->
        active(Widget, MM);
    Other ->
        io:format("chat_client login unexpected:~p~n",[Other]),
        wait_login_response(Widget, MM)
    end. 

%%登錄後獲取客戶端的反饋,並接受傳到客戶端的信息(如果所有的程序都運行無誤),active用於監視與羣組的連接
active(Widget, MM) ->
     receive
     {Widget, Nick, Str} ->
         lib_chan_mm:send(MM, {relay, Nick, Str}),
         active(Widget, MM);
     {chan, MM, {msg, From, Pid, Str}} ->
         insert_str(Widget, [From,"@",pid_to_list(Pid)," ", Str, "\n"]),
         active(Widget, MM);
     {'EXIT',Widget,windowDestroyed} ->
         lib_chan_mm:close(MM);
     {close, MM} ->
         exit(serverDied);
     Other ->
         io:format("chat_client active unexpected:~p~n",[Other]),
         active(Widget, MM)
     end. 

%%鏈接chat服務器
start_connector(Host, Port, Pwd) ->
    S = self(),
    spawn_link(fun() -> try_to_connect(S, Host, Port, Pwd) end).

try_to_connect(Parent, Host, Port, Pwd) ->
    %% Parent is the Pid of the process that spawned this process
    case lib_chan:connect(Host, Port, chat, Pwd, []) of
   	%% 無法連接,發送狀態消息給客戶端
    {error, _Why} ->
        Parent ! {status, {cannot, connect, Host, Port}},
        sleep(2000),
        try_to_connect(Parent, Host, Port, Pwd);
    {ok, MM} ->
        lib_chan_mm:controller(MM, Parent),
        Parent ! {connected, MM},
        exit(connectorFinished)
    end.


sleep(T) ->
    receive
    after T -> true
    end.
        
to_str(Term) ->
    io_lib:format("~p~n",[Term]).

parse_command(Str) -> skip_to_gt(Str).

skip_to_gt(">" ++ T) -> T;
skip_to_gt([_|T])    -> skip_to_gt(T);
skip_to_gt([])       -> exit("no >").
%%聊天的中轉站,將{chan,MM,Msg}形式的信息轉化爲 {mm, MM, Msg}形式
%%聊天控制器
-module(mod_chat_controller).
-export([start/3]).
-import(lib_chan_mm, [send/2]).

start(MM, _, _) ->
    process_flag(trap_exit, true),
    io:format("mod_chat_controller off we go ...~p~n",[MM]),
    loop(MM).

loop(MM) ->
     receive
     {chan, MM, Msg} ->
         chat_server ! {mm, MM, Msg},
         loop(MM);
      %會話終止,收到一個退出消息,通知聊天服務器客戶端語已經退出
     {'EXIT', MM, _Why} ->
         chat_server ! {mm_closed, MM};
     Other ->
         io:format("mod_chat_controller unexpected message =~p (MM=~p)~n",
               [Other, MM]),
         loop(MM)
    end.
mod_chat_controller
%%聊天羣組,包含各個連接進該羣組的進程id
-module(chat_group).
% controller的調用把當前中間人的控制進程設爲羣組控制器。意味着原本由中間人控制着套接字,所有發往這個套接字的消息。現在都會被髮給羣組控制器
-import(lib_chan_mm, [send/2, controller/2]).
-import(lists, [foreach/2, reverse/2]).

-export([start/2]).

%啓動羣組服務器z
start(C, Nick) ->
    process_flag(trap_exit, true),
    controller(C, self()),
    send(C, ack),
    self() ! {chan, C, {relay, Nick, "I'm starting the group"}},
    group_controller([{C,Nick}]).


delete(Pid, [{Pid,Nick}|T], L) -> {Nick, reverse(T, L)};
delete(Pid, [H|T], L)          -> delete(Pid, T, [H|L]);
delete(_, [], L)               -> {"????", L}.


group_controller([]) ->
    exit(allGone);
group_controller(L) ->
    receive
    % 將其廣播給羣組中的所有進程
    {chan, C, {relay, Nick, Str}} ->
        foreach(fun({Pid,_}) -> send(Pid, {msg,Nick,C,Str}) end, L),
        group_controller(L);
    % 往廣播列表中加入一個元組{C,Nick}
    {login, C, Nick} ->
        controller(C, self()),
        send(C, ack),
        self() ! {chan, C, {relay, Nick, "I'm joining the group"}},
        group_controller([{C,Nick}|L]);
    {chan_closed, C} ->
        {Nick, L1} = delete(C, L, []),
        self() ! {chan, C, {relay, Nick, "I'm leaving the group"}},
        group_controller(L1);
    Any ->
        io:format("group controller received Msg=~p~n", [Any]),
        group_controller(L)
    end.
%%聊天服務器
-module(chat_server).
-import(lib_chan_mm, [send/2, controller/2]).
-import(lists, [delete/2, foreach/2, map/2, member/2,reverse/2]).

-compile(export_all).

%%用chat.conf的配置啓動服務器,每個連接到服務器的socket將調用mod_chat_controller模塊的start函數
start() ->
    start_server(),
    lib_chan:start_server("chat.conf").

start_server() ->
    register(chat_server, 
         spawn(fun() ->
               process_flag(trap_exit, true),
               Val= (catch server_loop([])),
               io:format("Server terminated with:~p~n",[Val])
           end)).	


%%新啓的進程,註冊爲chat_server,用來接收客戶端用戶的登錄消息,當收到某一羣組的登錄信息時,如果該羣組已經存在則傳消息給該羣組,如果不在則創建羣組
server_loop(L) ->
    receive
    {mm, Channel, {login, Group, Nick}} ->
        case lookup(Group, L) of
        {ok, Pid} ->
            Pid ! {login, Channel, Nick},
            server_loop(L);
        error ->
            Pid = spawn_link(fun() ->
                         chat_group:start(Channel, Nick) 
                     end),
            server_loop([{Group,Pid}|L])
        end;
    {mm_closed, _} ->
        server_loop(L); 
    {'EXIT', Pid, allGone} ->
        L1 = remove_group(Pid, L),
        server_loop(L1);
    Msg ->
        io:format("Server received Msg=~p~n",
              [Msg]),
        server_loop(L)
    end.

% 操作羣主列表的幾個簡單列表處理過程
lookup(G, [{G,Pid}|_]) -> {ok, Pid};
lookup(G, [_|T])       -> lookup(G, T);
lookup(_,[])           -> error.

remove_group(Pid, [{G,Pid}|T]) -> io:format("~p removed~n",[G]), T;
remove_group(Pid, [H|T])       -> [H|remove_group(Pid, T)];
remove_group(_, [])            -> [].

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章