服务器准备
程序准备
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>NettyTest</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.112.Final</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 主动声明插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<!-- 绑定生命周期 -->
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<!-- 设置依赖的存放路径 -->
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
logback.xml
<configuration debug="false">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="${logLevel:-info}">
<appender-ref ref="STDOUT" />
</root>
</configuration>
服务端代码
org.example.counter.server.Main
package org.example.counter.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.AbstractByteBufAllocator;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.LineEncoder;
import io.netty.handler.codec.string.LineSeparator;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.AttributeKey;
import io.netty.util.CharsetUtil;
import org.example.counter.client.CounterClientRequestHandler;
import org.example.counter.client.CounterClientResponeHandler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static java.lang.System.out;
// 按两次 Shift 打开“随处搜索”对话框并输入 `show whitespaces`,
// 然后按 Enter 键。现在,您可以在代码中看到空格字符。
public class Main {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
CounterServerHandler serverHandler = new CounterServerHandler();
new ServerBootstrap()
.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
// .option(ChannelOption.SO_BACKLOG, 1024000)
.handler(new LoggingHandler(LogLevel.INFO))
.childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
.childOption(ChannelOption.TCP_NODELAY, false)
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
ChannelPipeline pipeline = ch.pipeline();
// 解决粘包,通过长度字段来拆包和封包
// Decoders
pipeline.addLast("frameDecoder", new LineBasedFrameDecoder(8192));
pipeline.addLast("stringDecoder", new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(serverHandler );
// Encoder
pipeline.addLast("lineEncoder", new LineEncoder(LineSeparator.UNIX, CharsetUtil.UTF_8));
}
}).bind(7070);
}
}
org.example.counter.server.CounterServerHandler
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you 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:
*
* https://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 org.example.counter.server;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Handler implementation for the echo server.
*/
@Sharable
public class CounterServerHandler extends ChannelInboundHandlerAdapter {
private final AtomicInteger counter = new AtomicInteger(0);
public void reset() {
counter.set(0);
}
public String getCount() {
return String.valueOf(counter.get());
}
public String incrementAndGet() {
return String.valueOf(counter.incrementAndGet());
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// System.out.println("receive msg: " + msg);
if(msg instanceof String)
{
if(msg.equals("reset"))
{
reset();
ctx.channel().write("reset success");
}
else if(msg.equals("get"))
{
ctx.channel().write(getCount());
}
else if(msg.equals("incr"))
{
ctx.channel().write( incrementAndGet() );
}
else {
ctx.channel().write("unknow command");
}
}
else {
ctx.channel().write("unknow command");
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// Close the connection when an exception is raised.
cause.printStackTrace();
ctx.close();
}
}
客户端代码
org.example.counter.client.Main
package org.example.counter.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.*;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.LineEncoder;
import io.netty.handler.codec.string.LineSeparator;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.AttributeKey;
import io.netty.util.CharsetUtil;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.atomic.AtomicLong;
// 按两次 Shift 打开“随处搜索”对话框并输入 `show whitespaces`,
// 然后按 Enter 键。现在,您可以在代码中看到空格字符。
public class Main {
private static long LIMIT = Runtime.getRuntime().freeMemory() / 1024 / 2;
static
{
System.out.println("背压上限: " + LIMIT );
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
int requests = args.length > 0 ? Integer.parseInt(args[0]) : 1;
int connections = args.length > 1 ? Integer.parseInt(args[1]) : 1;
int threads = args.length > 2 ? Integer.parseInt(args[2]) : 1;
NioEventLoopGroup group = new NioEventLoopGroup(threads);
Bootstrap bootstrap = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, false)
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.attr(CounterClientRequestHandler.getQueueKey()).set(new LinkedTransferQueue<>());
ChannelPipeline pipeline = ch.pipeline();
// 解决粘包,通过长度字段来拆包和封包
// Decoders
pipeline.addLast("frameDecoder", new LineBasedFrameDecoder(8192));
pipeline.addLast("stringDecoder", new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new CounterClientResponeHandler() );
// Encoder
pipeline.addLast("lineEncoder", new LineEncoder(LineSeparator.UNIX, CharsetUtil.UTF_8));
pipeline.addLast(new CounterClientRequestHandler() );
}
});
ChannelFuture[] channels = new ChannelFuture[connections];
for (int i = 0; i < connections; i++) {
// channels[i] = bootstrap.connect("127.0.0.1", 7070).sync();
channels[i] = bootstrap.connect("192.168.253.201", 7070).sync();
}
System.out.println("建立了 " + connections + " 个连接");
{
for (int i = 0; i < connections; i++) {
CompletableFuture<String> responseFuture = new CompletableFuture<>();
channels[i].channel().writeAndFlush(new Request("reset", responseFuture));
System.out.println(responseFuture.get());
}
}
System.out.println("验证了 " + connections + " 个连接");
CountDownLatch latch = new CountDownLatch(requests);
AtomicLong count = new AtomicLong();
long start = System.currentTimeMillis();
for (int i = 0; i < requests; i++) {
CompletableFuture<String> responseFuture = new CompletableFuture<>();
channels[ i % connections ].channel().writeAndFlush(new Request("incr",responseFuture));
count.incrementAndGet();
responseFuture.whenComplete((s, throwable) -> {
latch.countDown();
count.decrementAndGet();
if (throwable != null)
{
throwable.printStackTrace();
}else
{
if( latch.getCount() % 1000000 == 0 ) {
System.out.println("response " + s);
}
}
});
while ( count.get() > LIMIT )
{
Thread.yield();
}
}
latch.await();
long end = System.currentTimeMillis();
long delta = end -start;
for (int i = 0; i < connections; i++) {
channels[i].channel().close().sync();
}
System.out.println("关闭了 " + connections + " 个连接");
System.out.println();
System.out.println("请求 " + requests + " 个");
System.out.println("耗时 " + delta + " ms");
System.out.println("QPS " + requests * 1000L / delta + " 次/秒");
System.exit(0);
}
}
org.example.counter.client.Request
package org.example.counter.client;
import java.util.concurrent.CompletableFuture;
public class Request {
public final String msg;
public final CompletableFuture<String> future;
public Request(String msg, CompletableFuture<String> future) {
this.msg = msg;
this.future = future;
}
}
org.example.counter.client.CounterClientRequestHandler
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you 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:
*
* https://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 org.example.counter.client;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedTransferQueue;
/**
* Handler implementation for the echo server.
*/
@Sharable
public class CounterClientRequestHandler extends ChannelOutboundHandlerAdapter {
private static final AttributeKey<LinkedTransferQueue<CompletableFuture<String>>>
queueKey = AttributeKey.valueOf("queue");
public static AttributeKey<LinkedTransferQueue<CompletableFuture<String>>> getQueueKey()
{
return queueKey;
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
Attribute<LinkedTransferQueue<CompletableFuture<String>>> queue =ctx.channel().attr(queueKey);
if(msg instanceof Request )
{
queue.get().put(((Request) msg).future);
ctx.write(((Request) msg).msg, promise);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// Close the connection when an exception is raised.
cause.printStackTrace();
ctx.close();
}
}
org.example.counter.client.CounterClientResponeHandler
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you 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:
*
* https://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 org.example.counter.client;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedTransferQueue;
/**
* Handler implementation for the echo server.
*/
@Sharable
public class CounterClientResponeHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
Attribute<Object> future = ctx.channel().attr(AttributeKey.valueOf("queue"));
Object object = future.get();
if( object instanceof LinkedTransferQueue)
{
Object o = ((LinkedTransferQueue<?>) object).poll();
if( o instanceof CompletableFuture )
{
((CompletableFuture)o).complete(msg);
}
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// Close the connection when an exception is raised.
cause.printStackTrace();
ctx.close();
}
}
压测
服务端启动命令
nohup java -server -Djava.net.preferIPv4Stack=true -Dio.netty.leakDetectionLevel=advanced -Xmx3048m -Xms3048m -Xmn2848m -cp NettyTest-1.0-SNAPSHOT.jar:lib/* org.example.counter.server.Main &
客户端运行参数
压测结果