我参与过多个基于netty的项目, 他们对原生Netty组件的使用方式五花八门,风格迥异, 各种曲线救国,各种妥协, 各种凌乱, 并不是说这样不正确, 只是如果无法建立统一的使用范式, 将会浪费很多沟通,协调,学习, 百度的时间, 为此Nettyx提供了基础的TCP通信模板, 并分成客户端(client)和服务端(server), 意欲为netty的使用建立一种范式,有范式总比没有好, 说不定没有各位大佬手中的模板好,手动狗头保命
直接上依赖:
请从maven中央仓获取{lastest.version},最新版本号
<dependency>
<groupId>io.github.fbbzl</groupId>
<artifactId>nettyx</artifactId>
<version>{lastest.version}</version>
</dependency>
一. 客户端模板
Nettyx根据channel载量提供了两种tcp客户端,一种是单channel的客户端, 一种是多channel的客户端. 单channel顾名思义, 一个client组件中只保存了一个channel, 而多channel客户端通常是用来保存一组相同功能的channel.
1. SingleTcpChannelTemplate
Nettyx提供的单通道客户端基类SingleTcpChannelTemplate, 提供了一些通用的抽象方法,以下展示了一个简单的单通道客户端实现
@Slf4j
public class TestSingleTcpClient extends SingleTcpChannelTemplate {
// 为了演示, 此处定义了一个定时器 , 用来在连接成功之后发送消息给对端
static ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
// 保证初始化远端地址即可
public TestSingleTcp(InetSocketAddress address) {
super(address);
}
// channel初始化器, 各位根据自己业务自行创建
@Override
protected ChannelInitializer<NioSocketChannel> channelInitializer() {
return new TestChannelInitializer<>();
}
// 简简单单写个main进行测试
public static void main(String[] args) {
// 先创建当前单通道客户端的实例, 并在构造函数中指定要server的地址
TestSingleTcp testClient = new TestSingleTcp(new InetSocketAddress("3.11.134.88", 9888));
// 后续会详细介绍Nettyx对监听器的扩展, 这里先看下如何使用
ChannelFutureListener listener = new ActionChannelFutureListener()
.whenSuccess((ls, cf) -> {
executor.scheduleAtFixedRate(() -> {
byte[] msg = new byte[300];
Arrays.fill(msg, (byte) 1);
testClient.writeAndFlush(Unpooled.wrappedBuffer(msg));
}, 2, 30, TimeUnit.MILLISECONDS);
System.err.println(cf.channel().localAddress() + ": ok");
})
.whenCancel((ls, cf) -> System.err.println("cancel"))
// redo为ActionChannelFutureListener中的静态方法, 直接静态引入即可, 用来重试的方法
.whenFailure(redo(testClient::connect, 2, SECONDS))
.whenDone((ls, cf) -> System.err.println("done"));
// 单通道客户端的connect方法返回channelfuture, 我们这里添加监听器即可
testClient.connect().addListener(listener);
}
}
以上便实现了一个单通道的客户端, 调用connect方法即可开始使用此客户端. 此客户端亦可成为spring的bean, 单通道客户端的实现是线程安全的, 当成类似于HttpTemplate, RestTemplate一样去使用即可
2. MultiTcpChannelTemplate
讲完单通道, 接下来讲多通道客户端, 部分业务场景需要你保存一组功能相同的channel, 针对此场景Nettyx提供了MultiTcpChannelTemplate 泛型为key的类型, 此类所有方法都需要你带上相应的Key,以便内部检索出对应的channel来使用,来看一个简单的MultiTcpChannelTemplate实现
public class TestMultiTcp extends MultiTcpChannelTemplate<String> {
static ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
protected TestMultiTcp(Map<String, InetSocketAddress> inetSocketAddressMap) {
super(inetSocketAddressMap);
}
// 因为设置为同一组相同功能的channel, channel初始化器并没有根据key去进行特例化设置
@Override
protected ChannelInitializer<NioSocketChannel> channelInitializer() {
return new TestChannelInitializer<>();
}
// 对channel进行设置
@Override
protected void doChannelConfig(String key, SocketChannelConfig channelConfig) {
// 进行设置, 可以根据key进行独有的设置
super.doChannelConfig(key, channelConfig);
}
// 也是简单写个main测试下
public static void main(String[] args) {
Map<String, InetSocketAddress> map = new HashMap<>(2);
// 在构造TestMultiTcp 时需要你传入, key和远端地址的映射
map.put("a", new InetSocketAddress(9888));
map.put("b", new InetSocketAddress(9887));
TestMultiTcp testMultiTcp = new TestMultiTcp(map);
ChannelFutureListener listener = new ActionChannelFutureListener()
.whenSuccess((l, cf) -> {
executor.scheduleAtFixedRate(() -> {
byte[] msg = new byte[300];
Arrays.fill(msg, (byte) 1);
cf.channel().writeAndFlush(Unpooled.wrappedBuffer(msg));
}, 2, 30, TimeUnit.MILLISECONDS);
System.err.println(cf.channel().localAddress() + ": ok");
})
.whenCancel((l, cf) -> System.err.println("cancel"))
.whenFailure(redo(cf -> testMultiTcp.connect(channelKey(cf)), 2, SECONDS))
.whenDone((l, cf) -> System.err.println("done"));
testMultiTcp.connectAll().values().forEach(c -> c.addListener(listener));
}
}
至此我们完成了单通道和多通道tcp客户端的创建
二. 服务端模板
相较于客户端, 服务端因为自身的特性, 导致它并没有multiserver此类的封装, Nettyx暂时只提供了TcpServerTemplate一种模板
1. TcpServerTemplate
如下展示了一个简单的TCP服务端, 它继承自Nettyx中的模板TcpServerTemplate 实现指定方法后即可使用
public class TestServer extends TcpServerTemplate {
public TestServer(SocketAddress bindAddress) {
super(bindAddress);
}
public TestServer(int bindPort) {
super(bindPort);
}
public static void main(String[] args) {
TestServer testServer = new TestServer(9888);
ChannelFuture bindFuture = testServer.bind();
bindFuture.addListener(cf -> System.err.println("binding state:" + cf.isSuccess()));
bindFuture.channel().closeFuture().addListener(cf -> {
System.err.println("关闭了");
testServer.shutdownGracefully();
});
}
@Override
protected ChannelInitializer<SocketChannel> childChannelInitializer() {
return new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) {
InboundAdvice inboundAdvice = new InboundAdvice(channel)
.whenExceptionCaught((c, t) -> System.err.println("in error: [" + t + "]"));
OutboundAdvice outboundAdvice = new OutboundAdvice(channel)
.whenExceptionCaught((c, t) -> System.err.println("out error: [" + t + "]"));
channel.pipeline().addLast(
outboundAdvice
, new StartEndFlagFrameCodec(320, true, wrappedBuffer(new byte[]{(byte) 0x7e}))
, new EscapeCodec(EscapeMap.mapHex("7e", "7d5e"))
, new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object in) throws Exception {
byte[] msg = new byte[300];
Arrays.fill(msg, (byte) 1);
ctx.channel().writeAndFlush(Unpooled.wrappedBuffer(msg));
super.channelRead(ctx, in);
}
}
, new UserCodec()
, inboundAdvice);
}
};
}
}