From 12d8e8e4c072f9430884e1c91a399755377ef70a Mon Sep 17 00:00:00 2001 From: gubaojian Date: Fri, 20 Jun 2025 12:00:33 +0800 Subject: [PATCH 1/3] improve performance by mask 8 byte long, add log check for log.isTraceEnabled() and skip utf8 valid for utf8 byte --- .../org/java_websocket/WebSocketImpl.java | 28 ++++++++++++++--- .../org/java_websocket/drafts/Draft_6455.java | 31 ++++++++++++++++--- .../org/java_websocket/framing/TextFrame.java | 25 +++++++++++++-- 3 files changed, 73 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java index 3289aefcf..d4c413f72 100644 --- a/src/main/java/org/java_websocket/WebSocketImpl.java +++ b/src/main/java/org/java_websocket/WebSocketImpl.java @@ -393,7 +393,9 @@ private void decodeFrames(ByteBuffer socketBuffer) { try { frames = draft.translateFrame(socketBuffer); for (Framedata f : frames) { - log.trace("matched frame: {}", f); + if (log.isTraceEnabled()) { + log.trace("matched frame: {}", f); + } draft.processFrame(this, f); } } catch (LimitExceededException e) { @@ -673,12 +675,27 @@ private void send(Collection frames) { } ArrayList outgoingFrames = new ArrayList<>(); for (Framedata f : frames) { - log.trace("send frame: {}", f); + if (log.isTraceEnabled()) { + log.trace("send frame: {}", f); + } outgoingFrames.add(draft.createBinaryFrame(f)); } write(outgoingFrames); } + public ByteBuffer createEncodedBinaryFrame(Framedata framedata) { + return draft.createBinaryFrame(framedata); + } + + public void sendEncodedBinaryFrame(ByteBuffer binaryFrame) { + List bufs = new ArrayList<>(4); + sendEncodedBinaryFrames(bufs); + } + + public void sendEncodedBinaryFrames(List outgoingFrames) { + write(outgoingFrames); + } + @Override public void sendFragmentedFrame(Opcode op, ByteBuffer buffer, boolean fin) { send(draft.continuousFrame(op, buffer, fin)); @@ -734,8 +751,11 @@ public void startHandshake(ClientHandshakeBuilder handshakedata) } private void write(ByteBuffer buf) { - log.trace("write({}): {}", buf.remaining(), - buf.remaining() > 1000 ? "too big to display" : new String(buf.array())); + // should check isTraceEnabled() to avoid performance down because of log + if (log.isTraceEnabled()) { + log.trace("write({}): {}", buf.remaining(), + buf.remaining() > 1000 ? "too big to display" : new String(buf.array())); + } outQueue.add(buf); wsl.onWriteDemand(this); diff --git a/src/main/java/org/java_websocket/drafts/Draft_6455.java b/src/main/java/org/java_websocket/drafts/Draft_6455.java index eb4879976..6a018d011 100644 --- a/src/main/java/org/java_websocket/drafts/Draft_6455.java +++ b/src/main/java/org/java_websocket/drafts/Draft_6455.java @@ -27,6 +27,7 @@ import java.math.BigInteger; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; @@ -509,11 +510,28 @@ private ByteBuffer createByteBufferFromFramedata(Framedata framedata) { throw new IllegalStateException("Size representation not supported/specified"); } if (mask) { - ByteBuffer maskkey = ByteBuffer.allocate(4); - maskkey.putInt(reuseableRandom.nextInt()); - buf.put(maskkey.array()); - for (int i = 0; mes.hasRemaining(); i++) { - buf.put((byte) (mes.get() ^ maskkey.get(i % 4))); + int maskInt = reuseableRandom.nextInt(); + if (useFastMask) { + //default ByteOrder.BIG_ENDIAN + ByteBuffer maskLongkey = ByteBuffer.allocate(8); + maskLongkey.putInt(maskInt); + maskLongkey.putInt(maskInt); + buf.putInt(maskInt); + int length = mes.remaining() / 8; + long maskLong = maskLongkey.getLong(0); + for (int i = 0; i < length; i++) { + buf.putLong(mes.getLong() ^ maskLong); + } + for (int i = 0; mes.hasRemaining(); i++) { + buf.put((byte) (mes.get() ^ maskLongkey.get(i % 4))); + } + } else { + ByteBuffer maskkey = ByteBuffer.allocate(4); + maskkey.putInt(maskInt); + buf.put(maskkey.array()); + for (int i = 0; mes.hasRemaining(); i++) { + buf.put((byte) (mes.get() ^ maskkey.get(i % 4))); + } } } else { buf.put(mes); @@ -525,6 +543,8 @@ private ByteBuffer createByteBufferFromFramedata(Framedata framedata) { return buf; } + public static boolean useFastMask = true; + private Framedata translateSingleFrame(ByteBuffer buffer) throws IncompleteException, InvalidDataException { if (buffer == null) { @@ -791,6 +811,7 @@ public List createFrames(ByteBuffer binary, boolean mask) { public List createFrames(String text, boolean mask) { TextFrame curframe = new TextFrame(); curframe.setPayload(ByteBuffer.wrap(Charsetfunctions.utf8Bytes(text))); + curframe.setHasCheckUTF8PlayLoad(true); curframe.setTransferemasked(mask); try { curframe.isValid(); diff --git a/src/main/java/org/java_websocket/framing/TextFrame.java b/src/main/java/org/java_websocket/framing/TextFrame.java index 52154b47e..be6f5bb29 100644 --- a/src/main/java/org/java_websocket/framing/TextFrame.java +++ b/src/main/java/org/java_websocket/framing/TextFrame.java @@ -25,15 +25,19 @@ package org.java_websocket.framing; +import java.nio.ByteBuffer; import org.java_websocket.enums.Opcode; import org.java_websocket.exceptions.InvalidDataException; import org.java_websocket.util.Charsetfunctions; + /** * Class to represent a text frames */ public class TextFrame extends DataFrame { + boolean hasCheckUTF8PlayLoad = false; + /** * constructor which sets the opcode of this frame to text */ @@ -44,8 +48,25 @@ public TextFrame() { @Override public void isValid() throws InvalidDataException { super.isValid(); - if (!Charsetfunctions.isValidUTF8(getPayloadData())) { - throw new InvalidDataException(CloseFrame.NO_UTF8, "Received text is no valid utf8 string!"); + if (!hasCheckUTF8PlayLoad) { + if (!Charsetfunctions.isValidUTF8(getPayloadData())) { + throw new InvalidDataException(CloseFrame.NO_UTF8, "Received text is no valid utf8 string!"); + } + hasCheckUTF8PlayLoad = true; } } + + @Override + public void setPayload(ByteBuffer payload) { + super.setPayload(payload); + hasCheckUTF8PlayLoad = false; + } + + public boolean hasCheckUTF8PlayLoad() { + return hasCheckUTF8PlayLoad; + } + + public void setHasCheckUTF8PlayLoad(boolean hasCheckUTF8PlayLoad) { + this.hasCheckUTF8PlayLoad = hasCheckUTF8PlayLoad; + } } From 1938d7cace4974aee57041e0d3272a3659f5f77f Mon Sep 17 00:00:00 2001 From: "baojian.gu" Date: Fri, 20 Jun 2025 19:42:52 +0800 Subject: [PATCH 2/3] n div 8 eq n >> 3 --- src/main/java/org/java_websocket/drafts/Draft_6455.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/java_websocket/drafts/Draft_6455.java b/src/main/java/org/java_websocket/drafts/Draft_6455.java index 6a018d011..b99a69e75 100644 --- a/src/main/java/org/java_websocket/drafts/Draft_6455.java +++ b/src/main/java/org/java_websocket/drafts/Draft_6455.java @@ -517,20 +517,23 @@ private ByteBuffer createByteBufferFromFramedata(Framedata framedata) { maskLongkey.putInt(maskInt); maskLongkey.putInt(maskInt); buf.putInt(maskInt); - int length = mes.remaining() / 8; + // n / 8 eq n >> 3 + int length = mes.remaining() >> 3; long maskLong = maskLongkey.getLong(0); for (int i = 0; i < length; i++) { buf.putLong(mes.getLong() ^ maskLong); } for (int i = 0; mes.hasRemaining(); i++) { - buf.put((byte) (mes.get() ^ maskLongkey.get(i % 4))); + // x % 2^n 为 x & (2^n - 1) + buf.put((byte) (mes.get() ^ maskLongkey.get(i & 3))); } } else { ByteBuffer maskkey = ByteBuffer.allocate(4); maskkey.putInt(maskInt); buf.put(maskkey.array()); for (int i = 0; mes.hasRemaining(); i++) { - buf.put((byte) (mes.get() ^ maskkey.get(i % 4))); + // x % 2^n 为 x & (2^n - 1) + buf.put((byte) (mes.get() ^ maskkey.get(i & 3))); } } } else { From 0202d617b2ad86a316b851288f31bcb073685f29 Mon Sep 17 00:00:00 2001 From: "baojian.gu" Date: Fri, 20 Jun 2025 21:23:52 +0800 Subject: [PATCH 3/3] add flag for case where can skip utf8 valid --- .../org/java_websocket/drafts/Draft_6455.java | 7 ++++++- .../org/java_websocket/framing/TextFrame.java | 15 ++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/java_websocket/drafts/Draft_6455.java b/src/main/java/org/java_websocket/drafts/Draft_6455.java index b99a69e75..8b8b933a2 100644 --- a/src/main/java/org/java_websocket/drafts/Draft_6455.java +++ b/src/main/java/org/java_websocket/drafts/Draft_6455.java @@ -615,6 +615,11 @@ private Framedata translateSingleFrame(ByteBuffer buffer) (frame.getPayloadData().remaining() > 1000 ? "too big to display" : new String(frame.getPayloadData().array()))); } + + if (frame instanceof TextFrame) { + ((TextFrame) frame).setCanSkipCheckUTF8PlayLoad(true); + } + frame.isValid(); return frame; } @@ -814,7 +819,7 @@ public List createFrames(ByteBuffer binary, boolean mask) { public List createFrames(String text, boolean mask) { TextFrame curframe = new TextFrame(); curframe.setPayload(ByteBuffer.wrap(Charsetfunctions.utf8Bytes(text))); - curframe.setHasCheckUTF8PlayLoad(true); + curframe.setCanSkipCheckUTF8PlayLoad(true); curframe.setTransferemasked(mask); try { curframe.isValid(); diff --git a/src/main/java/org/java_websocket/framing/TextFrame.java b/src/main/java/org/java_websocket/framing/TextFrame.java index be6f5bb29..d905d6cc4 100644 --- a/src/main/java/org/java_websocket/framing/TextFrame.java +++ b/src/main/java/org/java_websocket/framing/TextFrame.java @@ -36,7 +36,7 @@ */ public class TextFrame extends DataFrame { - boolean hasCheckUTF8PlayLoad = false; + boolean canSkipCheckUTF8PlayLoad = false; /** * constructor which sets the opcode of this frame to text @@ -48,25 +48,26 @@ public TextFrame() { @Override public void isValid() throws InvalidDataException { super.isValid(); - if (!hasCheckUTF8PlayLoad) { + if (!canSkipCheckUTF8PlayLoad) { if (!Charsetfunctions.isValidUTF8(getPayloadData())) { throw new InvalidDataException(CloseFrame.NO_UTF8, "Received text is no valid utf8 string!"); } - hasCheckUTF8PlayLoad = true; + canSkipCheckUTF8PlayLoad = true; } } @Override public void setPayload(ByteBuffer payload) { super.setPayload(payload); - hasCheckUTF8PlayLoad = false; + canSkipCheckUTF8PlayLoad = false; } public boolean hasCheckUTF8PlayLoad() { - return hasCheckUTF8PlayLoad; + return canSkipCheckUTF8PlayLoad; } - public void setHasCheckUTF8PlayLoad(boolean hasCheckUTF8PlayLoad) { - this.hasCheckUTF8PlayLoad = hasCheckUTF8PlayLoad; + public void setCanSkipCheckUTF8PlayLoad(boolean canSkipCheckUTF8PlayLoad) { + this.canSkipCheckUTF8PlayLoad = canSkipCheckUTF8PlayLoad; } + }