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, и он довольно похож на то, как вы увидите, как люди реализуют гибридный стиль сокетов. По крайней мере, для меня этот метод проясняет мои рассуждения и одновременно предотвращает поток данных.

Другие вопросы по тегам