%%聊天的中转站,将{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.
%%聊天服务器 -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函数 %%{port, 2223}. %%{service, chat, password,"AsDT67aQ",mfa,mod_chat_controller,start,[]}. start() -> start_server(), lib_chan:start_server("D:\code\erlcode\socket_dist\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(_, []) -> [].
%%聊天群组,包含各个连接进该群组的进程id -module(chat_group). -import(lib_chan_mm, [send/2, controller/2]). -import(lists, [foreach/2, reverse/2]). -export([start/2]). 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); {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_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 sending data "), 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(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, " "]), 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 >").
-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** ** /h for help "} 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 ++ " ", io_widget:insert_str(W, Str1), loop(W) end. parse(Str) -> {str, Str}.