2013-05-11 87 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