如何为 Java 套接字正确实现阻塞、线程安全的写入方法?

How to properly implement a blocking, thread-safe write method for Java sockets?

我在 Java 中写了一个 WebSocket 服务器。 这是服务器用来向其客户端发送 WebSocket 数据包的方法:

private void sendFrame(boolean fin, boolean rsv1, boolean rsv2, boolean rsv3, WebSocketOpcode opcode, byte[] payloadData) throws IOException {
  if (connection.isClosed() || webSocketConnectionClosing != null) return;
  byte[] header = new byte[2];
  if (fin) header[0] |= 1 << 7;
  if (rsv1) header[0] |= 1 << 6;
  if (rsv2) header[0] |= 1 << 5;
  if (rsv3) header[0] |= 1 << 4;
  header[0] |= opcode.get() & 0b1111;
  header[1] |= payloadData.length < 126 ? payloadData.length : (payloadData.length <= 65535 ? 126 : 127);
  out.write(header);
  if (payloadData.length > 125) {
    if (payloadData.length <= 65535) {
      out.writeShort(payloadData.length);
    } else {
      out.writeLong(payloadData.length);
    }
  }
  out.write(payloadData);
  out.flush();
}

这就是我在客户端连接后声明输出流的方式:

out = new DataOutputStream(new BufferedOutputStream(connection.getOutputStream()));

我对此有一些疑问:

上面的代码是线程安全的吗?我的意思是,多个线程可以同时调用 sendFrame() 而没有数据包数据交错的风险吗?这段代码貌似是错误的,不过我还没有遇到交错的情况。

如果它不是线程安全的,那么我如何在不使用队列的情况下以这种形式使其成为线程安全的? (我希望 sendFrame() 方法在数据实际发送之前一直阻塞)

如果我不将 OutputStream 包装在 BufferedOutputStream 中,而是仅包装在 DataOutputStream 中,这会使 .write() 方法成为原子方法吗?将整个数据包数据打包到一个字节数组中,然后使用该数组调用一次 .write() 是否是线程安全的?

Is the above code thread-safe? What I mean by that is, can multiple threads call sendFrame() at the same time without the risk of packets data interleaving?

不是thread-safe。

It looks like this code is wrong, but I haven't encountered any interleaving yet.

交织发生的时间window非常小。可能不到一微秒。这意味着它发生的可能性很小。但不为零。


If it isn't thread-safe, then how would I make it thread-safe in this form without the use of queues? (I want the sendFrame() method to be blocking until the data is actually sent)

这取决于 sendFrame 方法如何适应您的其余代码。

我将使用的方法是确保针对特定输出流对 sendFrame 的所有调用都是在同一目标对象上进行的。然后我会使用synchronized来锁定目标对象或者属于目标对象的私有日志。

另一种方法是使用 synchronized 并锁定 out。然而,存在其他东西已经在做的风险,并且 sendFrame 呼叫将被不必要地阻止。


If I wouldn't wrap the OutputStream in BufferedOutputStream, but only in DataOutputStream instead, would this make the .write() method atomic?

(这不是重点。您有 3 个写入调用需要应对。但是....)

Would it be thread-safe to pack the entire packet data into a single byte array and then call .write() once with that array?

类 中的

None 已记录 1 作为 thread-safe,或作为保证write 操作是原子的。但是,在 OpenJDK Java 11(至少)中,相关的 write 方法在 BufferedOutputStreamDataOutputStream.

中实现为 synchronized

1 - 如果 javadocs 指定 thread-safety 等特性,那么这些特性 可以 因 Java 版本等而异