2013-05-11 105 views
0

我正在开发Erlang的邮件客户端适配器。当我尝试执行获取命令时,我遇到了问题,Erlang无法获取正文的内容。Erlang gen_tcp缺少数据包?

这是从我的终端输出,当我试图通过netcat来使用命令:

4 FETCH 2 BODY[2] 
* 2 FETCH (BODY[2] {1135} 

       <div> 
        test content 
       </div> 
      ) 
4 OK FETCH completed. 

唯一的输出调用gen_tcp服务器能够接收这是二进制的:

<<"* 2 FETCH (BODY[2] {1135}\r\n">> 

源代码在这里:

-module(mailconnector). 

-behaviour(gen_server). 

-export([start_link/2, stop/0]). 
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). 

start_link(Host, imap) -> 
    gen_server:start_link({local, ?MODULE}, ?MODULE, [Host, 143], []). 

stop() -> 
    gen_server:call(?MODULE, stop). 

init([Host, Port]) -> 
    {ok, Sock} = gen_tcp:connect(Host, Port, [binary, {packet, 0}, {active, true}]), 
    {ok, {Sock, 0}}. 

handle_call(stop, _From, State) -> 
    {stop, normal, ok, State}; 

handle_call({login, Username, Password}, _From, State) -> 
    {NewState, Output} = action(State, string:join(["LOGIN", Username, Password], " ")), 
    case Output of 
     {ok, Response, Data} -> Result = {Response, Data}; 
     _ -> Result = false 
    end, 
    {reply, Result, NewState}; 

handle_call(list, _From, State) -> 
    {NewState, Resp} = action(State, "LIST \"\" \"*\""), 
    {reply, Resp, NewState}; 

handle_call({select, MailBox}, _From, State) -> 
    {NewState, Output} = action(State, string:join(["SELECT", MailBox], " ")), 
    case Output of 
     {ok, Response, Data} -> Result = {Response, Data}; 
     _ -> Result = false 
    end, 
    {reply, Result, NewState}; 

handle_call({fetch, Num}, _From, State) -> 
    {NewState, Output} = action(State, string:join(["FETCH", Num, "BODY[1]"], " ")), 
    case Output of 
     {ok, Response, Data} -> Result = {Response, Data}; 
     _ -> Result = false 
    end, 
    {reply, Result, NewState}; 

handle_call(_Command, _From, _State) -> 
    {reply, not_valid, _State}. 

handle_cast(_Command, State) -> 
    {noreply, State}. 

handle_info(_Info, State) -> 
    {noreply, State}. 

terminate(_Reason, _State) -> 
    ok. 

code_change(_OldVsn, State, _Extra) -> 
    {ok, State}. 

action(_State = {Socket, Counter}, Command) -> 
    NewCount = Counter+1, 
    CounterAsList = lists:flatten(io_lib:format("~p ", [NewCount])), 
    Message = list_to_binary(lists:concat([CounterAsList, Command, "\r\n"])), 
    io:format("~p~n", [Message]), 
    gen_tcp:send(Socket, Message), 
    {{Socket, NewCount}, listener(Socket, NewCount)}. 

listener(_Sock, Count) -> 
    receive 
    {_, _, Reply} -> 
     io:format("RECEIVED: ~p~n", [Reply]), 
     Messages = string:tokens(binary_to_list(Reply), "\r\n"), 
     io:format("~p~n", [Messages]), 
     find_message(Messages, Count) 
    after 5000 -> 
     timeout 
    end. 

process_message(Message, Count) -> 
    StringCount = lists:flatten(io_lib:format("~p", [Count])), 
    case [MCount|PureMessage] = string:tokens(Message, " ") of 
     _M when StringCount == MCount -> 
      {ok, string:join(PureMessage, " ")}; 
     _ -> [_Command|Output] = PureMessage, {data, string:join(Output, " ")} 
    end. 

find_message(Messages, Count) -> 
    find_message(Messages, Count, []). 

find_message([], _, _) -> 
false;  

find_message([H|T], Count, Data) -> 
    case process_message(H, Count) of 
     {ok, Message} -> {ok, Message, lists:reverse(Data)}; 
     {data, Output} -> find_message(T, Count, [Output|Data]) 
    end. 

非常感谢您的帮助。

+1

TCP是一个流协议,这里是不能保证你会在一个去接收整个消息,即使它已经以这种方式被发送。接收端要为整个消息收集“足够”的字节。在你的情况下,你只能等待一条消息。为了在'listener/2'中安全,我会将TCP消息与'{tcp,Socket,Reply}'模式匹配,它更加明确和安全。 – rvirding 2013-05-11 19:16:33

回答

1

以下只是一个评论,而不是回答你的问题,我相信rvirding之上。

由于您使用的是标准行为(gen_server)之一,因此您会假设您打算编写符合OTP的应用程序。如果是这样,除非您准备好处理所有可能的系统消息以及您的应用程序,否则不应该直接使用接收表达式。在gen_server或gen_fsm的情况下,非系统消息由handle_info/2回调函数处理。你可以使用状态变量来保存的指标是什么命令你处理(例如,登录),并为每个单独的条款:

handle_info({tcp,Socket,Reply}, #state{pending = login} = State) -> 
...; 
handle_info({tcp,Socket,Reply}, #state{pending = list} = State) -> 
...; 

...然而这却成为一个穷人的有限状态机,所以你将它移植到一个gen_fsm行为回调模块会更好,在这个模块中,每个模块都有独立的状态(即wait_for_login)。然后你可以使用handle_info/3:

handle_info({tcp,Socket,Reply}, wait_for_login, State) -> 
...; 
handle_info({tcp,Socket,Reply}, wait_for_list, State) -> 
...; 
+0

用于提示'gen_fsm'的+1 – user601836 2013-05-14 10:13:28

0

如果您在active模式,最好的办法是由在接收流的handle_info(或您的自定义接收功能)使用模式{tcp, Socket, Msg}并将其存储在缓冲区中,直到它满足特定的模式匹配,或者具有特定的长度,然后按需要刷新缓冲区。

由于@rvirding说你不能确定你的所有消息将在一个数据包中接收,所以你必须处理可能的多个数据包。否则,您必须使用passive模式和功能gen_tcp:recv/2,但请记住,此功能是阻止的。

我建议你阅读本:http://learnyousomeerlang.com/buckets-of-sockets