You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

不使用Java Mail API及第三方库实现Java SMTP客户端时遭遇STARTTLS阻塞问题求助

问题分析与修复方案

嘿,别觉得尴尬!自己从零撸SMTP客户端这种“不走寻常路”的折腾感我太懂了,咱们一点点拆解你的问题:

核心问题1:STARTTLS后的流未加密升级

你发送STARTTLS命令后,服务器会返回就绪响应,之后必须把普通Socket升级为SSL/TLS加密Socket,否则服务器会等待加密握手,你的程序就会一直阻塞在读取响应的步骤里——这是你当前阻塞的直接原因。

核心问题2:错误的响应读取逻辑

你用while(c != -1)循环读取输入流,这会导致程序一直等待流关闭才会停止,但SMTP服务器在会话过程中不会关闭连接,所以第一次读取HELO响应时就会卡住,后面的STARTTLS等命令根本没机会发送。正确的做法是按行读取SMTP响应,直到识别出服务器的最终响应行(SMTP响应以数字开头,最后一行的数字后是空格,而不是-)。

核心问题3:AUTH PLAIN的格式错误

AUTH PLAIN的参数不是单独编码的用户名,而是需要把\0用户名\0密码这个整体做Base64编码,你现在的编码方式不符合SMTP协议规范,服务器会拒绝认证。


修复后的代码示例

import java.net.Socket;
import java.net.InetSocketAddress;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Base64;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.SSLSocket;

public class CustomSmtpClient {
    public static void main(String[] args) throws Exception {
        Socket socket = null;
        BufferedReader reader = null;
        OutputStream out = null;
        String username = "xyz@gmail.com";
        String password = "abc123";
        
        // 构建AUTH PLAIN需要的Base64字符串:\0用户名\0密码
        String authPlainPayload = String.format("\0%s\0%s", username, password);
        String authEncoded = Base64.getEncoder().encodeToString(authPlainPayload.getBytes());

        // 获取用户输入
        java.util.Scanner scanner = new java.util.Scanner(System.in);
        System.out.print("Enter host: ");
        String host = scanner.nextLine();
        System.out.print("Enter port: ");
        int port = Integer.parseInt(scanner.nextLine());

        // 建立初始连接
        socket = new Socket();
        socket.connect(new InetSocketAddress(host, port));
        System.out.println("Socket connected.");

        // 初始化普通流
        reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        out = socket.getOutputStream();

        // 读取服务器初始问候
        readSmtpResponse(reader);

        // 发送HELO命令
        sendCommand(out, "HELO " + host);
        readSmtpResponse(reader);

        // 发送STARTTLS命令
        sendCommand(out, "STARTTLS");
        readSmtpResponse(reader);

        // 升级为SSL/TLS连接
        SSLSocketFactory sslFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
        SSLSocket sslSocket = (SSLSocket) sslFactory.createSocket(
                socket, host, port, true);
        sslSocket.startHandshake();

        // 替换为加密后的流
        reader = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
        out = sslSocket.getOutputStream();

        // 重新发送HELO(有些服务器要求TLS握手后重新标识)
        sendCommand(out, "HELO " + host);
        readSmtpResponse(reader);

        // 发送AUTH PLAIN命令
        sendCommand(out, "AUTH PLAIN " + authEncoded);
        readSmtpResponse(reader);

        // 后续可以继续发送MAIL FROM、RCPT TO、DATA等命令...

        // 关闭资源
        out.close();
        reader.close();
        sslSocket.close();
        scanner.close();
    }

    // 发送SMTP命令(自动添加\r\n)
    private static void sendCommand(OutputStream out, String command) throws Exception {
        String fullCommand = command + "\r\n";
        out.write(fullCommand.getBytes());
        out.flush();
        System.out.println("> " + command);
    }

    // 读取SMTP响应,直到获取最终响应行
    private static void readSmtpResponse(BufferedReader reader) throws Exception {
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println("< " + line);
            // SMTP响应最后一行的格式是"数字 内容",中间是空格而非-
            if (line.length() > 3 && line.charAt(3) == ' ') {
                break;
            }
        }
    }
}

关键修改点说明

  1. TLS流升级:使用SSLSocketFactory将普通Socket包装成加密Socket,并触发握手,这是解决STARTTLS阻塞的核心。
  2. 响应读取逻辑:用BufferedReader按行读取,通过判断响应行的格式来确定是否结束读取,避免无限阻塞。
  3. AUTH PLAIN格式修正:按照协议要求构建\0用户名\0密码的字符串再编码,符合SMTP认证规范。

作为Java新手,能自己啃SMTP协议已经很厉害了,慢慢来,遇到协议相关的问题多查RFC文档(比如SMTP的RFC 5321和STARTTLS的RFC 3207),你会收获超多的!

内容的提问来源于stack exchange,提问作者Awais Mushtaq

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 23:02:42