目标
- 每一个客户端连接上服务器的时候需要给其它客户端发送消息。
- 当前客户端可以发送消息给所有连接上了的客户端。
服务端
public class WeChatServer {
private static final Logger logger = LoggerFactory.getLogger(WeChatServer.class);
public static void main(String[] args) throws InterruptedException {
EventLoopGroup boosGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boosGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
logger.info("有客户端连接到服务器:{}", ch.remoteAddress());
ChannelPipeline pipeline = ch.pipeline();
// 这个解码器的作用可以按照指定的分隔符进行分包
// Delimiters.lineDelimiter() 换行分隔符 \r\n
pipeline.addLast(new DelimiterBasedFrameDecoder(4096,Delimiters.lineDelimiter()));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
// 注入自定义的处理器
pipeline.addLast(new WeChatServerHandler());
}
});
ChannelFuture channelFuture = bootstrap.bind(8899).sync();
channelFuture.channel().closeFuture().sync();
} finally {
boosGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
public class WeChatServerHandler extends SimpleChannelInboundHandler<String> {
// 需要注意的是这个对象要设置成为static 否则只能收到自己的消息,不能收到别人的消息
private static final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
Channel channel = ctx.channel();
// 需要注意的是转发消息的时候要在后面加一个\n,因为我们在这个处理器的前面加了一个分隔符处理器DelimiterBasedFrameDecoder
channelGroup.forEach(ch -> {
if (ch == channel) {
ch.writeAndFlush("[自己] " + msg + "\n");
} else {
ch.writeAndFlush("[" + channel.remoteAddress() + "]" + msg + "\n");
}
});
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
channelGroup.writeAndFlush("[服务器]- " + ctx.channel().remoteAddress() + " 加入\n");
channelGroup.add(channel);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
channelGroup.writeAndFlush("[服务器]- " + ctx.channel().remoteAddress() + " 离开\n");
// 这里不需要手动从channelGroup 中移除当前通道
// 因为channelGroup 在初始化的时候使用的GlobalEventExecutor.INSTANCE会帮我们去干这个事情
}
@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();
}
}
客户端
public class WeChatClient {
private static final Logger logger = LoggerFactory.getLogger(WeChatClient.class);
public static void main(String[] args) throws InterruptedException {
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup).channel(NioSocketChannel.class).handler(new 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 WeChatClientHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect("localhost", 8899).sync();
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext()){
String next = scanner.next();
channelFuture.channel().writeAndFlush(next+"\r\n");
}
channelFuture.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
}
}
}
public class WeChatClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg);
}
}
实现效果
总结
channelGroup的作用
- channelGroup 是netty为我们提供的持有channel的容器,通过这个对象 可以实现给一批客户端推送消息。
- 它实现了Set接口,所以我们可以像处理集合一样丝滑的处理它,比如 forEach、filter等等。
DelimiterBasedFrameDecoder的作用
它可以按照我们指定的规则,对数据包进行切割,保证每次接收到的报文都是完整的。