IRC程序

IRC协议:因特网在线聊天协议(IRC)可以分散的运行多台机器,从而实现internet的远程会议。IRC协议利用基本的TCP/IP网络协议系统开发。但是它并没有要求TCP/IP是唯一的运行环境。IRC是一种文本协议,它仅要求用户有一个简单端口程序能与服务器连接。

IRC的一种典型配置包括:服务器形成一个供客户机(或其它服务器)连接、实现所需信息的发送/多路复用技术等功能的信道,服务器作为 IRC 中枢,提供与客户机连接的信道从而实现相互间的聊天;此外它也提供与服务器连接的信道,从而形成一个 IRC 网络。IRC 服务器需要唯一网络配置生成树,每一个服务器充当一个中心代码服务于生成树所看到的其它网络部分。】

为了维持 IRC 网络中的合理秩序,需要一种特殊类型的客户机(操作员)以实现基本的网络维护功能。所谓 IRC 信道指一个命名的包含一个或多个用户的小組,送给这个信道的信息,每一个组内的用户均可收到。

IRC允许两个客户之间的相互通信,一个用户对多个其他用户、以及用户对服务器和服务器对服务器。IRC协议为大多数网络消息及聊天系统提供了技术基础。

协议结构:
IRC是一种具有很多命令的文本协议,主要命令有:
用户〈用户名〉〈主机名〉〈服务器名〉〈真实名〉:在连接初使用,详细说明新用户的用户名、主机名、服务器名及真实名。
通过〈口令〉:在设置“连接口令”时使用。
昵称〈昵称〉〈跳转〉:给用户一个昵称或更换以前的昵称。
服务器〈服务器名〉〈跳转〉〈信息〉:告诉服务器连接的另一个终端是服务器。
进入〈用户〉〈口令〉:请求获得操作权。
停止〈停止消息〉:用户会话以停止消息结束。
服务器停止〈服务器〉〈注释〉:停止和终结服务器提示。
连接〈频道〉:客户机开始收听特别频道。
主题〈频道〉:改换或检查频道。
名字〈频道〉:列出所有的昵称使其他任何信道上的用户都可以看到。
目录〈信道〉:列出信道及其主题。
删除〈信道〉〈用户〉〈注释〉:强制性地从信道上删除用户 。


类IRC程序:
五个组件:
1.用户界面组件:用于接收到的消息显示出来的GUI窗口组件。也负责发送消息,它发出的消息会发送到聊天客户端组件。
2.聊天客户端:负责管理来自聊天窗口的消息,然后将他们转发至当前群组的群控制器。它也负责接收来自群控制器的消息,并将这些消息再转发给聊天窗口。
3.群控制器:负责管理单个聊天组,如果群控制器接收到一个消息,它会将这条消息广播到组中的所有的成员。它还会持续跟踪新加入和离开的成员,并在所有组成员都退出该群时自行注销。
4.聊天服务器:负责持续跟踪所有的群控制器,当有一个新的成员试图加入一个群组时,聊天服务器才会工作,无论是有一个,还是有多个群组控制器,聊天服务器都是单进程的。
5.中间人:负责管理系统之间的数据传输,它屏蔽了两台机器之间的底层套接字接口。使我们无须关心底层的通信基础结构的细节问题。


聊天客户端:

-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").

//创建handler/5的并行进程。
connect(Host, Port, HostPsw, Group, Nick) ->
spawn(fun() -> handler(Host, Port, HostPsw, Group, Nick) end).

//把自己转换为系统进程
//创建一个输入输出窗口组件,并设置
//创建连接进程(这个进程会尝试连接服务器)
//最后在disconnected/2函数内等待一个连接事件
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).


//等待接收一个从服务器返回的{connected,MM}的响应。
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(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.


//定期的尝试与IRC服务器连接,try_to_connect不断循环。每两秒尝试连接一下服务器,如//果无法连接,就发送一个状态消息给聊天客户端
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.

//让程序等待T毫秒
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 >").


lin_chan的配置:

{port, 2223}.
{service, chat, password,"AsDT67aQ",mfa,mod_chat_controller,start,[]}.


聊天控制器:

//只接收两种消息,当客户端连上来时,它会收到一个消息,然后把它转发给聊天服务器,
//与之相反的情况是,如果会话因为某种原因而终止,它会收到一个退出消息,然后通知聊//天服务器客户端已经退出
-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_server,并且在内部启动lib_chan
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)).

//等待一个{login,Group,Nick}的消息,这个消息来自通道为PID的中间人,如果存在与这个//群组对应的群组控制器,那么就想群组控制器发送登录消息,否则,开启一个新的群组控制//器。
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(_, []) -> [].


聊天群组(群组管理器):

-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}.


//参数为L的列表,元素形如{Pid,Nick},存放了用户昵称和中间的Pid.
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(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]).

//创建一个新的输入输出窗口,返回一个可以用于与窗口组件进行交互的PID,
//当用户在输入框输入一条消息时,运行这个函数的进程就会收到形如{PID,State,Parse}的//消息,State是一个可以由用户设置的状态变量,Parse是输入的字符串用用户定义的解析//器解析之后的结果。
start(Pid) ->
gs:start(),
spawn_link(fun() -> widget(Pid) end).

get_state(Pid) -> rpc(Pid, get_state).

//设置窗体组件的标题
set_title(Pid, Str) -> Pid ! {title, Str}.

//设置窗体组件的解析器为Fun.当用户在窗口中输入一行字符串时,窗口组件会发送//{PID,State,Parse}这条消息,Parse是经过窗体组件的解析器解析之后的字符串。当用户//关闭组件时,在窗口组件摧毁之间会发送{Pid,destoryed}这条消息.
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,120,100},
{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 Satte=~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("Read:~p~n",[Text]),
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}.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值