客户端与服务器均创建ObjectInputStream时初始化阻塞问题排查
ObjectInputStream/ObjectOutputStream 结合分组密码流时的阻塞问题及解决办法
问题现象
当仅在客户端创建ObjectInputStream、服务器端创建ObjectOutputStream时,初始化可正常完成;但同时在服务器端创建ObjectInputStream时,程序会陷入无限阻塞。尝试提前初始化所有流也无法解决该问题。
复现代码
服务器端代码
package Server; import javax.crypto.*; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.*; import java.net.ServerSocket; import java.net.Socket; public class SimpleServer { public static void main(String[] args) throws Exception { int port = 5000; ServerSocket serverSocket = new ServerSocket(port); System.out.println("服务器已启动,等待连接..."); Socket clientSocket = serverSocket.accept(); System.out.println("客户端已连接。"); // 硬编码的密钥和IV byte[] keyBytes = "1234567890123456".getBytes(); byte[] ivBytes = "1234567890123456".getBytes(); SecretKey key = new SecretKeySpec(keyBytes, "AES"); IvParameterSpec iv = new IvParameterSpec(ivBytes); // 初始化密码器和流 Cipher cipher_enc = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher_enc.init(Cipher.ENCRYPT_MODE, key, iv); Cipher cipher_dec = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher_dec.init(Cipher.DECRYPT_MODE, key, iv); CipherOutputStream cipher_encOut = new CipherOutputStream(clientSocket.getOutputStream(), cipher_enc); CipherInputStream cipher_decIn = new CipherInputStream(clientSocket.getInputStream(), cipher_dec); System.out.println("密码器已初始化。"); ObjectOutputStream objOut = new ObjectOutputStream(cipher_encOut); System.out.println("ObjectOutputStream已初始化。"); objOut.writeObject("来自服务器的问候!"); objOut.flush(); // 移除该代码块可使程序正常运行 ObjectInputStream objIn = new ObjectInputStream(cipher_decIn); String message = (String) objIn.readObject(); System.out.println("来自客户端的消息:" + message); objOut.close(); objIn.close(); clientSocket.close(); serverSocket.close(); System.out.println("服务器已结束运行。"); } }
客户端代码
package Server; import javax.crypto.*; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.*; import java.net.Socket; public class SimpleClient { public static void main(String[] args) throws Exception { String host = "localhost"; int port = 5000; Socket socket = new Socket(host, port); System.out.println("已连接到服务器。"); // 硬编码的密钥和IV byte[] keyBytes = "1234567890123456".getBytes(); byte[] ivBytes = "1234567890123456".getBytes(); SecretKey key = new SecretKeySpec(keyBytes, "AES"); IvParameterSpec iv = new IvParameterSpec(ivBytes); // 初始化密码器和流 Cipher cipher_enc = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher_enc.init(Cipher.ENCRYPT_MODE, key, iv); Cipher cipher_dec = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher_dec.init(Cipher.DECRYPT_MODE, key, iv); CipherInputStream cipher_decIn = new CipherInputStream(socket.getInputStream(), cipher_dec); CipherOutputStream cipher_encOut = new CipherOutputStream(socket.getOutputStream(), cipher_enc); System.out.println("密码器已初始化。"); ObjectInputStream objIn = new ObjectInputStream(cipher_decIn); System.out.println("ObjectInputStream已初始化。"); String message = (String) objIn.readObject(); System.out.println("来自服务器的消息:" + message); // 移除该代码块可使程序正常运行 ObjectOutputStream objOut = new ObjectOutputStream(cipher_encOut); objOut.writeObject("来自客户端的问候!"); objOut.flush(); objIn.close(); objOut.close(); socket.close(); System.out.println("客户端已断开连接。"); } }
问题原因
ObjectOutputStream初始化时会写入固定的序列化协议头部,而分组密码(如AES/CBC/PKCS5Padding)对应的CipherOutputStream会缓存数据,直到凑够一个完整的加密块才会发送数据。当头部长度不足一个加密块时,数据会被滞留在缓存中,导致对端的ObjectInputStream一直等待完整的头部,最终引发无限阻塞。此问题与常规的ObjectInputStream阻塞原因(如未发送数据、流未刷新)无关。
解决方案
将分组密码模式替换为流密码模式,例如使用AES/CTR/NoPadding。流密码逐字节处理数据,不会缓存数据等待凑块,能确保ObjectOutputStream的头部被及时发送,避免阻塞。
内容的提问来源于stack exchange,提问作者SmoothBren
相关产品推荐
相关产品推荐

