如果服务器在写入 OutputStream 时关闭,SSLSocket 客户端不会抛出异常

SSLSocket client doesn't throw exception if server is shutdown when writing to OutputStream

编辑:

我更改了代码,以便在传输后检查文件的完整性。

但是现在当我在 dos.write(buffer, 0, count) 处的客户端中放置一个断点,杀死服务器,然后恢复客户端代码执行时,它无限期地挂在 serverMD5[i] = dataInputStream.readByte()

即使用户现在知道传输不成功(应用程序挂起并需要重新启动),这又一次没有按照我的预期进行(抛出 IOException)。

原始 post 更改代码:

我创建了一个 android 客户端,它使用 SSLSocket 连接到服务器并发送一些数据。 这是相关的客户端和服务器代码

客户:

try {
  SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket();
  sslsocket.connect(new InetSocketAddress(SERVER_IP, UPLOAD_PORT), 2000);

  OutputStream outputStream = sslsocket.getOutputStream();

  DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
  dataOutputStream.writeInt(DEVICE_ID);
  dataOutputStream.writeLong(FILE_LENGTH);

  MessageDigest md = MessageDigest.getInstance("MD5");
  DigestOutputStream dos = new DigestOutputStream(outputStream, md);

  InputStream readingsInputStream = new FileInputStream(FILE_NAME);

  int count;
  byte[] buffer = new byte[10 * 1024];
  while ((count = readingsInputStream.read(buffer)) > 0) {
    dos.write(buffer, 0, count);
  }
  readingsInputStream.close();

  byte[] md5 = md.digest();
  byte[] serverMD5 = new byte[16];

  DataInputStream dataInputStream = new DataInputStream(sslsocket.getInputStream());

  for (int i = 0;i<16;i++) {
    serverMD5[i] = dataInputStream.readByte();
    if (md5[i] != serverMD5[i]) throw new Exception("MD5 mismatch");
  }

  sslsocket.close();
} catch (Exception e) {
  ...
}

服务器:

try {
  SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();

  InputStream inputStream = sslSocket.getInputStream();

  DataInputStream dataInputStream = new DataInputStream(inputStream);
  int deviceID = dataInputStream.readInt();
  long fileLength = dataInputStream.readLong();

  MessageDigest md = MessageDigest.getInstance("MD5");
  DigestInputStream dis = new DigestInputStream(inputStream, md);

  OutputStream readingsOutputStream = new FileOutputStream("Device"+deviceID+".txt", false);

  int count;
  byte[] buffer = new byte[1];
  do {
    count = dis.read(buffer);
    readingsOutputStream.write(buffer, 0, count);
    fileLength -= count;
  } while (fileLength > 0);

  readingsOutputStream.close();

  byte[] md5 = md.digest();

  DataOutputStream md5OutputStream = new DataOutputStream(sslSocket.getOutputStream());
  for (int i = 0;i<16;i++) md5OutputStream.writeByte(md5[i]);

  sslSocket.close();
} catch (Exception e) {
  ...
}

通常这一切都按预期工作,但是当我在客户端的第 dos.write(buffer, 0, count) 行放置断点然后在到达断点时终止服务器时出现问题。

在客户端继续执行代码后,它没有抛出异常,只是遍历了这个代码块的其余部分,让我相信文件已成功写入服务器。

当然情况并非如此,因为在写入 OutputStream 之前服务器已关闭。这会导致服务器上出现一个空的 DeviceX.txt(X 是设备编号)文件。

这是一个大问题,因为用户可能认为数据已成功传输并从设备中删除它(由于应用程序的性质,发送的数据在某个时间点被删除)。

由于我设法产生了这个错误,我认为它也有可能在现实世界中发生。这是我第一次使用套接字,我不知道如何解决这个问题。

此外,如果有人注意到此代码块可能出现的任何其他问题(结果不符合预期但未抛出异常的另一种情况),请告诉我。

看起来您的客户端和服务器之间需要双向通信。当客户端完成上传后,您可以让客户端发送传输结束 (ascii 0x4) 字符(或您的 client/server 认为是 "special" 序列的任何其他字符)。当服务器收到转换结束字符时,服务器可以用它收到的字节数进行回复。在客户端,等待响应,如果达到超时,则告诉用户出了点问题。

这是TCP的正常操作。抛开 SSL,您的发送被缓冲在套接字发送缓冲区中,并在 send() 函数返回后异步传输。因此 send() 函数不可能立即检测到对等中断。如果继续发送,TCP 对未决数据的重试最终将失败并导致 后续 发送失败,在 Java 和 IOException: connection reset 的情况下。

inputStream.read(deviceIDbuffer);

您不能假设 read() 会填满缓冲区。你应该在这里使用DataInputStream.readInt()