1. 程式人生 > >一個自定義通訊協議的erlang socket多人線上聊天程式

一個自定義通訊協議的erlang socket多人線上聊天程式

利用自定義的erlang通訊協議編寫的入門socket程式,可實現簡單的多人線上聊天程式。

  1. 服務端的程式碼:
-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>>