【前言】
之前想寫壓測工具來着,但是沒有很好的分析工具,所以沒什麼動力寫。最近學會了用阿爾薩斯趕緊把壓測工具補上了,瘋狂的壓榨服務器性能。用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<InetSocketAddress, SimpleChannelPool> 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<InetSocketAddress, SimpleChannelPool>() {
@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 {}
}
其中實現HandlerConfiguratorInterface接口(自定義的接口,只有一個方法public void configChannel(Channel channel);)的類,需要通過configChannel方法給Channel對象裝配編解碼器
對於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<Channel> future = pool.acquire();
// 獲取到實例後發消息
future.addListener((FutureListener<Channel>)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<Channel> 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