当我运行我的服务器和客户端时,服务器会收到一个请求,但是当调用protocol / 3方法时,它就会停止。我已经使用调试器进行了检查,它只是在函数调用上,但没有任何反应。目前典型的运行方式如下:
server:start().
S = client:start().
client:authenticate(S, "Anon").
此时它就停止工作了。请注意,我在两个独立的erlang shell上运行服务器和客户端。
服务器代码
start() ->
io:format("Starting Server..."),
Clients = dict:new(),
Database = spawn_link(fun() -> database(Clients)end),
case gen_tcp:listen(1992, [binary, {active, false}]) of
{ok, ListeningSocket} ->
io:format("ok~n"),
accept(ListeningSocket, Database),
{ok, Port} = inet:port(ListeningSocket);
{error, Reason} ->
io:format("Failed: ~s", [Reason]),
{error, Reason}
end.
database(Clients) ->
receive
{get_client, From, User} ->
case dict:find(User, Clients) of
{ok, UserSocket} ->
From ! {id, UserSocket};
error ->
From ! error
end,
database(Clients);
{ins_client, From, Socket, User} ->
case dict:find(User, Clients) of
{ok, _} ->
From ! error;
error ->
Updated = dict:store(User, Socket, Clients),
From ! ok,
database(Updated)
end
end.
accept(ListeningSocket, Database) ->
io:format("Accepting..."),
case gen_tcp:accept(ListeningSocket) of
{ok, Socket} ->
io:format("ok~n"),
Pid = spawn(fun() -> loop(Socket, Database)end),
gen_tcp:controlling_process(Socket, Pid);
{error, Reason} ->
io:format("Failed: ~s", [Reason])
end,
accept(ListeningSocket, Database).
loop(Socket, Database) ->
inet:setopts(Socket, [{active, once}]),
io:format("Waiting for request~n"),
receive
{tcp, Socket, Data} ->
Request = binary_to_term(Data),
protocol(Socket, Request, Database),
loop(Socket, Database);
{tcp_closed, Socket} ->
io:format("Socket ~s closed. ~n", [Socket]),
gen_tcp:close(Socket)
end.
stop(Socket) ->
gen_tcp:close(Socket).
protocol(Socket, Request, Database) ->
case Request of
{message, To, Message} ->
io:format("Message received: for ~s ~s", [To, Message]),
Database ! {get_client, self(), To},
receive
{id, Receiver} ->
gen_tcp:send(Receiver, term_to_binary(Message)),
io:format("Sent to ~s: ~s~n", [Receiver, Message]);
error ->
gen_tcp:send(Socket, term_to_binary("User not found"))
end;
{enter, User} ->
io:format("Request: {Enter: ~s}", User),
Database ! {ins_client, self(), User},
receive
ok ->
gen_tcp:send(Socket, term_to_binary("Authenticated"));
error ->
gen_tcp:send(Socket, term_to_binary("Authentication failed"))
end;
Other ->
io:format("This happened: ~s", [Other])
end.
客户代码
start() ->
io:format("Client starting..."),
case gen_tcp:connect({127,0,0,1}, 1992, [binary, {active, false}]) of
{ok, Socket} ->
io:format("ok.\n"),
spawn(fun() -> loop(Socket)end),
Socket;
{error, Reason} ->
io:format("Failed: ~s", [Reason]),
{error, Reason}
end.
loop(Socket) ->
inet:setopts(Socket, [{active, once}]),
io:format("Waiting for data...~n"),
receive
{tcp_closed, Socket} ->
io:format("Server ended connection.~n"),
disconnect(Socket);
{tcp, Socket, Data} ->
io:format("From Server:' ~s' ~n", [Data])
end.
authenticate(Socket, Username) ->
inet:setopts(Socket, [{active, once}]),
Term = {enter, Username},
Request = term_to_binary(Term),
gen_tcp:send(Socket, Request).
send(Socket, To, Input) ->
inet:setopts(Socket, [{active, once}]),
Message = term_to_binary({message, To, Input}),
gen_tcp:send(Socket, Message).
disconnect(Socket) ->
gen_tcp:close(Socket).
对于任何格式错误,我很抱歉,我仍然很新:)
答案 0 :(得分:2)
挂起的原因是您的数据库进程的ins_client
消息缺少Socket
元素。数据库进程只是忽略了错误的ins_client
消息,将其留在消息队列中。
要解决的其他一些问题:
接受连接后,您的服务器会生成一个接收循环过程,然后将新套接字的控制权转移到该新进程。最好是生成一个新的acceptor并直接调用循环,因为这样就不需要改变新套接字的控制过程了。
accept(ListeningSocket, Database) ->
io:format("Accepting..."),
case gen_tcp:accept(ListeningSocket) of
{ok, Socket} ->
io:format("ok~n"),
spawn(fun() -> accept(ListeningSocket, Database) end),
loop(Socket, Database);
{error, Reason} ->
io:format("Failed: ~s", [Reason]),
error(Reason)
end.
在您的服务器protocol
函数中,您在尝试打印用户的io:format
中出错:User
参数需要在列表中。< / p>
您的protocol
函数是一个带有内部case语句的大函数。它最好写成多个子句,每个条款对应一个预期的消息:
protocol(Socket, {message, To, Message}, Database) ->
io:format("Message received: for ~s ~s", [To, Message]),
...;
protocol(Socket, {enter, User}, Database) ->
io:format("Request: {Enter: ~s}", [User]),
...;
protocol(_Socket, Other, _Database) ->
io:format("This happened: ~s", [Other]).
您客户的loop
函数是奇数,因为它不是循环。它更像是一个简单的接收功能,所以你可能想要重命名它。
从loop
函数调用start
是没有意义的,因为新生成的进程不是套接字的控制进程,因此它永远不会收到任何内容。
您应该使loop
子句处理{tcp, Socket, Data}
返回Data
,因此其调用者可以处理返回的数据。
如果您的客户的start
函数返回{ok, Socket}
成功而不仅仅是Socket
,那会更好,因为这样可以让调用者更容易区分成功和失败