并发编程 | Netty - [简易群聊示例]
@
§1 需求
- 可以支持多人群聊
- 有客户端加入群聊时,会有广播通知
- 有客户端离开群聊时,会有广播通知
- 有客户端发言后,会有广播通知
§2 GeneralNettyChannelInitializer & NamableEntry
无论是 Server 端还是 Client 端,都需要配置 handler
在配置 handler 时,常规写法如下
- 匿名内部类的逻辑高度重复
- 不够优雅
bootstrap.group(boss,worker)
// 其他配置
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("name",new HttpServerCodec());
ch.pipeline().addLast("name",new NettySimpleHttpServerHandler());
ch.pipeline().addLast(new IdleStateHandler(1,2,3));
......
}
});
尝试通过下面的方式进行优化:
GeneralNettyChannelInitializer用于提供通用的ChannelInitializer- 提供静态方法获取示例,每次获取的实例都是新的,防止线程问题
NamableEntry用于提供对 name / handler 的映射- 支持有名 handler 和无名 handler 两种封装
- handler 使用 class 而不是具体实例
每次使用时会通过newInstance()获取示例,因此要求无参构造
可以防止线程问题
GeneralNettyChannelInitializer
public class GeneralNettyChannelInitializer extends ChannelInitializer<SocketChannel> {
private final Collection<NamableEntry<? extends ChannelHandler>> handlers = new ArrayList<>();
public GeneralNettyChannelInitializer add(NamableEntry<? extends ChannelHandler> entry) {
this.handlers.add(entry);
return this;
}
public static GeneralNettyChannelInitializer build(NamableEntry<? extends ChannelHandler> entry) {
GeneralNettyChannelInitializer initializer = new GeneralNettyChannelInitializer();
initializer.add(entry);
return initializer;
}
@Override
protected void initChannel(SocketChannel ch) throws Exception {
for(NamableEntry<? extends ChannelHandler> entry: handlers){
if(entry.isInstance()){
ch.pipeline().addLast(entry.getName(), entry.getConstructor().newInstance(entry.getParams()));
}else
ch.pipeline().addLast(entry.getName(),entry.getReal().newInstance());
}
}
}
NamableEntry
public class NamableEntry<T> {
private String name;
private Class<T> real;
private Constructor<T> constructor;
private Object[] params;
/* *******************************
* 以下是构造
******************************* */
private NamableEntry(Class<T> real){
this.real = real;
}
private NamableEntry(String name, Class<T> handler){
this.name = name;
this.real = handler;
}
public NamableEntry(Constructor<T> constructor, Object[] params) {
this.constructor = constructor;
this.params = params;
}
public NamableEntry(String name, Constructor<T> constructor, Object[] params) {
this.name = name;
this.constructor = constructor;
this.params = params;
}
/* *******************************
* 以下是工具
******************************* */
public boolean isNamed(){
return StrUtil.isNotEmpty(name);
}
public boolean isInstance() { return null!=constructor; }
public static<T> NamableEntry<T> named(String name, Class<T> real){
return new NamableEntry<T>(name,real);
}
public static<T> NamableEntry<T> unnamed(Class<T> real){
return new NamableEntry<T>(real);
}
public static<T> NamableEntry<T> named(String name, Constructor<T> constructor, Object[] params){
return new NamableEntry<T>(name,constructor,params);
}
public static<T> NamableEntry<T> unnamed(Constructor<T> constructor, Object[] params){
return new NamableEntry<T>(constructor,params);
}
/* *******************************
* 以下是 setter/getter
******************************* */
public String getName() {
return name;
}
public Class<T> getReal() {
return real;
}
public Constructor<T> getConstructor() {
return constructor;
}
public Object[] getParams() {
return params;
}
}
最终,调整配置方式为
ServerBootstrap starter = new ServerBootstrap()
.group(boss,workers)
// 其他配置
.childHandler(
GeneralNettyChannelInitializer
.build(NamableEntry.named("decoder",StringDecoder.class))
.add(NamableEntry.named("encoder", StringEncoder.class))
.add(NamableEntry.unnamed(NettyChatWorkerHandler.class))
.add(NamableEntry.unnamed(
IdleStateHandler.class.getConstructor(int.class,int.class,int.class),
new Object[]{1,2,3}))
.add(NamableEntry.named("heartStopEventHandler", NettyChatHeartStopEventHandler.class))
);
§3 示例
server
public class NettyChatServer {
public void start(int bossThreadCount, int workersThreadCount) {
EventLoopGroup boss = new NioEventLoopGroup(bossThreadCount);
EventLoopGroup workers = new NioEventLoopGroup(workersThreadCount);
try {
ServerBootstrap starter = new ServerBootstrap()
.group(boss,workers)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,128)
.childOption(ChannelOption.SO_KEEPALIVE,true)
.childHandler(
GeneralNettyChannelInitializer
.build(NamableEntry.named("decoder",StringDecoder.class))
.add(NamableEntry.named("encoder", StringEncoder.class))
.add(NamableEntry.unnamed(NettyChatWorkerHandler.class))
.add(NamableEntry.unnamed(
IdleStateHandler.class.getConstructor(int.class,int.class,int.class),
new Object[]{1,2,3}))
.add(NamableEntry.named("heartStopEventHandler", NettyChatHeartStopEventHandler.class))
);
ChannelFuture started = starter.bind(7777).sync();
System.out.println("Server..............................");
started.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
workers.shutdownGracefully();
}
}
public static void main(String[] args) {
new NettyChatServer().start(1,0);
}
}
server handler
public class NettyChatWorkerHandler extends SimpleChannelInboundHandler<String> {
private static final DefaultChannelGroup CHANNELS = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
/* *******************************
* 每增加一个 channel 每增加一个handler 时触发
*
* PS:正常一个 channel 应该具有若干个 handler
* 加入聊天的通知功能,应该只是某一个 handler 需要实现的功能
* 只是这个例子中,都用一个 handler 集成了,实际中不是每个 handlerAdded 时都需要此方法的实现
******************************* */
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
CHANNELS.writeAndFlush(ctx.channel().remoteAddress()+" entereing................");
CHANNELS.add(ctx.channel());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
CHANNELS.writeAndFlush(ctx.channel().remoteAddress()+" leveled................");
}
/* *******************************
* handler 活跃
* 与服务端连接成功时触发
******************************* */
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
CHANNELS.writeAndFlush(ctx.channel().remoteAddress()+" entered................");
CHANNELS.add(ctx.channel());
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
CHANNELS.writeAndFlush(msg, ChannelMatchers.isNot(ctx.channel()));
}
}
client
public class NettyChatClient {
public void start() {
EventLoopGroup client = new NioEventLoopGroup();
try (Scanner waiter = new Scanner(System.in)){
Bootstrap starter = new Bootstrap().group(client)
.channel(NioSocketChannel.class)
.handler(
GeneralNettyChannelInitializer
.build(NamableEntry.named("encoder", StringEncoder.class))
.add(NamableEntry.named("decoder", StringDecoder.class))
.add(NamableEntry.unnamed(NettyChatClientHandler.class))
);
ChannelFuture channelFuture = starter.connect("192.168.3.17", 7777).sync();
String msg = null;
while(true){
if(waiter.hasNextLine()){
msg = waiter.nextLine();
channelFuture.channel().writeAndFlush(msg);
System.out.println();
}else {
TimeUnit.SECONDS.sleep(1);
}
}
// channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
client.shutdownGracefully();
}
}
public static void main(String[] args) {
new NettyChatClient().start();
}
}
client handler
public class NettyChatClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(ctx.channel().remoteAddress() + " : " + msg);
}
}

浙公网安备 33010602011771号