Java 中的 IMAP 客户端(仅限套接字)在接收时挂起
IMAP client in Java (sockets only) hangs on receiving
我想实现一个简单的 IMAP 客户端。我可以连接到服务器,收到它的问候语,但似乎我在 login
命令后收到响应时遇到问题。
我知道在 IMAP 中行以 \r\n
或 .\r\n
结尾,但似乎一次读取一个字节在这里根本行不通,我不知道为什么。
我的代码:
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);
}
}
通过线程转储和检查目标线程的堆栈跟踪,您始终可以知道程序挂起的位置(提示:如果需要,您可以 Thread.interrupt()
作业在套接字读取中等待)。
无论如何,您的程序会阻塞,因为它正在等待来自服务器的更多输入,而服务器没有要发送的内容。在开始阅读之前始终设置超时:
Socket socket = new Socket();
socket.connect(new InetSocketAddress(host, port), 1000);
socket.setSoTimeout(1000);
readAll();
此时您的程序可能会抛出 SocketTimeoutException
并且您会在最顶层的帧中看到对 BufferedReader.read()
的调用。假设 IMAP 消息恰好是一行,并以 CRLF
结束,读取例程可能是:
Scanner scanner = new Scanner(/* initialize from socket */);
scanner.useDelimiter("\r\n");
String response = scanner.nextLine();
提示:安装像 Wireshark 这样的嗅探器可以更好地了解发生了什么。
IMAP 简单,它是一个非常复杂的协议,需要从头开始实施。考虑寻找一个预先存在的 IMAP 库。
话虽如此,很明显您没有阅读 IMAP 规范,RFC 3501 (in particular, section 2.2 "Commands and Responses"),否则您会知道您的代码失败的原因。您的代码不符合规范。
特别是:
您没有在 LOGIN
命令末尾发送尾随 \r\n
,因此服务器不会发送任何响应。 PrintWriter.print()
不打印换行符。 PrintWriter.println()
会,但它只打印一个 LF,但 IMAP 使用 CRLF 代替。因此,您需要在发送到服务器的每个命令的末尾包含 \r\n
。
您根本没有正确读取服务器响应。您必须阅读行,考虑 *
和 +
标记,直到您收到一行 starts 具有相同的 A1
标记你曾经用标记 LOGIN
命令。如果您使用 BufferedReader.readLine()
而不是将字符单独读入 StringBuilder
,则该逻辑将更容易实现。从技术上讲,您应该异步进行读取,例如在后台线程中,因为 IMAP 是一种异步协议。一次可以有多个命令在运行,并且在命令响应之间可以有服务器发送的未经请求的数据。响应中的标签告诉您正在响应哪些命令(如果有的话)。以 *
标记为前缀的未标记响应应按原样处理,无论哪个命令生成它们。
我在 python 2 中制作了这个并且效果很好
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()
我想实现一个简单的 IMAP 客户端。我可以连接到服务器,收到它的问候语,但似乎我在 login
命令后收到响应时遇到问题。
我知道在 IMAP 中行以 \r\n
或 .\r\n
结尾,但似乎一次读取一个字节在这里根本行不通,我不知道为什么。
我的代码:
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);
}
}
通过线程转储和检查目标线程的堆栈跟踪,您始终可以知道程序挂起的位置(提示:如果需要,您可以 Thread.interrupt()
作业在套接字读取中等待)。
无论如何,您的程序会阻塞,因为它正在等待来自服务器的更多输入,而服务器没有要发送的内容。在开始阅读之前始终设置超时:
Socket socket = new Socket();
socket.connect(new InetSocketAddress(host, port), 1000);
socket.setSoTimeout(1000);
readAll();
此时您的程序可能会抛出 SocketTimeoutException
并且您会在最顶层的帧中看到对 BufferedReader.read()
的调用。假设 IMAP 消息恰好是一行,并以 CRLF
结束,读取例程可能是:
Scanner scanner = new Scanner(/* initialize from socket */);
scanner.useDelimiter("\r\n");
String response = scanner.nextLine();
提示:安装像 Wireshark 这样的嗅探器可以更好地了解发生了什么。
IMAP 简单,它是一个非常复杂的协议,需要从头开始实施。考虑寻找一个预先存在的 IMAP 库。
话虽如此,很明显您没有阅读 IMAP 规范,RFC 3501 (in particular, section 2.2 "Commands and Responses"),否则您会知道您的代码失败的原因。您的代码不符合规范。
特别是:
您没有在
LOGIN
命令末尾发送尾随\r\n
,因此服务器不会发送任何响应。PrintWriter.print()
不打印换行符。PrintWriter.println()
会,但它只打印一个 LF,但 IMAP 使用 CRLF 代替。因此,您需要在发送到服务器的每个命令的末尾包含\r\n
。您根本没有正确读取服务器响应。您必须阅读行,考虑
*
和+
标记,直到您收到一行 starts 具有相同的A1
标记你曾经用标记LOGIN
命令。如果您使用BufferedReader.readLine()
而不是将字符单独读入StringBuilder
,则该逻辑将更容易实现。从技术上讲,您应该异步进行读取,例如在后台线程中,因为 IMAP 是一种异步协议。一次可以有多个命令在运行,并且在命令响应之间可以有服务器发送的未经请求的数据。响应中的标签告诉您正在响应哪些命令(如果有的话)。以*
标记为前缀的未标记响应应按原样处理,无论哪个命令生成它们。
我在 python 2 中制作了这个并且效果很好
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()