diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index bbaee90..c328874 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -2,7 +2,7 @@ name: Build on: push: - branches: [ "develop" ] + branches: [ "develop", "feature/**" ] pull_request: branches: [ "develop" ] diff --git a/README.md b/README.md index 01f0101..b330404 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,7 @@ repositories { } dependencies { - implementation "com.jauntsdn.netty:netty-websocket-http1:1.0.0" + implementation "com.jauntsdn.netty:netty-websocket-http1:1.1.0" } ``` diff --git a/gradle.properties b/gradle.properties index e256f90..8c23493 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group=com.jauntsdn.netty -version=1.1.0 +version=1.1.1 googleJavaFormatPluginVersion=0.9 dependencyManagementPluginVersion=1.1.0 @@ -7,14 +7,14 @@ gitPluginVersion=0.13.0 osDetectorPluginVersion=1.7.3 versionsPluginVersion=0.45.0 -nettyVersion=4.1.93.Final +nettyVersion=4.1.96.Final nettyTcnativeVersion=2.0.61.Final hdrHistogramVersion=2.1.12 slf4jVersion=1.7.36 -logbackVersion=1.2.11 +logbackVersion=1.2.12 jsr305Version=3.0.2 -junitVersion=5.9.3 +junitVersion=5.10.0 assertjVersion=3.24.2 org.gradle.parallel=true diff --git a/netty-websocket-http1-perftest/build.gradle b/netty-websocket-http1-perftest/build.gradle index 341b2c5..41c5fce 100644 --- a/netty-websocket-http1-perftest/build.gradle +++ b/netty-websocket-http1-perftest/build.gradle @@ -61,6 +61,33 @@ task clientScripts(type: CreateStartScripts) { startScripts.dependsOn serverScripts startScripts.dependsOn clientScripts +task runBulkServer(type: JavaExec) { + classpath = sourceSets.main.runtimeClasspath + mainClass = "com.jauntsdn.netty.handler.codec.http.websocketx.perftest.bulkencoder.server.Main" +} + +task runBulkClient(type: JavaExec) { + classpath = sourceSets.main.runtimeClasspath + mainClass = "com.jauntsdn.netty.handler.codec.http.websocketx.perftest.bulkencoder.client.Main" +} + +task serverBulkScripts(type: CreateStartScripts) { + mainClass = "com.jauntsdn.netty.handler.codec.http.websocketx.perftest.bulkencoder.server.Main" + applicationName = "${project.name}-bulkserver" + classpath = startScripts.classpath + outputDir = startScripts.outputDir +} + +task clientBulkScripts(type: CreateStartScripts) { + mainClass = "com.jauntsdn.netty.handler.codec.http.websocketx.perftest.bulkencoder.client.Main" + applicationName = "${project.name}-bulkclient" + classpath = startScripts.classpath + outputDir = startScripts.outputDir +} + +startScripts.dependsOn serverBulkScripts +startScripts.dependsOn clientBulkScripts + tasks.named("startScripts") { enabled = false } \ No newline at end of file diff --git a/netty-websocket-http1-perftest/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/perftest/bulkencoder/client/Main.java b/netty-websocket-http1-perftest/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/perftest/bulkencoder/client/Main.java new file mode 100644 index 0000000..f320ede --- /dev/null +++ b/netty-websocket-http1-perftest/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/perftest/bulkencoder/client/Main.java @@ -0,0 +1,387 @@ +/* + * Copyright 2023 - present Maksym Ostroverkhov. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.jauntsdn.netty.handler.codec.http.websocketx.perftest.bulkencoder.client; + +import com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketCallbacksHandler; +import com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler; +import com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketFrameFactory; +import com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketFrameListener; +import com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketProtocol; +import com.jauntsdn.netty.handler.codec.http.websocketx.test.Security; +import com.jauntsdn.netty.handler.codec.http.websocketx.test.Transport; +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.codec.http.HttpClientCodec; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.ssl.OpenSsl; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslHandler; +import io.netty.util.ResourceLeakDetector; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import org.HdrHistogram.Histogram; +import org.HdrHistogram.Recorder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Main { + private static final Logger logger = LoggerFactory.getLogger(Main.class); + + public static void main(String[] args) throws Exception { + ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED); + + String host = System.getProperty("HOST", "localhost"); + int port = Integer.parseInt(System.getProperty("PORT", "8088")); + int duration = Integer.parseInt(System.getProperty("DURATION", "600")); + boolean isNativeTransport = Boolean.parseBoolean(System.getProperty("NATIVE", "true")); + boolean isEncrypted = Boolean.parseBoolean(System.getProperty("ENCRYPT", "true")); + boolean isMasked = Boolean.parseBoolean(System.getProperty("MASK", "false")); + boolean isTotalFrames = Boolean.parseBoolean(System.getProperty("TOTAL", "false")); + int frameSize = Integer.parseInt(System.getProperty("FRAME", "64")); + int outboundFramesWindow = Integer.parseInt(System.getProperty("WINDOW", "2222")); + + boolean isOpensslAvailable = OpenSsl.isAvailable(); + boolean isEpollAvailable = Transport.isEpollAvailable(); + boolean isKqueueAvailable = Transport.isKqueueAvailable(); + + logger.info("\n==> http1 websocket test client\n"); + logger.info("\n==> remote address: {}:{}", host, port); + logger.info("\n==> duration: {}", duration); + logger.info("\n==> native transport: {}", isNativeTransport); + logger.info("\n==> epoll available: {}", isEpollAvailable); + logger.info("\n==> kqueue available: {}", isKqueueAvailable); + logger.info("\n==> openssl available: {}\n", isOpensslAvailable); + logger.info("\n==> encryption: {}\n", isEncrypted); + logger.info("\n==> frame masking: {}\n", isMasked); + logger.info("\n==> frame payload size: {}", frameSize); + logger.info("\n==> outbound frames window: {}", outboundFramesWindow); + + Transport transport = Transport.get(isNativeTransport); + logger.info("\n==> io transport: {}", transport.type()); + SslContext sslContext = isEncrypted ? Security.clientLocalSslContext() : null; + + List framesPayload = framesPayload(1000, frameSize); + FrameCounters frameCounters = new FrameCounters(isTotalFrames); + + Channel channel = + new Bootstrap() + .group(transport.eventLoopGroup()) + .channel(transport.clientChannel()) + .handler( + new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) { + + HttpClientCodec http1Codec = new HttpClientCodec(); + HttpObjectAggregator http1Aggregator = new HttpObjectAggregator(65536); + WebSocketCallbacksHandler webSocketHandler = + new WebSocketClientHandler( + frameCounters, + framesPayload, + ThreadLocalRandom.current(), + outboundFramesWindow); + + WebSocketClientProtocolHandler webSocketProtocolHandler = + WebSocketClientProtocolHandler.create() + .path("/echo") + .mask(isMasked) + .allowMaskMismatch(true) + .maxFramePayloadLength(65_535) + .webSocketHandler(webSocketHandler) + .build(); + + ChannelPipeline pipeline = ch.pipeline(); + if (sslContext != null) { + SslHandler sslHandler = sslContext.newHandler(ch.alloc()); + pipeline.addLast(sslHandler); + } + pipeline.addLast(http1Codec, http1Aggregator, webSocketProtocolHandler); + } + }) + .connect(new InetSocketAddress(host, port)) + .sync() + .channel(); + + int warmupMillis = 5000; + logger.info("==> warming up for {} millis...", warmupMillis); + channel + .eventLoop() + .schedule( + () -> { + logger.info("==> warm up completed"); + frameCounters.start(); + channel + .eventLoop() + .scheduleAtFixedRate( + new StatsReporter(frameCounters, frameSize), + 1000, + 1000, + TimeUnit.MILLISECONDS); + }, + warmupMillis, + TimeUnit.MILLISECONDS); + + channel.closeFuture().sync(); + logger.info("Client terminated"); + } + + private static class FrameCounters { + private final Recorder histogram; + private int frameCount; + private boolean isStarted; + + public FrameCounters(boolean totalFrames) { + histogram = totalFrames ? null : new Recorder(36000000000L, 3); + } + + private long totalFrameCount; + + public void start() { + isStarted = true; + } + + public void countFrame(long timestamp) { + if (!isStarted) { + return; + } + + if (histogram == null) { + totalFrameCount++; + } else { + frameCount++; + if (timestamp >= 0) { + histogram.recordValue(System.nanoTime() - timestamp); + } + } + } + + public Recorder histogram() { + return histogram; + } + + public int frameCount() { + int count = frameCount; + frameCount = 0; + return count; + } + + public long totalFrameCount() { + return totalFrameCount; + } + } + + private static class StatsReporter implements Runnable { + private final FrameCounters frameCounters; + private final int frameSize; + private int iteration; + + public StatsReporter(FrameCounters frameCounters, int frameSize) { + this.frameCounters = frameCounters; + this.frameSize = frameSize; + } + + @Override + public void run() { + Recorder histogram = frameCounters.histogram(); + if (histogram != null) { + Histogram h = histogram.getIntervalHistogram(); + long p50 = h.getValueAtPercentile(50) / 1000; + long p95 = h.getValueAtPercentile(95) / 1000; + long p99 = h.getValueAtPercentile(99) / 1000; + int count = frameCounters.frameCount(); + + logger.info("p50 => {} micros", p50); + logger.info("p95 => {} micros", p95); + logger.info("p99 => {} micros", p99); + logger.info("throughput => {} messages", count); + logger.info("throughput => {} kbytes\n", count * frameSize / (float) 1024); + } else { + if (++iteration % 10 == 0) { + logger.info( + "total frames, iteration {} => {}", iteration, frameCounters.totalFrameCount()); + } + } + } + } + + static class WebSocketClientHandler implements WebSocketCallbacksHandler, WebSocketFrameListener { + + private final FrameCounters frameCounters; + private final List dataList; + private final Random random; + private final int window; + private int sendIndex; + private boolean isClosed; + private FrameWriter frameWriter; + + WebSocketClientHandler( + FrameCounters frameCounters, List dataList, Random random, int window) { + this.frameCounters = frameCounters; + this.dataList = dataList; + this.random = random; + this.window = window; + } + + @Override + public WebSocketFrameListener exchange( + ChannelHandlerContext ctx, WebSocketFrameFactory frameFactory) { + frameWriter = new FrameWriter(ctx, frameFactory.bulkEncoder(), window); + return this; + } + + @Override + public void onOpen(ChannelHandlerContext ctx) { + frameWriter.startWrite(); + } + + @Override + public void onClose(ChannelHandlerContext ctx) { + isClosed = true; + frameWriter.close(); + } + + @Override + public void onChannelRead( + ChannelHandlerContext ctx, boolean finalFragment, int rsv, int opcode, ByteBuf payload) { + if (opcode != WebSocketProtocol.OPCODE_BINARY) { + payload.release(); + return; + } + + long timeStamp = payload.readLong(); + frameCounters.countFrame(timeStamp); + payload.release(); + frameWriter.tryContinueWrite(); + } + + @Override + public void onChannelReadComplete(ChannelHandlerContext ctx) {} + + @Override + public void onUserEventTriggered(ChannelHandlerContext ctx, Object evt) {} + + @Override + public void onChannelWritabilityChanged(ChannelHandlerContext ctx) { + Channel ch = ctx.channel(); + if (!ch.isWritable()) { + ch.flush(); + } + } + + @Override + public void onExceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + if (!isClosed) { + isClosed = true; + logger.error("Channel error", cause); + ctx.close(); + } + } + + class FrameWriter { + private final ChannelHandlerContext ctx; + private final WebSocketFrameFactory.BulkEncoder bulkEncoder; + private ByteBuf outbuffer; + private final int window; + private int queued; + + FrameWriter( + ChannelHandlerContext ctx, WebSocketFrameFactory.BulkEncoder bulkEncoder, int window) { + this.ctx = ctx; + this.bulkEncoder = bulkEncoder; + this.window = window; + this.outbuffer = ctx.alloc().buffer(4096, 4096); + } + + void close() { + ByteBuf out = outbuffer; + if (out != null) { + outbuffer = null; + out.release(); + } + } + + void startWrite() { + if (isClosed) { + return; + } + int cur = queued; + int w = window; + int writeCount = w - cur; + ChannelHandlerContext c = ctx; + for (int i = 0; i < writeCount; i++) { + writeWebSocketFrame(c); + } + queued = w; + ByteBuf out = outbuffer; + if (out.readableBytes() > 0) { + c.write(out, c.voidPromise()); + outbuffer = ctx.alloc().buffer(4096, 4096); + } + c.flush(); + } + + void tryContinueWrite() { + int q = --queued; + if (q <= window / 2) { + startWrite(); + } + } + + void writeWebSocketFrame(ChannelHandlerContext ctx) { + List dl = dataList; + int dataIndex = random.nextInt(dl.size()); + ByteBuf data = dl.get(dataIndex); + int index = sendIndex++; + int dataSize = data.readableBytes(); + int payloadSize = Long.BYTES + dataSize; + + ByteBuf out = outbuffer; + WebSocketFrameFactory.BulkEncoder encoder = bulkEncoder; + int outSize = encoder.sizeofBinaryFrame(payloadSize); + if (outSize > out.capacity() - out.writerIndex()) { + ctx.write(out, ctx.voidPromise()); + out = outbuffer = ctx.alloc().buffer(4096, 4096); + } + + int mask = encoder.encodeBinaryFramePrefix(out, payloadSize); + out.writeLong(index % 50_000 == 0 ? System.nanoTime() : -1).writeBytes(data, 0, dataSize); + encoder.maskBinaryFrame(out, mask, payloadSize); + } + } + } + + private static List framesPayload(int count, int size) { + Random random = new Random(); + List data = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + byte[] bytes = new byte[size]; + random.nextBytes(bytes); + data.add(Unpooled.wrappedBuffer(bytes)); + } + return data; + } +} diff --git a/netty-websocket-http1-perftest/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/perftest/bulkencoder/server/Main.java b/netty-websocket-http1-perftest/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/perftest/bulkencoder/server/Main.java new file mode 100644 index 0000000..e6289b4 --- /dev/null +++ b/netty-websocket-http1-perftest/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/perftest/bulkencoder/server/Main.java @@ -0,0 +1,198 @@ +/* + * Copyright 2023 - present Maksym Ostroverkhov. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.jauntsdn.netty.handler.codec.http.websocketx.perftest.bulkencoder.server; + +import com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketCallbacksHandler; +import com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketFrameFactory; +import com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketFrameListener; +import com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketProtocol; +import com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; +import com.jauntsdn.netty.handler.codec.http.websocketx.test.Security; +import com.jauntsdn.netty.handler.codec.http.websocketx.test.Transport; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.codec.http.websocketx.WebSocketDecoderConfig; +import io.netty.handler.ssl.OpenSsl; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslHandler; +import io.netty.util.ResourceLeakDetector; +import java.io.IOException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Main { + private static final Logger logger = LoggerFactory.getLogger(Main.class); + + public static void main(String[] args) throws Exception { + ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED); + + String host = System.getProperty("HOST", "localhost"); + int port = Integer.parseInt(System.getProperty("PORT", "8088")); + boolean isNativeTransport = + Boolean.parseBoolean(System.getProperty("NATIVE_TRANSPORT", "true")); + boolean isEncrypted = Boolean.parseBoolean(System.getProperty("ENCRYPT", "true")); + + boolean isOpensslAvailable = OpenSsl.isAvailable(); + boolean isEpollAvailable = Transport.isEpollAvailable(); + boolean isKqueueAvailable = Transport.isKqueueAvailable(); + + logger.info("\n==> http1 websocket load test server\n"); + logger.info("\n==> bind address: {}:{}", host, port); + logger.info("\n==> native transport: {}", isNativeTransport); + logger.info("\n==> epoll available: {}", isEpollAvailable); + logger.info("\n==> kqueue available: {}", isKqueueAvailable); + logger.info("\n==> openssl available: {}", isOpensslAvailable); + logger.info("\n==> encryption: {}\n", isEncrypted); + + Transport transport = Transport.get(isNativeTransport); + logger.info("\n==> io transport: {}", transport.type()); + SslContext sslContext = isEncrypted ? Security.serverSslContext() : null; + + ServerBootstrap bootstrap = new ServerBootstrap(); + Channel server = + bootstrap + .group(transport.eventLoopGroup()) + .channel(transport.serverChannel()) + .childHandler(new ConnectionAcceptor(sslContext)) + .bind(host, port) + .sync() + .channel(); + logger.info("\n==> Server is listening on {}:{}", host, port); + server.closeFuture().sync(); + } + + private static class ConnectionAcceptor extends ChannelInitializer { + private final SslContext sslContext; + private final WebSocketDecoderConfig webSocketDecoderConfig; + + ConnectionAcceptor(SslContext sslContext) { + this.sslContext = sslContext; + this.webSocketDecoderConfig = + WebSocketDecoderConfig.newBuilder() + .allowMaskMismatch(true) + .expectMaskedFrames(false) + .maxFramePayloadLength(65_535) + .withUTF8Validator(false) + .build(); + } + + @Override + protected void initChannel(SocketChannel ch) { + HttpServerCodec http1Codec = new HttpServerCodec(); + HttpObjectAggregator http1Aggregator = new HttpObjectAggregator(65536); + WebSocketCallbacksHandler webSocketHandler = new WebSocketServerCallbacksHandler(); + WebSocketServerProtocolHandler webSocketProtocolHandler = + WebSocketServerProtocolHandler.create() + .path("/echo") + .decoderConfig(webSocketDecoderConfig) + .webSocketCallbacksHandler(webSocketHandler) + .build(); + + ChannelPipeline pipeline = ch.pipeline(); + if (sslContext != null) { + SslHandler sslHandler = sslContext.newHandler(ch.alloc()); + pipeline.addLast(sslHandler); + } + pipeline.addLast(http1Codec).addLast(http1Aggregator).addLast(webSocketProtocolHandler); + } + } + + private static class WebSocketServerCallbacksHandler + implements WebSocketCallbacksHandler, WebSocketFrameListener { + private WebSocketFrameFactory.BulkEncoder bulkEncoder; + private ByteBuf outbuffer; + + @Override + public WebSocketFrameListener exchange( + ChannelHandlerContext ctx, WebSocketFrameFactory frameFactory) { + this.bulkEncoder = frameFactory.bulkEncoder(); + return this; + } + + @Override + public void onOpen(ChannelHandlerContext ctx) { + outbuffer = ctx.alloc().buffer(4096, 4096); + } + + @Override + public void onClose(ChannelHandlerContext ctx) { + ByteBuf out = outbuffer; + if (out != null) { + outbuffer = null; + out.release(); + } + } + + @Override + public void onChannelRead( + ChannelHandlerContext ctx, boolean finalFragment, int rsv, int opcode, ByteBuf payload) { + if (opcode != WebSocketProtocol.OPCODE_BINARY) { + payload.release(); + throw new IllegalStateException("received non-binary opcode"); + } + WebSocketFrameFactory.BulkEncoder encoder = bulkEncoder; + ByteBuf out = outbuffer; + int payloadSize = payload.readableBytes(); + + int outSize = encoder.sizeofBinaryFrame(payloadSize); + if (outSize > out.capacity() - out.writerIndex()) { + ctx.write(out, ctx.voidPromise()); + out = outbuffer = ctx.alloc().buffer(4096, 4096); + } + + int mask = encoder.encodeBinaryFramePrefix(out, payloadSize); + out.writeBytes(payload); + payload.release(); + encoder.maskBinaryFrame(out, mask, payloadSize); + } + + @Override + public void onChannelReadComplete(ChannelHandlerContext ctx) { + ByteBuf out = outbuffer; + if (out.readableBytes() > 0) { + ctx.writeAndFlush(out, ctx.voidPromise()); + this.outbuffer = ctx.alloc().buffer(4096, 4096); + } + } + + @Override + public void onChannelWritabilityChanged(ChannelHandlerContext ctx) { + if (!ctx.channel().isWritable()) { + ctx.flush(); + } + } + + @Override + public void onExceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + if (cause instanceof IOException) { + return; + } + logger.info("Unexpected websocket error", cause); + ctx.close(); + } + + @Override + public void onUserEventTriggered(ChannelHandlerContext ctx, Object evt) {} + } +} diff --git a/netty-websocket-http1-test/gradle.lockfile b/netty-websocket-http1-test/gradle.lockfile index 30d9157..4fe4bae 100644 --- a/netty-websocket-http1-test/gradle.lockfile +++ b/netty-websocket-http1-test/gradle.lockfile @@ -1,34 +1,34 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -ch.qos.logback:logback-classic:1.2.11=testRuntimeClasspath -ch.qos.logback:logback-core:1.2.11=testRuntimeClasspath +ch.qos.logback:logback-classic:1.2.12=testRuntimeClasspath +ch.qos.logback:logback-core:1.2.12=testRuntimeClasspath com.google.code.findbugs:jsr305:3.0.2=googleJavaFormat1.6 com.google.errorprone:error_prone_annotations:2.0.18=googleJavaFormat1.6 com.google.errorprone:javac-shaded:9+181-r4173-1=googleJavaFormat1.6 com.google.googlejavaformat:google-java-format:1.6=googleJavaFormat1.6 com.google.guava:guava:22.0=googleJavaFormat1.6 com.google.j2objc:j2objc-annotations:1.1=googleJavaFormat1.6 -io.netty:netty-buffer:4.1.93.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-codec-http:4.1.93.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-codec:4.1.93.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-common:4.1.93.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-handler:4.1.93.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-resolver:4.1.93.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-transport-classes-epoll:4.1.93.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-transport-classes-kqueue:4.1.93.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-transport-native-unix-common:4.1.93.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-transport:4.1.93.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-buffer:4.1.96.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-codec-http:4.1.96.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-codec:4.1.96.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-common:4.1.96.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-handler:4.1.96.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-resolver:4.1.96.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-transport-classes-epoll:4.1.96.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-transport-classes-kqueue:4.1.96.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-transport-native-unix-common:4.1.96.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-transport:4.1.96.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath net.bytebuddy:byte-buddy:1.12.21=testCompileClasspath,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath org.assertj:assertj-core:3.24.2=testCompileClasspath,testRuntimeClasspath org.codehaus.mojo:animal-sniffer-annotations:1.14=googleJavaFormat1.6 -org.junit.jupiter:junit-jupiter-api:5.9.3=testCompileClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter-engine:5.9.3=testRuntimeClasspath -org.junit.jupiter:junit-jupiter-params:5.9.3=testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-commons:1.9.3=testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-engine:1.9.3=testRuntimeClasspath -org.junit:junit-bom:5.9.3=testCompileClasspath,testRuntimeClasspath -org.opentest4j:opentest4j:1.2.0=testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-api:5.10.0=testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-engine:5.10.0=testRuntimeClasspath +org.junit.jupiter:junit-jupiter-params:5.10.0=testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-commons:1.10.0=testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-engine:1.10.0=testRuntimeClasspath +org.junit:junit-bom:5.10.0=testCompileClasspath,testRuntimeClasspath +org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath org.slf4j:slf4j-api:1.7.36=compileClasspath,testCompileClasspath,testRuntimeClasspath empty=annotationProcessor,testAnnotationProcessor diff --git a/netty-websocket-http1-test/src/test/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketCodecTest.java b/netty-websocket-http1-test/src/test/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketCodecTest.java index b750ed2..e4a4dd7 100644 --- a/netty-websocket-http1-test/src/test/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketCodecTest.java +++ b/netty-websocket-http1-test/src/test/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketCodecTest.java @@ -537,17 +537,6 @@ public void onChannelRead( } } - @Override - public void onChannelWritabilityChanged(ChannelHandlerContext ctx) { - boolean writable = ctx.channel().isWritable(); - if (sentFrames > 0 && writable) { - int toSend = framesCount - sentFrames; - if (toSend > 0) { - sendFrames(ctx, toSend); - } - } - } - @Override public void onOpen(ChannelHandlerContext ctx) { this.ctx = ctx; @@ -600,9 +589,6 @@ private void sendFrames(ChannelHandlerContext c, int toSend) { outBuffer = c.alloc().buffer(bufferSize, bufferSize); if (c.channel().bytesBeforeUnwritable() < readableBytes) { c.writeAndFlush(out, c.voidPromise()); - if (!c.channel().isWritable()) { - return; - } } else { c.write(out, c.voidPromise()); } @@ -615,7 +601,12 @@ private void sendFrames(ChannelHandlerContext c, int toSend) { frameEncoder.maskBinaryFrame(out, mask, payloadSize); sentFrames++; } - c.flush(); + ByteBuf out = outBuffer; + if (out.readableBytes() > 0) { + c.writeAndFlush(out, c.voidPromise()); + } else { + c.flush(); + } } } diff --git a/netty-websocket-http1/gradle.lockfile b/netty-websocket-http1/gradle.lockfile index 13ce193..0844f35 100644 --- a/netty-websocket-http1/gradle.lockfile +++ b/netty-websocket-http1/gradle.lockfile @@ -7,13 +7,13 @@ com.google.errorprone:javac-shaded:9+181-r4173-1=googleJavaFormat1.6 com.google.googlejavaformat:google-java-format:1.6=googleJavaFormat1.6 com.google.guava:guava:22.0=googleJavaFormat1.6 com.google.j2objc:j2objc-annotations:1.1=googleJavaFormat1.6 -io.netty:netty-buffer:4.1.93.Final=compileClasspath -io.netty:netty-codec-http:4.1.93.Final=compileClasspath -io.netty:netty-codec:4.1.93.Final=compileClasspath -io.netty:netty-common:4.1.93.Final=compileClasspath -io.netty:netty-handler:4.1.93.Final=compileClasspath -io.netty:netty-resolver:4.1.93.Final=compileClasspath -io.netty:netty-transport-native-unix-common:4.1.93.Final=compileClasspath -io.netty:netty-transport:4.1.93.Final=compileClasspath +io.netty:netty-buffer:4.1.96.Final=compileClasspath +io.netty:netty-codec-http:4.1.96.Final=compileClasspath +io.netty:netty-codec:4.1.96.Final=compileClasspath +io.netty:netty-common:4.1.96.Final=compileClasspath +io.netty:netty-handler:4.1.96.Final=compileClasspath +io.netty:netty-resolver:4.1.96.Final=compileClasspath +io.netty:netty-transport-native-unix-common:4.1.96.Final=compileClasspath +io.netty:netty-transport:4.1.96.Final=compileClasspath org.codehaus.mojo:animal-sniffer-annotations:1.14=googleJavaFormat1.6 empty=annotationProcessor diff --git a/perf_bulkclient_run.sh b/perf_bulkclient_run.sh new file mode 100755 index 0000000..ba1d359 --- /dev/null +++ b/perf_bulkclient_run.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +cd netty-websocket-http1-perftest/build/install/netty-websocket-http1-perftest/bin && ./netty-websocket-http1-perftest-bulkclient \ No newline at end of file diff --git a/perf_bulkserver_run.sh b/perf_bulkserver_run.sh new file mode 100755 index 0000000..7e46eda --- /dev/null +++ b/perf_bulkserver_run.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +export NETTY_WEBSOCKET_HTTP1_PERFTEST_BULKSERVER_OPTS='--add-exports java.base/sun.security.x509=ALL-UNNAMED' + +cd netty-websocket-http1-perftest/build/install/netty-websocket-http1-perftest/bin && ./netty-websocket-http1-perftest-bulkserver \ No newline at end of file