Erlang:连接到服务器,并在同一套接字上接收输入

时间:2018-12-15 10:49:50

标签: sockets functional-programming erlang erl

我正在建立与本地主机的套接字连接。我想在此连接(或任何连接)上从服务器接收回输入,但是使用相同的连接似乎是最简单的解决方案。因为几个客户端将能够连接到我的服务器,所以我创建了一个登录功能,该功能将产生一个线程来侦听服务器发送的消息。

当我输入命令

c(erlSoc).    
erlSoc:logon("Jacob").

我收到错误消息

[C] error {error,einval}

我知道这一行

E ->
                io:format("[C] error ~p~n", [E])

正在打印它,但是我需要更改什么,以便不会发生此错误,并且能够从服务器接收消息。


erlSoc.erl的主要兴趣点

logon(Uname) ->
    {ok, Sock} = gen_tcp:connect("localhost", 5300, [binary, {packet, 0}]),
    spawn(erlSoc, client_receive, [Sock]),
    io:format("Create a Room: create~nList Rooms: list~nJoin Rooms: join~n Leave Rooms: leave~nSend a message: message ~n"),
    client(Sock, Uname).

client_receive(Sock) ->
    case gen_tcp:recv(Sock, 0) of
       {ok, Data} ->
            io:format("[C]  ~p~n", [Data]);
        {error, closed} ->
            io:format("[C] closed~n", []);
        E ->
            io:format("[C] error ~p~n", [E])
    end.

erlSoc.erl

-module(erlSoc).
-export([start_server/0, logon/1, remove/2, server/1, client_receive/1]).
-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).

listen() ->
    {ok, LSocket} = gen_tcp:listen(5300, ?TCP_OPTIONS),
    io:format("Accepted the socket connection ~n"),
    accept(LSocket).

accept(LSocket) ->
    {ok, CSocket} = gen_tcp:accept(LSocket),
    Ref = make_ref(),
    To = spawn(fun() -> init(Ref, CSocket) end),
    gen_tcp:controlling_process(CSocket, To),
    To ! {handoff, Ref, CSocket},
    io:format("Second Test ~n"),
    accept(LSocket).

init(Ref, Socket) ->
    receive
        {handoff, Ref, Socket} ->
            {ok, Peername} = inet:peername(Socket),
        io:format("[S] peername ~p~n", [Peername]),
            loop(Socket)
    end.

loop(Socket) ->
    case gen_tcp:recv(Socket, 0) of
        {ok, Data} ->
        List = binary_to_list(Data),
        List2 = string:tokens(List,","),
            io:format("[S] got ~p~n", [List2]),
            parse_data(Socket, List2),
            loop(Socket);
        {error, closed} ->
            io:format("[S] closed~n", []);
        E ->
            io:format("[S] error ~p~n", [E])
    end.

server_node() ->
    erlSoc@localhost.

parse_data(Soc, [Task, Rname | Message]) -> 
    if 
    Task =:= "message" ->
        {erlSoc, self()} ! {message, Rname, Message};
    Task =:= "create" ->
        {erlSoc, server_node()} ! {create, Rname, Soc};
    Task =:= "list" ->
        {erlSoc, self()} ! {list, Soc};
    Task =:= "join" ->
        {erlSoc, self()} ! {join, Rname, Soc};
    Task =:= "leave" ->
        {erlSoc, self()} ! {leave, Rname, Soc}
    end.

start_server() ->
    spawn(erlSoc, server, [[[]]]),
    listen().

remove(X, L) ->
    [Y || Y <- L, Y =/= X].

server(RoomList) ->
    receive
        {message, Rname, Message} ->
            [_|[Users|Messages]] = lists:keyfind(Rname, 1, RoomList),
            RoomUpdate = lists:keyreplace(Rname, 1, RoomList, [Rname|[Users|[Messages|Message]]]),
            send_message(Message, Rname, Users),
            server(RoomUpdate);
        {create, Rname, Soc} ->
            server([[Rname|[Soc|[]]]|RoomList]);
        {list, Soc} ->
            {Rooms, _} = RoomList,
            gen_tcp:send(Soc,Rooms),
            server(RoomList);
        {join, Rname, User} ->
            [Room|[Users|Messages]] = lists:keyfind(Rname, 1, RoomList),
            RoomUpdate = lists:keyreplace(Rname, 1, RoomList, [Rname|[[Users|User]|[Messages]]]),
            gen_tcp:send(User,[Rname,Messages]),
            [Room|[Users|Messages]] = lists:keyfind(Rname, 1, RoomUpdate),
            io:format("Users in ~p : ~p~n", [Rname, Users]),
            server(RoomList);
        {leave, Rname, User} ->
            [Room|[Users|Messages]] = lists:keyfind(Rname, 1, RoomList),
            NewU = remove(User, Users),
            RoomUpdate = lists:keyreplace(Rname, 1, RoomList, [Rname|[[NewU]|[Messages]]]),
            [Room|[Users|Messages]] = lists:keyfind(Rname, 1, RoomUpdate),
            io:format("Users in ~p : ~p~n", [Rname, Users]),
            server(RoomUpdate)
    end.


send_message(Message, ChatRoom, []) ->
    void;
send_message(Message, Chatroom, [To|Users]) ->
    gen_tcp:send(To,"Message From Chatroom "++Chatroom++": "++Message),
    send_message(Message, Chatroom, Users).


logon(Uname) ->
    {ok, Sock} = gen_tcp:connect("localhost", 5300, [binary, {packet, 0}]),
    spawn(erlSoc, client_receive, [Sock]),
    io:format("Create a Room: create~nList Rooms: list~nJoin Rooms: join~n Leave Rooms: leave~nSend a message: message ~n"),
    client(Sock, Uname).

client_receive(Sock) ->
    case gen_tcp:recv(Sock, 0) of
       {ok, Data} ->
            io:format("[C]  ~p~n", [Data]);
        {error, closed} ->
            io:format("[C] closed~n", []);
        E ->
            io:format("[C] error ~p~n", [E])
    end.

client(Sock, Uname) ->
    {ok,[Task]} = io:fread("Task? : ", "~s"),
    if
    Task =:= "message" ->
        {ok, [Rname]} = io:fread("Send the message to which room? : ", "~s"),
        Message = io:get_line("Type your message: "),
        ok = gen_tcp:send(Sock,"message,"++Rname++","++Uname++": "++Message);
    Task =:= "create" ->
        {ok, [Rname]} = io:fread("Enter a room name : ", "~s"),
        ok = gen_tcp:send(Sock, "create,"++Rname);
    Task =:= "list" ->
        ok = gen_tcp:send(Sock, "list");
    Task =:= "join" ->
        {ok, [Rname]} = io:fread("Leave Which Room? : ", "~s"),
        ok = gen_tcp:send(Sock, "leave,"++Rname);
    Task =:= "leave" ->
        {ok, [Rname]} = io:fread("Join Which Room? : ", "~s"),
        ok = gen_tcp:send(Sock, "join,"++Rname);
    Task =:= "help" ->
        io:format("Create a Room: create~nList Rooms: list~nJoin Rooms: join~n Leave Rooms: leave~nSend a message: message ~n");
    Task =:= "exit" ->
        gen_tcp:close(Sock)
    end.

更新

-module(erlSoc).
-export([start_server/0, logon/1, remove/2, server/1, client_receive/1, client/2]).
-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).

listen() ->
    {ok, LSocket} = gen_tcp:listen(5300, ?TCP_OPTIONS),
    io:format("Accepted the socket connection ~n"),
    accept(LSocket).

accept(LSocket) ->
    {ok, CSocket} = gen_tcp:accept(LSocket),
    Ref = make_ref(),
    To = spawn(fun() -> init(Ref, CSocket) end),
    gen_tcp:controlling_process(CSocket, To),
    To ! {handoff, Ref, CSocket},
    io:format("Second Test ~n"),
    accept(LSocket).

init(Ref, Socket) ->
    receive
        {handoff, Ref, Socket} ->
            {ok, Peername} = inet:peername(Socket),
        io:format("[S] peername ~p~n", [Peername]),
            loop(Socket)
    end.

loop(Socket) ->
    case gen_tcp:recv(Socket, 0) of
        {ok, Data} ->
        List = binary_to_list(Data),
        List2 = string:tokens(List,","),
            io:format("[S] got ~p~n", [List2]),
            parse_data(Socket, List2),
            loop(Socket);
        {error, closed} ->
            io:format("[S] closed~n", []);
        E ->
            io:format("[S] error ~p~n", [E])
    end.

server_node() ->
    erlSoc@localhost.

parse_data(Soc, [Task, Rname | Message]) -> 
    case Task of 
    "message" ->
        {erlSoc, server_node()} ! {message, Rname, Message};
    "create" ->
        io:format("[C]  ~p~n", [Rname]),
        {erlSoc, server_node()} ! {create, Rname, Soc};
    "list" ->
        {erlSoc, server_node()} ! {list, Soc};
    "join" ->
        {erlSoc, server_node()} ! {join, Rname, Soc};
    "leave" ->
        {erlSoc, server_node()} ! {leave, Rname, Soc}
    end.

start_server() ->
    spawn(erlSoc, server, [[[]]]),
    listen().

remove(X, L) ->
    [Y || Y <- L, Y =/= X].

server(RoomList) ->
    receive
        {message, Rname, Message} ->
            [_|[Users|Messages]] = lists:keyfind(Rname, 1, RoomList),
            RoomUpdate = lists:keyreplace(Rname, 1, RoomList, [Rname|[Users|[Messages|Message]]]),
            send_message(Message, Rname, Users),
            server(RoomUpdate);
        {create, Rname, Soc} ->
            server([[Rname|[Soc|[]]]|RoomList]),
            io:format("[C]  ~p~n", [Rname]);
        {list, Soc} ->
            {Rooms, _} = RoomList,
            gen_tcp:send(Soc,Rooms),
            server(RoomList);
        {join, Rname, User} ->
            [Room|[Users|Messages]] = lists:keyfind(Rname, 1, RoomList),
            RoomUpdate = lists:keyreplace(Rname, 1, RoomList, [Rname|[[Users|User]|[Messages]]]),
            gen_tcp:send(User,[Rname,Messages]),
            [Room|[Users|Messages]] = lists:keyfind(Rname, 1, RoomUpdate),
            io:format("Users in ~p : ~p~n", [Rname, Users]),
            server(RoomList);
        {leave, Rname, User} ->
            [Room|[Users|Messages]] = lists:keyfind(Rname, 1, RoomList),
            NewU = remove(User, Users),
            RoomUpdate = lists:keyreplace(Rname, 1, RoomList, [Rname|[[NewU]|[Messages]]]),
            [Room|[Users|Messages]] = lists:keyfind(Rname, 1, RoomUpdate),
            io:format("Users in ~p : ~p~n", [Rname, Users]),
            server(RoomUpdate)
    end.


send_message(Message, ChatRoom, []) ->
    void;
send_message(Message, Chatroom, [To|Users]) ->
    gen_tcp:send(To,"Message From Chatroom "++Chatroom++": "++Message),
    send_message(Message, Chatroom, Users).


logon(Uname) ->
    {ok, Sock} = gen_tcp:connect("localhost", 5300, [binary, {packet, 0}, {active, false}]),
    Merp = spawn(erlSoc, client_receive, [Sock]),
    gen_tcp:controlling_process(Sock, Merp),
    io:format("Create a Room: create~nList Rooms: list~nJoin Rooms: join~n Leave Rooms: leave~nSend a message: message ~n"),
    Raph = spawn(erlSoc, client, [Sock, Uname]),
    gen_tcp:controlling_process(Sock, Raph).

client_receive(Sock) ->
    case gen_tcp:recv(Sock, 0) of
       {ok, Data} ->
            io:format("[C]  ~p~n", [Data]),
            client_receive(Sock);
        {error, closed} ->
            io:format("[C] closed~n", []);
        E ->
            io:format("[C] error ~p~n", [E])
    end.

client(Sock, Uname) ->
    {ok,[Task]} = io:fread("Task? : ", "~s"),
    if
    Task =:= "message" ->
        {ok, [Rname]} = io:fread("Send the message to which room? : ", "~s"),
        Message = io:get_line("Type your message: "),
        ok = gen_tcp:send(Sock,"message,"++Rname++","++Uname++": "++Message);
    Task =:= "create" ->
        {ok, [Rname]} = io:fread("Enter a room name : ", "~s"),
        ok = gen_tcp:send(Sock, "create,"++Rname);
    Task =:= "list" ->
        ok = gen_tcp:send(Sock, "list");
    Task =:= "join" ->
        {ok, [Rname]} = io:fread("Leave Which Room? : ", "~s"),
        ok = gen_tcp:send(Sock, "leave,"++Rname);
    Task =:= "leave" ->
        {ok, [Rname]} = io:fread("Join Which Room? : ", "~s"),
        ok = gen_tcp:send(Sock, "join,"++Rname);
    Task =:= "help" ->
        io:format("Create a Room: create~nList Rooms: list~nJoin Rooms: join~n Leave Rooms: leave~nSend a message: message ~n");
    Task =:= "exit" ->
        gen_tcp:close(Sock)
    end,
    client(Sock,Uname).

1 个答案:

答案 0 :(得分:2)

自从您标记了functional-programming后,我将不仅仅向您介绍我在代码中发现的错误,还可以告诉您更多的信息。

我已经在您的代码中添加了一些注释:


erlSoc.erl

-module(erlSoc).
-export([start_server/0, logon/1, remove/2, server/1, client_receive/1]).
-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).

% ...

loop(Socket) ->
    % Remember that in server, after accepting a connection, 
    % you are waiting for packet
    case gen_tcp:recv(Socket, 0) of
        {ok, Data} ->

% ...

parse_data(Soc, [Task, Rname | Message]) -> 
if 
Task =:= "message" ->
    % below code does not work. If you want to send a message to a process,
    % you can use its process id (pid) or its registered name or name of the
    % node which that process belongs to with its pid
    % Based server/1 function, I think that you want to have a process named
    % erlSoc and send message to it. If yes, then you have to register that 
    % process for that name using erlang:register/2 and just use:
    % erlSoc ! YourMessage
    {erlSoc, self()} ! {message, Rname, Message};
% ...
Task =:= "list" ->
    % It seems that you are sending a socket to your erlSoc process and you
    % did not make erlSoc controller of socket !
    {erlSoc, self()} ! {list, Soc};

logon(Uname) ->
    % In below, Your socket will be in 'active' mode and in client_receive/2 function
    % you have called gen_tcp:recv/2 on socket which is invalid (einval)
    {ok, Sock} = gen_tcp:connect("localhost", 5300, [binary, {packet, 0}]),

    % In below, You are passing Sock to a new process which is not owner of socket
    % You have to call gen_tcp:controlling_process/2 here
    spawn(erlSoc, client_receive, [Sock]),
    io:format("Create a Room: create~nList Rooms: list~nJoin Rooms: join~n Leave Rooms: leave~nSend a message: message ~n"),
    % Also here you need to call gen_tcp:controlling_process/2 in that process to
    % become owner of socket in this process
    client(Sock, Uname).

client_receive(Sock) ->
    % As I mentioned, In server you were waiting for a packet, Also here in
    % client you are waiting for a packet ! So nothing will happen
    % If you want to use gen_tcp:recv, I recommend to use it with third 
    % argument which is timeout
    case gen_tcp:recv(Sock, 0) of
       {ok, Data} ->
            io:format("[C]  ~p~n", [Data]);
        {error, closed} ->
            io:format("[C] closed~n", []);
        E ->
            io:format("[C] error ~p~n", [E])
    end.

% ...

我建议重写部分代码。最好将客户端和服务器代码分开。我也建议正确命名事物,在Erlang中,模块和函数名称均应小写。推荐。您可以使用if来代替case,例如:

if 
Task =:= "message" ->
    {erlSoc, self()} ! {message, Rname, Message};
Task =:= "create" ->
    {erlSoc, server_node()} ! {create, Rname, Soc};
Task =:= "list" ->
    {erlSoc, self()} ! {list, Soc};
Task =:= "join" ->
    {erlSoc, self()} ! {join, Rname, Soc};
Task =:= "leave" ->
    {erlSoc, self()} ! {leave, Rname, Soc}
end.

可以是:

case Task of 
    "message" ->
        {erlSoc, self()} ! {message, Rname, Message};
    "create" ->
        {erlSoc, server_node()} ! {create, Rname, Soc};
    "list" ->
        {erlSoc, self()} ! {list, Soc};
    "join" ->
        {erlSoc, self()} ! {join, Rname, Soc};
    "leave" ->
        {erlSoc, self()} ! {leave, Rname, Soc}
end.