Erlang gen_tcp закрывает порт, который ошибается GCDAsynchSocket
Я застрял с проблемой. У меня есть iOS-клиент и tcp-сервер на Erlang/OTP. Клиент предполагает отправлять и получать сообщения на сервер через GCDAsynchSocket. Это работает довольно хорошо, если мне нужно отправить сообщение, но не как получатель, потому что клиент должен вызвать этот метод делегата:
/**
* Called when a socket has completed reading the requested data into memory.
* Not called if there is an error.
**/
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag;
проблема в том, что сервер закрывает соединение, которое вызывает другой метод делегата (- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err;
). Как завершить чтение данных с сервера в клиенте и как оставить рабочие соединения на сервере, пока клиент не будет отключен сам? Клиент iOS получает байты с сервера, но не может закрыть соединение как обычно.
часть сервера Erlang:
-behaviour (gen_server).
-export ([start_link/0, check_data/1]).
%%gen_server callbacks
-export ([init/1, handle_call/3,
handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define (PORT, 1477).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [?PORT], []).
init([Port]) ->
process_flag(trap_exit, true),
{ok, Listen} = gen_tcp:listen(Port,
[{active, false},
binary,
{reuseaddr, true}]),
spawn(fun() ->
accept_parallel(Listen) end),
io:format("~p started~n", [?MODULE]),
{ok, 0}.
accept_parallel(Listen) ->
{ok, Socket} = gen_tcp:accept(Listen),
spawn(fun() -> accept_parallel(Listen) end),
loop(Socket).
handle_call(Request, _From, N) ->
{reply, Request, N + 1}.
handle_cast(_Msg, N) ->
{noreply, N}.
handle_info(_Info, N) ->
{noreply, N}.
terminate(_Reason, _N) ->
io:format("~p stoped~n", [?MODULE]),
ok.
code_change(_OldVsn, N, _Extra) -> {ok, N}.
loop(Socket) ->
case gen_tcp:recv(Socket, 0) of
{ok, Bin} ->
case check_data(Bin) of
ok ->
gen_tcp:send(Socket, "ok");
{error, _Data} ->
gen_tcp:send(Socket, "error")
end;
{error, Reason} ->
exit(Reason)
end.
Я могу поставить тайм-аут после отправки сообщения клиенту, но в любом случае, как я могу закончить прием данных?
Обновление: мне нужно подключиться только один раз для подтверждения авторизации с отправкой токена клиенту.
2 ответа
Я нашел решение. Я надеюсь, что это кому-то поможет. Небольшая поправка на сервере:
loop(Socket) ->
inet:setopts(Socket, [{active, once}]),
receive
{tcp, Socket, Bin} ->
Answer = check_data(Bin),
gen_tcp:send(Socket, atom_to_binary(Answer, utf8)),
loop(Socket);
{tcp_closed, Socket} ->
io:format("Socket ~p closed [~p]~n", [Socket, self()]),
ok
end.
check_data(Bin) ->
Data = mochijson:decode(Bin),
case Data of
{struct, [{"login", Name}, {"password", Password}]} = NewUser ->
io:format("We got a new user:~n Name - ~p~n Password - ~p~n", [Name, Password]),
spawn(fun() -> save_data(NewUser) end),
ok;
Any ->
io:format("We got ~p~n", [Any]),
error
end.
и клиент:
#pragma mark - managing TCP connection
- (IBAction)signInPressed:(id)sender {
NSString *login = [_loginTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSString *password = [_passwordTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSDictionary *loginInformation = @{@"login" : login, @"password" : password};
NSData *data = [NSJSONSerialization dataWithJSONObject:loginInformation
options:NSJSONWritingPrettyPrinted
error:nil];
//[socket readDataWithTimeout:-1 tag:0]; if you need to get more than one response
[socket writeData:data withTimeout:-1 tag:1];
}
- (void)initTCPConnection
{
NSError *err;
if (![socket connectToHost:@"192.168.1.7" onPort:1477 error:&err]) {
NSLog(@"We got an error - %@", err);
}
NSLog(@"We are fine");
}
- (void)socket:(GCDAsyncSocket *)sender didConnectToHost:(NSString *)host port:(UInt16)port
{
NSLog(@"connected!");
[socket readDataWithTimeout:-1 tag:0];
}
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
NSLog(@"First request sent");
}
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
NSString *msg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"MSG: %@",msg);
}
В моем случае я не смог получить доступ к didReadData с помощью других методов, таких как readDataToData: и т. Д. Удачи.
Ваш цикл не зацикливается, поэтому после оценки выражения case ваш процесс завершается (ничего не остается), и gen_tcp закрывает для вас соединение при выходе из процесса. Вам нужно добавить loop(Socket)
в конце цикла /1:
loop(Socket) ->
case gen_tcp:recv(Socket, 0) of
% stuff...
end,
loop(Socket).
Лично я предпочитаю следовать более общим receive
стиль с gen_tcp чем gen_tcp:recv/2
как показано в этом примере:
loop(S) ->
inet:setopts(S,[{active,once}]),
receive
{tcp,S,Data} ->
Answer = process(Data), % business logic here
gen_tcp:send(S,Answer),
loop(S);
{tcp_closed,S} ->
io:format("Socket ~w closed [~w]~n",[S,self()]),
ok
end.
Приведенный выше пример взят из документации gen_tcp, и он довольно похож на то, как вы увидите, как люди реализуют гибридный стиль сокетов. По крайней мере, для меня этот метод проясняет мои рассуждения и одновременно предотвращает поток данных.