一個自定義通訊協議的erlang socket多人線上聊天程式
阿新 • • 發佈:2019-02-11
利用自定義的erlang通訊協議編寫的入門socket程式,可實現簡單的多人線上聊天程式。
- 服務端的程式碼:
-module(server_example).
-export([start/0,initialize_ets/0,info_lookup/1,loop/2,info_update/3,init/1,handle_call/3,terminate/2]).
-import(counter,[start/1,add/1,value/1,decrease/1,log_add/1,chat_add/1]).
-import(my_fsm,[start_count/0,count/1]).
-include("user_info.hrl" ).
-define(SERVER,?MODULE).
start() ->
gen_server:start_link({local,?SERVER},?MODULE,[],[]).
init([]) ->
initialize_ets(),
start_parallel_server(),
{ok,true}.
user_online_count({add,Pid}) ->
gen_server:call(?MODULE,{add,Pid}).
user_online_decrease({decrease,Pid}) ->
gen_server:call(?MODULE,{decrease,Pid}).
personal_login_count({ladd,Name,Count}) ->
gen_server:call(?MODULE,{ladd,Name,Count}).
personal_chat_count({cadd,Name,Count}) ->
gen_server:call(?MODULE,{cadd,Name,Count}).
%統計線上使用者的回撥函式
handle_call({add,Pid},_From,State) ->
counter :add({add,Pid}),
Reply = counter:value(Pid),
{reply,Reply,State};
handle_call({decrease,Pid},_From,State) ->
counter:decrease(Pid),
Reply = counter:value(Pid),
{reply,Reply,State};
%統計個人登入次數
handle_call({ladd,Name,Count},_From,State) ->
Pi = spawn(fun() -> counter:start({ladd,Count}) end),
counter:add({ladd,Pi}),
Reply = counter:log_add({value,Pi}),
info_update(Name,4,Reply),
{reply,Reply,State};
%個人來聊天次數
handle_call({cadd,Name,Count},_From,State) ->
C = spawn(fun() -> counter:start({cadd,Count}) end),
counter:add({cadd,C}),
Reply = counter:chat_add({value,C}),
info_update(Name,5,Reply),
{reply,Reply,State}.
terminate(_Reason,_State) -> ok.
%開啟伺服器
start_parallel_server() ->
{ok,Listen} = gen_tcp:listen(2345,[binary,{packet,4},{reuseaddr,true},{active,true}]),
Pid = spawn(fun() -> counter:start({init,0}) end),%開啟統計程序,此時為0
my_fsm:start_count(),%統計某時段的登入數
spawn(fun() -> per_connect(Pid,Listen) end).
%每次繫結一個當前Socket後再分裂一個新的服務端程序,再接收新的請求
per_connect(Pid,Listen) ->
{ok,Socket} = gen_tcp:accept(Listen),
spawn(fun() -> per_connect(Pid,Listen) end),
loop(Pid,Socket).
%初始化ets
initialize_ets() ->
ets:new(test,[set,public,named_table,{keypos,#user.name}]),
ets:insert(test,#user{id=01,name=laner,passwd="123456",login_times=0,chat_times=0,last_login={}}),
ets:insert(test,#user{id=02,name=river,passwd="23456",login_times=0,chat_times=0,last_login={}}),
ets:insert(test,#user{id=03,name=huang,passwd="3456",login_times=0,chat_times=0,last_login={}}).
%查詢ets
info_lookup(Key) ->
%返回值是一個元組
ets:lookup(test,Key).
%修改ets資訊
info_update(Key,Pos,Update) ->
ets:update_element(test,Key,{Pos,Update}).
%接收資訊並處理
loop(Pid,Socket) ->
io:format("receiving...~n"),
receive
{tcp,Socket,Bin} ->
<<State:4,Str1:2/binary,Str2:2/binary>> = Bin,
%case binary_to_term(Bin) of
case State of
%登入
0000 -> %{Name,Passwd} = binary_to_term(Str),
Name = binary_to_term(Str1),
case info_lookup(Name) of
[{user,Uid,Pname,Pwd,Logc,ChatC,Lastlog}] -> S = term_to_binary(success),
N = term_to_binary(Name),
Packet = <<0000:4,(byte_size(S)):16,S/binary,(byte_size(N)):16,N/binary>>,
gen_tcp:send(Socket,Packet),
my_fsm:count(0),
%取到了名字和密碼之後傳送成功的標識並增加登入次數
Reply = user_online_count({add,Pid}),
io:format("~p users online ~n",[Reply]),
Reply1 = personal_login_count({ladd,Name,Logc}),
io:format("user ~p have logged ~p times ~n",[Name,Reply1]),
loop(Pid,Socket);
%為空表示該使用者沒有記錄
[{}] -> io:format("you haved not registered yet"),
F = term_to_binary(failed),
N = term_to_binary(Name),
Packet = <<0000:4,(byte_size(F)):16,F/binary,(byte_size(N)):16,N/binary>>,
gen_tcp:send(Socket,Packet),
loop(Pid,Socket)
end;
%接收資訊
0001 ->
Name = binary_to_term(Str1),
Msg = binary_to_term(Str2),
[#user{chat_times=Ccount}] = info_lookup(Name),
Reply = personal_chat_count({cadd,Name,Ccount}),
io:format("User ~p :~p~n",[Name,Msg]),
io:format("User ~p have chatted with his friend on line ~p times ~n",[Name,Reply]),
N = term_to_binary({ok,received}),
Len = byte_size(N),
Packet = <<0001:4,Len:16,N/binary>>,
gen_tcp:send(Socket,Packet),
loop(Pid,Socket);
%退出
0002 ->
Name = binary_to_term(Str2),
io:format("~p~n",[info_lookup(Name)]),
[#user{login_times=Logc,last_login=LastLo}] = info_lookup(Name),
Last = calendar:now_to_local_time(erlang:now()),
Reply = user_online_decrease({decrease,Pid}),
N = term_to_binary(ok),
Packet = <<0002:4,(byte_size(N)):16,N/binary>>,
gen_tcp:send(Socket,Packet),
%修改最後登入時間
info_update(Name,6,Last),
io:format("last time ~p logined is ~p~n",[Name,Last]),
io:format("~p users online~n",[Reply])
end;
{tcp_closed,Socket} ->
io:format("Server socket closed~n")
end.
2.客戶端的程式碼
-module(client_example).
-export([get_socket/0,client_login/1,send_message/1,logout/1]).
%獲取socket
get_socket() ->
{ok,Socket} = gen_tcp:connect("localhost",2345,[binary,{packet,4}]),
register(client,spawn(fun() -> handle(Socket) end)).
%登入介面
client_login({Name,Password}) ->
client ! {self(),{login,Name,Password}},
receive
Response -> ok
end.
%聊天傳送介面
send_message({Name,Msg}) ->
client ! {self(),{msg,Name,Msg}},
receive
Response -> ok
end.
%退出介面
logout(Name) ->
client ! {self(),{logout,Name}},
receive
Response -> ok
end.
handle(Socket) ->
receive
%來自控制程序的請求
{From,Request} ->
case Request of
%登入的請求協議號0000
{login,Name,Password} ->
N = term_to_binary(Name),
P = term_to_binary(Password),
Packet = <<0000:4,(byte_size(N)):16,N/binary,(byte_size(P)):16,P/binary>>,
gen_tcp:send(Socket,Packet),
receive
%來自服務端socket響應
{tcp,Socket,Bin} ->
<<State:4,S:2/binary,N:2/binary>> = Bin,
case binary_to_term(S) of
success ->
From ! {"you have login successfully"},
io:format("you have login successfully ~n");
failed ->
From ! {"you haved login failed,please try again"},
gen_tcp:close(Socket)
end
after 5000 ->
ok
end,
handle(Socket);
%傳送資訊協議號0001
{msg,Name,Msg} ->
io:format("my message:~p~n",[Msg]),
N = term_to_binary(Name),
M = term_to_binary(Msg),
Packet = <<0001:4,(byte_size(N)):16,N/binary,(byte_size(M)):16,M/binary>>,
gen_tcp:send(Socket,Packet),
receive
{tcp,Socket,Bin} ->
<<State:4,N:2/binary>> = Bin,
case binary_to_term(N) of
{ok,received} ->
From ! {"ok,you can send next message ~n"}
end
after 3000 ->
ok
end,
handle(Socket);
%登出協議號0002
{logout,Name} ->
L = term_to_binary({logout}),
N = term_to_binary({Name}),
Packet = <<0002:4,(byte_size(L)):16,L/binary,(byte_size(N)):16,N/binary>>,
gen_tcp:send(Socket,Packet),
receive
{tcp,Socket,Bin} ->
<<State:4,N:2/binary>> = Bin,
case binary_to_term(N) of
ok ->
From ! {"ok,you will logout successfully ~n"}
end
after 5000 ->
ok
end,
gen_tcp:close(Socket)
% 關閉socket時只有再次連線新socket才能開啟
end
end.
客戶端與服務端的通訊協議為自定義形式,協議號佔4位,資料部分分為兩部分,一部分為個人姓名,另一部分為資訊,如登入密碼或者聊天資訊,或者登出標誌。如:
封包時的協議:<<0000:4,byte_size(Name):16,Name/binary,byte_size(Msg):16,Msg/binary>>