您的位置:首页 > 其它

一个自定义通讯协议的erlang socket多人在线聊天程序

2017-03-28 23:36 393 查看
利用自定义的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>>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: