【前言】
之前想写压测工具来着,但是没有很好的分析工具,所以没什么动力写。最近学会了用阿尔萨斯赶紧把压测工具补上了,疯狂的压榨服务器性能。用Netty已有的连接池模型,可以省去自己维护连接保活的工作。本来写了5个类,整理了一下代码发现非常简单,基础类就一个。
【连接池】
忘记自己借鉴了谁的代码,客户端连接池采用Netty的ChannelPoolMap接口,用网络连接地址做Key,用FixedChannelPool实例化value,即不同的连接服务地址对应不同的连接池。FixedChannelPool的理论连接数上限是Integer.MAX_VALUE,并且使用ChannelHealthChecker接口来判断channel被取出池的时候是否是活的,如果是活的才向应用层吐出去。这样一来保活问题就不用自己操心了。
public class TcpClientPool {
final EventLoopGroup group = new NioEventLoopGroup();
final Bootstrap bootstrap = new Bootstrap();
private static final int thread_num = Runtime.getRuntime().availableProcessors();
// key 是地址, value是pool,即一个地址一个pool
private AbstractChannelPoolMap poolMap;
public void build(ChannelPoolHandler poolHandler) throws Exception {
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.SO_KEEPALIVE, true);
poolMap = new AbstractChannelPoolMap() {
@Override
protected SimpleChannelPool newPool(InetSocketAddress key) {
return new FixedChannelPool(bootstrap.remoteAddress(key), poolHandler, thread_num);
}
};
}
/* 下面的代码省略 */
}
构造方法需要传入一个实现了ChannelPoolHandler接口处理Handler,这个Handler需要实现三个方法
void channelReleased(Channel ch) throws Exception;
void channelAcquired(Channel ch) throws Exception;
void channelCreated(Channel ch) throws Exception;
在处理类中最重要的事情是给channel加载业务协议的编解码处理器
public abstract class BaseChannelPoolHandler implements ChannelPoolHandler {
private HandlerConfiguratorInterface configurator;
public BaseChannelPoolHandler(HandlerConfiguratorInterface configurator) {
this.configurator = configurator;
}
/**
* 因为是裸的channel,所以需要给他配置上编解码器
* 只需要配置一次就可以,因为channel会被还回到池里
*/
@Override
public void channelCreated(Channel ch) throws Exception {
configurator.configChannel(ch);
}
@Override
public void channelReleased(Channel ch) throws Exception {}
@Override
public void channelAcquired(Channel ch) throws Exception {}
}
对于Http的实现
public class HttpConfigurator implements HandlerConfiguratorInterface {
private static final int HTTP_AGGREGATE_SIZE = 8192;
@Override
public void configChannel(Channel ch) {
SocketChannel channel = (SocketChannel)ch;
channel.config().setKeepAlive(true);
channel.config().setTcpNoDelay(true);
channel.pipeline()
.addLast(new HttpClientCodec())
.addLast(new HttpObjectAggregator(HTTP_AGGREGATE_SIZE))
.addLast(new HttpResponseHandler());
}
}
这一步和常见的Netty处理器挂载方式是一致的。最后一个HttpResponseHandler是处理应答的Handler。
【关闭连接池】
客户端池还需要提供关闭的能力,否则程序无法正常退出
public void close() {
poolMap.close();
group.shutdownGracefully();
}
客户端池封装了异步和同步发消息的方法
异步方法
public void asyncWriteMessage(InetSocketAddress address, Object message) {
SimpleChannelPool pool = getPool(address);
Future future = pool.acquire();
// 获取到实例后发消息
future.addListener((FutureListener)f -> {
if (f.isSuccess()) {
Channel ch = f.getNow();
if (ch.isWritable()) {
ch.writeAndFlush(message);
}
// 归还实例
pool.release(ch);
}
});
}
public boolean syncWriteMessage(InetSocketAddress address, Object message) {
SimpleChannelPool pool = getPool(address);
Future future = pool.acquire();
try {
Channel channel = future.get();
if (channel.isWritable()) {
channel.writeAndFlush(message);
pool.release(channel);
return true;
}
pool.release(channel);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
如果要发HTTP消息,需要自己封装Http消息体,否则Netty编码器会扔掉
public class HttpMsgComposer {
public static Object compose(URI uri, String msg) throws Exception {
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
uri.toASCIIString(), Unpooled.wrappedBuffer(msg.getBytes("UTF-8")));
// 构建http请求
request.headers().set(HttpHeaderNames.HOST, uri.getHost());
request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
request.headers().set(HttpHeaderNames.CONTENT_LENGTH, request.content().readableBytes());
request.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json");
return request;
}
}
public class App {
public static void main(String[] args) throws Exception {
TcpClientPool pool = new TcpClientPool();
pool.build(new HttpChannelPoolHandler(new HttpConfigurator()));
String url = "http://163.com";
URI uri = new URI(url);
String host = uri.getHost();
int port = uri.getPort() == -1 ? 80 : uri.getPort();
InetSocketAddress address = new InetSocketAddress(host, port);
for (int i = 0; i < 10; i++) {
pool.asyncWriteMessage(address, HttpMsgComposer.compose(uri, "Hello World"));
}
while (true) {
Thread.sleep(1000L);
pool.close();
break;
}
}
}
1. github上发布代码
2. 支持UDP的客户端
3. 支持websocket
4. 支持Protocol Buff
5. 支持MQTT