MySQL SSLException: 关闭入站之前未收到同行的close_notify
在本文中,我们将介绍MySQL SSLException中的一个常见问题,即关闭入站之前未收到同行的close_notify错误。MySQL在与对等方建立SSL连接时,会使用TLS(Transport Layer Security)协议。在TLS握手过程中,存在关闭通道的部分,其中每个对等方都应该向对方发送close_notify。这将被用于关闭会话并执行释放操作。
之所以会出现“关闭入站之前未收到同行的close_notify”错误,是因为MySQL服务器(TLS客户端)已经开始关闭入站通道,而对等方服务器(TLS服务器)仍在继续发送数据。这将导致无法完全清除当前的TLS会话,从而产生错误。
以下是如何解决此问题:
阅读更多:MySQL 教程
修改MySQL连接参数
为了解决这个问题,可以在MySQL连接字符串中添加两个参数:autoReconnect和useSSL。其中autoReconnect参数将MySQL连接设置为自动重新连接,并useSSL设置为启用SSL加密。这样会引发一些其他问题,但对于海量读取场景可能是一种权衡方案。
String url = "jdbc:mysql://localhost:3306/mydatabase?autoReconnect=true&useSSL=true";
String user = "root";
String password = "root";
try {
Connection connection = DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
e.printStackTrace();
}
使用TLS协议
如果您正在使用自己基于TLS的协议或需要自定义TLS握手过程,那么您可以考虑使用Java的SSLSocket类,而不是MySQL提供的JDBC驱动程序。
SSLSocket的创建顺序为套接字(socket),然后是SSL套接字(试图在该套接字上建立安全连接),然后是通信线程。接下来是要注意的几点:
- 您必须手动向对方发送close_notify以关闭会话(在TLS握手期间完成的正常操作)。
- 您还必须等待对等方发送close_notify。在接收对等方的close_notify之前,不应关闭套接字。这个等待可能会导致您的程序挂起,这是一个需要注意的点。
- 您应该注意不要在write()操作的返回值等于-1时关闭套接字,因为这意味着它不能正确关闭。
以下是一个使用SSLSocket的示例:
// Key pair, trust store, and algorithm are all required to establish the SSL connection.
KeyStore keyStore = null, trustStore = null;
String keyStoreAlias = "", trustStoreAlias = "";
String password = "";
// Reference to an established SSLSocket.
SSLSocket sslSocket = null;
// Connection establishment code.
try {
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(ALGORITHM); // set algorithm used
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(ALGORITHM); // set algorithm used
keyStore = KeyStore.getInstance(KEY_STORE_TYPE); // set type of keystore used
trustStore = KeyStore.getInstance(TRUST_STORE_TYPE); // set type of truststore used
keyStore.load(KEY_STORE_INPUT_STREAM, password.toCharArray()); // load keystore
trustStore.load(TrustStoreInputStream, password.toCharArray()); // load truststore
keyManagerFactory.init(keyStore, password.toCharArray()); // initialize key manager with keystore
trustManagerFactory.init(trustStore); // initialize trust manager with truststore
SSLContext sslContext = SSLContext.getInstance(TLS_PROTOCOL); // set TLS protocol used
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); // initialize SSL context with key manager and trust manager
sslSocket = (SSLSocket) sslContext.getSocketFactory().createSocket(targetIp, targetPort); // create socket with connect to given target
// SSL/TLS handshake.
sslSocket.startHandshake(); // start handshake
// You might need to wait for the flush() to finish.
OutputStream outputStream = sslSocket.getOutputStream(); // establish output stream
// Close tunnel.
outputStream.write(("close_notify" + CLOSE_NOTIFY_EOL).getBytes(); // send close_notify
outputStream.flush(); // flush output stream
InputStream inputStream = sslSocket.getInputStream(); // establish input stream
int byteAvailable = inputStream.available();
while (byteAvailable != 0) {
inputStream.skip(byteAvailable); // skip all data
byteAvailable = inputStream.available(); // check for available data
}
sslSocket.close(); // close SSL socket
} catch (Exception e) {
e.printStackTrace();
}
关闭Nagle算法
Nagle算法是由John Nagle在1984年发明的,作为在TCP的若干实现中一个解决小白点(microscopic inefficiency)的方法。虽然它在许多情况下非常有效,但在某些情况下有时会出现问题。关闭它可能会帮助解决上述问题,但这是不推荐的做法,因为存在一些风险。
要关闭Nagle算法,可以使用TCP_NODELAY套接字选项:
boolean nodelay = true; // enable No-delay algorithm on this socket
Socket socket = . . . ;
socket.setTcpNoDelay(nodelay);
总结
MySQL SSLException: 关闭入站之前未收到同行的close_notify错误是一个常见的问题,但可以通过设置MySQL连接参数、使用TLS协议或关闭Nagle算法来解决。但我们要注意,关闭Nagle算法有一些潜在的风险。总之,在设置MySQL的SSL连接时,我们需要仔细考虑每一个细节,以确保其正常运行。
极客教程