intro
netty封裝了nio,利用責任鏈模式,組織了許多handler。
上圖就是典型的責任鏈模式。
我們將寫一個簡單的聊天程序,學習更多handler。
server
必須要先寫server端,因爲它可以用telnet單獨測試。
首先是啓動類,一堆模版代碼:
public class ChatServer {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class)
.childHandler(new ChatServerInitializer());
ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
channelFuture.channel().closeFuture().sync();
}
catch (InterruptedException e) {
e.printStackTrace();
}
finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
我們只需看一下自己定義的初始化器就行了:
public class ChatServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter()));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new ChatServerInboundHandler());
}
}
這裏有一個新的handler:DelimiterBasedFrameDecoder
。
如果有多個分隔符,這種基於frame的decoder會選擇將一行分割成最小的frame。
比如ABC\nDEF\r\n
,有\r
和\n
兩種分隔符,選擇\n
會得到更小的單元。
然後是具體的處理邏輯,它在自定義的inboundhandler中:
public class ChatServerInboundHandler extends SimpleChannelInboundHandler<String> {
private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
Channel channel = ctx.channel();
channelGroup.forEach(ch -> {
if (channel != ch) {
ch.writeAndFlush(channel.remoteAddress() + " 發送消息: " + msg + "\n");
}
else {
channel.writeAndFlush("自己發的消息:" + msg + "\n");
}
});
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
channelGroup.writeAndFlush("用戶-" + channel.remoteAddress() + "加入\n");
channelGroup.add(channel);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
channelGroup.writeAndFlush("服務器-" + channel.remoteAddress() + "離開\n");
channelGroup.remove(channel);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
System.out.println(channel.remoteAddress() + "上線");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
System.out.println(channel.remoteAddress() + "下線");
}
}
這其實就是channel的生命週期。一個channel表示與客戶端的一次連接。
要注意的就是ChannelGroup
:
/**
* A thread-safe {@link Set} that contains open {@link Channel}s and provides
* various bulk operations on them. Using {@link ChannelGroup}, you can
* categorize {@link Channel}s into a meaningful group (e.g. on a per-service
* or per-state basis.) A closed {@link Channel} is automatically removed from
* the collection, so that you don't need to worry about the life cycle of the
* added {@link Channel}. A {@link Channel} can belong to more than one
* {@link ChannelGroup}.
*
*/
首先它是個set,所以我們可以遍歷。
然後它的作用是把一個channel加入到一個有意義的group,比如per-state basis
這樣一個group。
它會把斷了的channel自動移除掉。所以channelGroup.remove(channel);
是可以不用寫的。
當你調用channelGroup.writeAndFlush
,它會向group中的每一個channel都發消息。
server端寫好了,我們啓動它,並拿telnet測試:
我開了三個窗口連接server,所以服務端的打印是:
第一個連的人會得到兩次服務端返回的消息:
我輸入一個字符串:
自己:
別人:
這其實已經可以用了。但是我們還是要完成客戶端的java代碼。
client
ublic class ChatClient {
public static void main(String[] args) {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
.handler(new ChatClientInitializer());
Channel channel = bootstrap.connect("localhost", 8899).channel();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
for (; ; ) {
channel.writeAndFlush(bufferedReader.readLine() + "\r\n");
}
}
catch (Exception e) {
e.printStackTrace();
}
finally {
eventLoopGroup.shutdownGracefully();
}
}
}
這是客戶端的主類。
客戶端連上服務器之後,就會去獲取控制檯的輸入,然後發給服務器。
客戶端的初始化器是一樣的:
public class ChatClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter()));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new ChatClientHandler());
}
}
客戶端的處理邏輯就是打印服務端發過來的消息:
public class ChatClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg);
}
}
開多個client連接服務器,會得到和telnet連接一樣的效果。