您的位置:首页 > 编程语言 > PHP开发

erlang简易聊天室基于OTP(gen_server,gen_fsm)

2017-09-27 16:13 423 查看
学习erlang一周多了,尝试自己做了一个小练习,聊天室包括客户端、服务器以及一个定时器(gen_fsm)。 
功能:登录、发消息、登出、统计登陆次数、聊天次数、最后一次登录时间以及在线人数,定时器会在整点把在线人数上传到mysql. 
hrl文件和mysql连接文件这里就不列出了。 
希望对初学者学习erlang otp有帮助。 

%%%-----------------------------------
%%% @Module      : chat_client1
%%% @Author      : zyh
%%% @Created     : 2017.09.26
%%% @Description : 客户端
%%%-----------------------------------
-module(chat_client1).
-export([get_socket/0,client_login/1,send_message/1,logout/1]).
-import(chat_binary1,[pack/1,unpack/1,unpack_cmd/1,unpack_string/1]).

%获取socket
get_socket() ->
Pid = spawn(fun() ->  {ok,Socket} = gen_tcp:connect("localhost",2345,[binary,{packet,4}]),handle(Socket) end),
register(client,Pid).
%%gen_tcp:controlling_process(Socket,Pid).

%登录接口
client_login({Name,Password}) ->
client ! {self(),{login,Name,Password}}.

%聊天发送接口
send_message({Name,Msg}) ->
client ! {self(),{msg,Name,Msg}}.

%退出接口
logout(Name) ->
client ! {self(),{logout,Name}}.

handle(Socket) ->
io:format("a~p",[Socket]),
%%接受服务系消息
receive
{tcp,Socket,Bin} ->
{Cmd,B0}=chat_binary1:unpack_cmd(Bin),
case Cmd of
1001->
{S,B1} = chat_binary1:unpack_string(B0),
case S of
1 ->
io:format("you have login successfully ~n");
0 ->
io:format("you haved login failed,please try again ~n");
_->
gen_tcp:close(Socket)
end,
handle(Socket);
1002->
{S,B1} = chat_binary1:unpack_string(B0),
case S of
1 ->
io:format("you can send next message ~n");
0 ->
io:format("send message failed,please try again ~n");
_->
gen_tcp:close(Socket)
end,
handle(Socket);
1003->
{S,B1} = chat_binary1:unpack_string(B0),
case S of
1 ->
io:format("you have loginout successfully ~n");
0 ->
io:format("you haved loginout failed,please try again ~n");
_->
gen_tcp:close(Socket)
end,
handle(Socket)
end;

%%发送消息给服务器
{From,Request} ->
case Request of

{login,Name,Password} ->
N = term_to_binary(Name),
P = term_to_binary(Password),
Packet= <<1001:32, (byte_size(N)):16, N/binary, (byte_size(P)):16, P/binary>>,
gen_tcp:send(Socket,Packet),
handle(Socket);

{msg,Name,Msg} ->
io:format("my message:~p~n",[Msg]),
N = term_to_binary(Name),
M = term_to_binary(Msg),
Packet = <<1002:32,(byte_size(N)):16,N/binary,(byte_size(M)):16,M/binary>>,
gen_tcp:send(Socket,Packet),
handle(Socket);

{logout,Name} ->
N = term_to_binary(Name),
Packet = <<1003:32,(byte_size(N)):16,N/binary>>,
gen_tcp:send(Socket,Packet),
handle(Socket);
A->
io:format("aaaaaaaa"),
io:format("~p",[A])
end
end.


%%%-----------------------------------
%%% @Module : chat_server1
%%% @Author : zyh
%%% @Created : 2017.09.26
%%% @Description : OTP gen_server服务器端
%%%-----------------------------------

-module(chat_server1).
-behaviour(gen_server).
-compile([debug_info,export_all]).
-define (SERVER,?MODULE).
-include("userinfo.hrl").
-include("userinfo1.hrl").
-export([loop/1]).
-import(chat_binary1,[pack/1,unpack/1,unpack_cmd/1,unpack_string/1]).

start()->gen_server:start_link({local,?SERVER},?MODULE,[],[]).
stop()->gen_server:call(?MODULE,stop).

set_info(User,Name)->gen_server:cast(?MODULE,{set_info,User,Name}).
get_info(Name)->gen_server:call(?MODULE,{get_info,Name}).
get_time()->gen_server:call(?MODULE,get_time).
get_list()->gen_server:call(?MODULE,get_list).

%%存/更新登陆信息到State
handle_cast({set_info,User,Name},State) ->
State1=lists:keystore(Name,1,State,{Name,User}),
{noreply,State1}.

%%取登陆信息到State
handle_call({get_info,Name},_From,State) ->
Sql=lists:keyfind(Name,1,State),
{reply,Sql,State};

%%取整个数组
handle_call(get_list,_From,State) ->
State2=lists:foldl(fun(X={Q,W},B) ->[W|B] end, [], State),
{reply,State2,State};

%%取时间字符串
handle_call(get_time,_From,State) ->
{{Year, Month, Day}, {Hour, Minite, Second}} = calendar:local_time(),
PP=lists:concat([Year,"-",Month,"-",Day," ",Hour,":",Minite,":",Second]),
{reply,PP,State};

handle_call(stop,_From,Tab)->
{stop,normal,stopped,Tab};

%%容错
handle_call(_,_From,State) ->
{reply,[],State}.

%%统计在线人数
count(List)->count(List,0).
count([],N)-> N;
count([H|T],N)->
#user1{sta=A} = H,
case A =:= 1 of
true->
count(T,N+1);
false->
count(T,N)
end.

%%初始化
init([])->
initialize_ets(),
start_parallel_server(),
{ok,[]}.

%开启服务器
start_parallel_server() ->
{ok,Listen} = gen_tcp:listen(2345,[binary,{packet,4}]),
io:format("b~p",[Listen]),
spawn(fun() -> per_connect(Listen) end).

%每次绑定一个当前Socket后再分裂一个新的服务端进程,再接收新的请求
per_connect(Listen) ->
{ok,Socket} = gen_tcp:accept(Listen),
spawn(fun() -> per_connect(Listen) end),
io:format("c~p",[Socket]),
loop(Socket).%%启动主程序

%%添加初始的账号信息
initialize_ets() ->
ets:new(test,[set,public,named_table,{keypos,#user.name}]),
ets:insert(test,#user{id=01,name=admin1,passwd=11111}),
ets:insert(test,#user{id=02,name=admin2,passwd=22222}),
ets:insert(test,#user{id=03,name=admin3,passwd=33333}).

%查询ets
info_lookup(Key) ->
%返回值是一个元组
ets:lookup(test,Key).

%修改ets信息
info_update(Key,Pos,Update) ->
ets:update_element(test,Key,{Pos,Update}).

%%主程序
loop(Socket) ->
io:format("receiving...~n"),
receive
%%_->
%%io:format("666666666666666666 ~n");

{tcp,Socket,Bin} ->
{Cmd,B0}=chat_binary1:unpack_cmd(Bin),
case Cmd of
%%接受登录请求
1001->
{Name,B1} = chat_binary1:unpack_string(B0),
{Pwd,B2} = chat_binary1:unpack_string(B1),
Password1=ets:lookup_element(test,Name,4),
if
Pwd==Password1 ->
S = term_to_binary(1),
N = term_to_binary(Name),
Packet= <<1001:32, (byte_size(S)):16, S/binary, (byte_size(N)):16, N/binary>>,
gen_tcp:send(Socket,Packet),
User1=get_info(Name),
%%User1可能是两种值,false或者是{Name,User}格式的结果,分不同情况处理
%%用户未登录过
case User1 of
false ->
Userroot=#user1{login_times=1,chat_times=0,last_login=0,sta=1},
set_info(Userroot,Name),
Reply1=newlogin;
%%用户登陆过
_->
{U,Userf}=User1,
#user1{login_times=Login_times,last_login=Last_login}=Userf,
User2=Userf#user1{login_times=Login_times+1,sta=1},
set_info(User2,Name),
Reply1=Last_login
end,
Reply=length(get_list()),
io:format("~p users online. ~n",[Reply]),
io:format("user ~p lastlogin is :~p.~n",[Name,Reply1]), %%Reply1是上一次登陆时间
loop(Socket);
%%密码错误时返回错误信息
true-> io:format("you passwd mistake"),
F = term_to_binary(0),
N = term_to_binary(Name),
Packet = <<1001:32,(byte_size(F)):16,F/binary,(byte_size(N)):16,N/binary>>,
gen_tcp:send(Socket,Packet),
loop(Socket)
end;
%%接受消息发送请求
1002->
{Name,B1} = chat_binary1:unpack_string(B0),
{Msg,B2} = chat_binary1:unpack_string(B1),
{U,User1}=get_info(Name),
#user1{chat_times=Chat_times,last_login=Time,sta=Sta}=User1,
if
%%判断用户是否登录
Sta==1 ->
S = term_to_binary(1),
N = term_to_binary(Name),
Packet= <<1002:32, (byte_size(S)):16, S/binary, (byte_size(N)):16, N/binary>>,
gen_tcp:send(Socket,Packet),

Time1=get_time(),
User2=User1#user1{chat_times=Chat_times+1},
set_info(User2,Name),
%%User=info_lookup(Name),
%%[#user{login_times=W}]=User, %%提取记录字段方法
%%ets:update_element(test,Name,{#user.chat_times,W+1}),
io:format("~nUser ~p :~p.",[Name,Msg]),
io:format("~nUser ~p chat ~p.~n",[Name,Chat_times]),
loop(Socket);
%%未登录则返回失败消息
true->
io:format("you send msg failed,because there is no login"),
F = term_to_binary(0),
N = term_to_binary(Name),
Packet = <<1002:32,(byte_size(F)):16,F/binary,(byte_size(N)):16,N/binary>>,
gen_tcp:send(Socket,Packet),
loop(Socket)
end;
%%接受登出请求
1003 ->
{Name,B1} = chat_binary1:unpack_string(B0),
{U,User1}=get_info(Name),
#user1{sta=Sta,login_times=Login_times}=User1,
if
%%判断用户是否登录
Sta==1 ->
S = term_to_binary(1),
N = term_to_binary(Name),
Packet= <<1003:32, (byte_size(S)):16, S/binary, (byte_size(N)):16, N/binary>>,
gen_tcp:send(Socket,Packet),

Time1=get_time(),
User2=User1#user1{last_login=Time1,sta=0},
set_info(User2,Name),
%%ets:update_element(test,Name,{#user.sta,1}),
Reply=count(get_list()),

io:format("user ~p login ~p.",[Name,Login_times]),
io:format("~n~p users online ~n",[Reply]),
io:format("user ~p have loginout~n",[Name]),
loop(Socket);
%%未登录则返回失败消息
true->
io:format("failed,your account is out of line"),
F = term_to_binary(0),
N = term_to_binary(Name),
Packet = <<1003:32,(byte_size(F)):16,F/binary,(byte_size(N)):16,N/binary>>,
gen_tcp:send(Socket,Packet),
loop(Socket)
end;
%%接受定时器请求,返回在线人数
1004 ->
Reply=count(get_list()),
S=term_to_binary(Reply),
N=term_to_binary(0),
Packet= <<1004:32, (byte_size(S)):16, S/binary, (byte_size(N)):16, N/binary>>,
gen_tcp:send(Socket,Packet);
_->
ok
end;
{tcp_closed,Socket} ->
io:format("Server socket closed~n")
end.

handle_info(_Info,State)->{noreply,State}.
terminate(_Reason,_State)->ok.
code_change(_OldVsn,State,_Extra)->{ok,State}.
%%%-----------------------------------
%%% @Module      : code_lock
%%% @Author      : zyh
%%% @Created     : 2017.09.26
%%% @Description : 定时器
%%%-----------------------------------
-module(code_lock).
-behaviour(gen_fsm).
-compile([debug_info,export_all]).
-define(SERVER, ?MODULE).
-import(chat_server1,[set_sql/0]).

start_link() ->
gen_fsm:start_link({local, code_lock}, code_lock, [], []).

init([]) ->
{ok, gettime,[]}.

%%提交开启定时器事件
open_timer() ->
gen_fsm:send_event(?SERVER,[]).

%%获取系统时间,判断是不是整点
gettime([],[]) ->
{D,E,F,A,B,C}=gettime(),
if
(C=:=0)->
{next_state, open,[],1000};
true ->
io:format("D|"),
{next_state, gettime,[],1000}
end;

%%不是整点则继续获取系统事件判断,间隔为一秒一次
gettime(timeout,[]) ->
{D,E,F,A,B,C}=gettime(),
if
(C=:=0)->
{next_state, open,[],1000};
true ->
io:format("D|"),
{next_state, gettime,[],1000}
end.

%%是整点则向服务器发送请求,获得在线人数,再写入sql中
open(timeout,[]) ->
io:format("Z|"),
chat_server1:set_sql(),
{next_state, open,[],60000}.

code_change(_OldVsn, StateName, Data, _Extra) ->
{ok, StateName, Data}.

terminate(normal, _StateName, _Da
9921
ta) ->
ok.

handle_event(Event, StateName, Data) ->
io:format("handle_event... ~n"),
unexpected(Event, StateName),
{next_state, StateName, Data}.

handle_sync_event(Event, From, StateName, Data) ->
io:format("handle_sync_event, for process: ~p... ~n", [From]),
unexpected(Event, StateName),
{next_state, StateName, Data}.

handle_info(Info, StateName, Data) ->
io:format("handle_info...~n"),
unexpected(Info, StateName),
{next_state, StateName, Data}.

%% Unexpected allows to log unexpected messages
unexpected(Msg, State) ->
io:format("~p RECEIVED UNKNOWN EVENT: ~p, while FSM process in state: ~p~n",
[self(), Msg, State]).
%%
%% actions
gettime()->
{{Year, Month, Day}, {Hour, Minite, Second}} = calendar:local_time(),
{Year,Month,Day,Hour,Minite,Second}.
%%%-----------------------------------
%%% @Module      : chat_binary1
%%% @Author      : zyh
%%% @Created     : 2017.09.26
%%% @Description : 数据传输封包解包
%%%-----------------------------------
-module(chat_binary1).
-compile([debug_info,export_all]).

%%登录打包
pack(1001,{Name,Pwd})->
N = term_to_binary(Name),
P = term_to_binary(Pwd),
<<1001:32, (byte_size(N)):16, N/binary, (byte_size(P)):16, P/binary>>;

%%退出登录打包
pack(1002,{Name})->
N = term_to_binary(Name),
<<1002:32, (byte_size(N)):16, N/binary>>;
%%发送消息打包
pack(1003,{Name,Message})->
N = term_to_binary(Name),
M = term_to_binary(Message),
<<1003:32, (byte_size(N)):16, N/binary, (byte_size(M)):16, M/binary>>;
%%断开Socket连接打包
pack(1004,{Q})->
N = term_to_binary(Q),
<<1004:32, (byte_size(N)):16, N/binary>>;
%%服务器响应客户端的错误提醒
pack(1005,{Msg})->
N = term_to_binary(Msg),
<<1005:32, (byte_size(N)):16, N/binary>>;
%%容错命令打包
pack(N,Q)->
N = term_to_binary(Q),
<<N:32, (byte_size(N)):16, N/binary>>.

%%解包协议号
unpack_cmd(P)->
<<Cmd:32,B0/binary>> = P,
{Cmd,B0}.

%%解包字符串
unpack_string(P)->
case byte_size(P)>=1 of
true->
<<Len:16,B1/binary>> = P,
<<String:Len/binary,B2/binary>> = B1,
{binary_to_term(String),B2};
false->
ok
end.

%%解包整个数据包--暂时用不上
unpack(P)->
<<Cmd:32,B0/binary>> = P,
case Cmd of
1001->
{Name,B1} = unpack_string(B0),
{Pwd,_B2} = unpack_string(B1),
{Cmd,Name,Pwd};
1002->
{Name,_B1} = unpack_string(B0),
{Cmd,Name};
1003->
{Name,B1} = unpack_string(B0),
{Message,_B2} = unpack_string(B1),
{Cmd,Name,Message};
1004->
{Cmd,disconnect};
1005->
{Message,_B1} = unpack_string(B0),
{Cmd,Message};
_->
error

end.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: