IMAP client in Java (sockets only) hangs on receiv

2019-09-15 11:15发布

问题:

I want to implement a simple IMAP client. I can connect to server, receive its greeting but it seems I have a problem receiving the response after the login command.

I know that in IMAP line ends with \r\n or .\r\n but it seems that reading one byte a time simply does not work here and I do not know why.

My code:

class NewClass {

    public static String readAll(Socket socket, String end) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        StringBuilder sb = new StringBuilder();
        String line;

        while (!sb.toString().endsWith(end))
        {
            char c = (char)reader.read();
            sb.append(c);
        }
        return sb.toString();
    }

    public static void main(String[] args) throws IOException {
        String host = "x.x.x.x";
        int port = 143;

        Socket socket = new Socket(host, port);

        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);

        // server's greeting
        String initreply = readAll(socket, "\r\n");
        System.out.println(initreply);

        // log me in
        String login_cmd = "A1 LOGIN user pass\r\n";
        writer.print(login_cmd);
        writer.flush();
        String loginreply = readAll(socket, "\r\n");
        System.out.println(loginreply);

    }
}

回答1:

There is nothing simply about IMAP, it is a very complex protocol to implement from scratch. Consider finding a pre-existing IMAP library instead.

That being said, it is clear that you did not read the IMAP specification, RFC 3501 (in particular, section 2.2 "Commands and Responses"), or you would know why your code is failing. Your code is not compliant with the spec.

In particular:

  1. you are not sending a trailing \r\n at the end of your LOGIN command, so the server will not send any response. PrintWriter.print() does not print a line break. PrintWriter.println() does, but it only prints a LF, but IMAP uses CRLF instead. So you need to include \r\n at the end of every command you send to the server.

  2. you are not reading the server response correctly at all. You have to read lines, taking the * and + tokens into account, until you receive a line that starts with the same A1 tag that you used to tag the LOGIN command with. The logic would be much easier to implement if you use BufferedReader.readLine() instead of reading characters individually into a StringBuilder. And technically, you should be doing the reading asynchronously, such as in a background thread, as IMAP is an asynchronous protocol. There can be multiple commands in flight at one time, and there can be unsolicited data sent by the server in between command responses. The tags in the responses tell you which commands are being responded to, if any. Untagged responses that are prefixed with the * token should be processed as-is regardless of which command generates them.



回答2:

You can always know where your program hangs by taking a thread dump and examining the stack trace of the target thread (hint: if needed you can Thread.interrupt() a job waiting in a socket read).

Anyway, your program blocks because it's waiting for more input from the server, and the server has nothing to send. Always set a timeout before starting to read:

Socket socket = new Socket();
socket.connect(new InetSocketAddress(host, port), 1000);
socket.setSoTimeout(1000);
readAll();

At this point your program will likely throw a SocketTimeoutException and you'll see a call to BufferedReader.read() among the topmost frames. Assuming that IMAP messages are exactly one line long and terminated by CRLF, the read routine may be:

Scanner scanner = new Scanner(/* initialize from socket */);
scanner.useDelimiter("\r\n");
String response = scanner.nextLine();

Hint: install a sniffer like Wireshark to better understand what's going on.



回答3:

i made this in python 2 and it worked well

import socket
import ssl
import time
def RecServeurResponse(SecuredConnexion, Tag_Command):
    Reponse_recu=SecuredConnexion.recv(1024)
    Delimiteur_De_Fin_De_Reponse=Tag_Command+" "
    while True:
        # read server respons until Tag_Command
        if(Delimiteur_De_Fin_De_Reponse in Reponse_recu):
            break 
        Reponse_recu+=SecuredConnexion.recv(1024)
    return Reponse_recu

IMAPServerDomaineName="imap.gmail.com"
ConnectionToServer=socket.socket()
ConnectionToServer.connect((IMAPServerDomaineName,993))
# Create wraped socket with ssl
SecuredConnexion=ssl.create_default_context().wrap_socket(ConnectionToServer,server_hostname=IMAPServerDomaineName)
Command=""
# get the first greeting line
print SecuredConnexion.recv(2048)
Tag_Command=""
while True:
    # read IMAP commands
    Command=raw_input()
    # create a tag with system clock
    Tag_Command_Horloge=str(int(time.time()))
    # send command
    SecuredConnexion.send(Tag_Command_Horloge+" "+Command+"\r\n")
    print RecServeurResponse(SecuredConnexion,Tag_Command_Horloge)
    # if the command == logout, break
    if(Command.strip().lower().startswith("logout")):
        break
SecuredConnexion.close()