一、概述
netty作爲服務端時,可以連接多個客戶端。利用此特性,可以開發一個簡單的羣聊應用。包含以下功能:
- 服務端記錄接入
- 上線下線提醒
- 消息羣發
功能分析:
- 服務端記錄接入: netty的handle中SimpleChannelInboundHandler的channelActive方法可以監聽channel是否接入,利用此回調方法來監聽客戶端是否接入
- 上線下線提醒:handleAdded方法可以監聽接入的客戶端是否活動,此方法可以監聽上線與下線
- 消息羣發:channelRead0可以實現服務端和客戶端的相互通信,客戶端利用channelRead0中的ChannelHandlerContext對象向服務端發送消息,服務端接收到消息後利用ChannelHandlerContext向客戶端回寫消息;ChannelGroup用於存儲當前與服務端連接的客戶端channel,遍歷該group便可以發送不同的消息給客戶端
二、代碼實現
1. 服務端
1.1 主程序
package com.learner.netty.ch3.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.nio.channels.SocketChannel;
/**
* @Desc:
* @author: [email protected]
* @date: 2019-12-04
*/
public class Server {
public static void main(String[] args) throws InterruptedException {
// 1. 創建兩個線程組
EventLoopGroup boosGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
// 2. 創建ServerBootStrap
ServerBootstrap bootstrap = new ServerBootstrap();
ChannelFuture channelFuture = bootstrap.group(boosGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ServerInitializer())
.bind(8899)
.sync();
channelFuture.channel().closeFuture().sync();
} finally {
// 3. 關閉
boosGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
1.2 ServerInitializer
package com.learner.netty.ch3.server;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
/**
* @Desc:
* @author: [email protected]
* @date: 2019-12-04
*/
public class ServerInitializer 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 ServerHandle());
}
}
1.3 ServerHandle
package com.learner.netty.ch3.server;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.util.logging.SocketHandler;
/**
* @Desc:
* @author: [email protected]
* @date: 2019-12-04
*/
public class ServerHandle extends SimpleChannelInboundHandler<String> {
/** **/
private static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
// 1. 獲取channel
Channel channel = ctx.channel();
channels.forEach(ch -> {
if (ch != channel) {
ch.writeAndFlush("【" + channel.remoteAddress() + "】:" + msg + "\n");
} else {
ch.writeAndFlush("【自己】發送的消息:" + msg + "\n");
}
});
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
// 1. 獲取channel
Channel channel = ctx.channel();
// 2. 服務器發送通知
channels.writeAndFlush("【服務器】 - " + channel.remoteAddress() + " 加入\n");
// 3. 將新的channel添加至channelGroup
channels.add(channel);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
// 1. 獲取channel
Channel channel = ctx.channel();
// 2. 服務器發送通知
channels.writeAndFlush("【服務器】 - " + channel.remoteAddress() + " 離開\n");
}
@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() + " 下線");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
2. 客戶端
2.1 客戶端主程序
package com.learner.netty.ch3.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* @Desc:
* @author: [email protected]
* @date: 2019-12-07
*/
public class Client {
public static void main(String[] args) throws IOException, InterruptedException {
// 1. 創建EventLoopGroup
EventLoopGroup group = new NioEventLoopGroup();
try {
// 2. 創建BootStrap
Bootstrap bootstrap = new Bootstrap();
// 3. 註冊initializer,handle
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ClientInitializer());
// 4. 獲取channel
Channel channel = bootstrap.connect("localhost", 8899).sync().channel();
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
for(;;) {
channel.writeAndFlush( reader.readLine() + "\r\n");
}
} finally {
group.shutdownGracefully();
}
}
}
2.2 ClientInitializer
package com.learner.netty.ch3.client;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
/**
* @Desc:
* @author: [email protected]
* @date: 2019-12-07
*/
public class ClientInitializer 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 StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new ClientHandle());
}
}
2.3 ClientHandle
package com.learner.netty.ch3.client;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* @Desc:
* @author: [email protected]
* @date: 2019-12-07
*/
public class ClientHandle extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg);
}
}
啓動多個客戶端,在控制檯輸入消息,便可以接收到信息了