使用netty实现一个简单的聊天室
需求
当有用户连接时广播消息=>XXX加入聊天室,用户断开连接时广播消息=>XXX离开聊天室,并且用户发送消息时,服务端进行广播
Server端实现
Server代码:
public class ChatServer {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap cb = new ServerBootstrap();
try {
//这里直接用匿名内部类构造channel初始化类
cb.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter()))//分割器,分割每行数据
.addLast(new StringDecoder(CharsetUtil.UTF_8))
.addLast(new StringEncoder(CharsetUtil.UTF_8))
.addLast(new ChatServerHandler());
}
});
ChannelFuture channelFuture = cb.bind( 9999).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
handler代码:
public class ChatServerHandler extends SimpleChannelInboundHandler<String> {
//这里是一个channel容器,用于管理所有没有断开的连接,广播消息
static ChannelGroup cg = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
Channel me = ctx.channel();
System.out.println("来自=>"+me.remoteAddress()+"的消息:"+msg+"\n");
cg.forEach(ch->{
if(me != ch){
ch.writeAndFlush("来自=>"+me.remoteAddress()+"的消息:"+msg+"\n");
}
else {
ch.writeAndFlush("自己:"+msg+"\n");
}
});
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
cg.writeAndFlush("[系统提示]-用户:"+channel.remoteAddress()+"-加入聊天室\n");
cg.add(channel);
System.out.println(channel.remoteAddress()+" 上线,当前容量:"+cg.size());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
cg.writeAndFlush("[系统提示]-用户:"+channel.remoteAddress()+"-离开聊天室\n");
//cg.remove(channel); ChannelGroup 容器会自动清除断开的连接,所以不需要手动断开
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
cg.writeAndFlush("[系统提示]-用户:"+channel.remoteAddress()+"-上线\n");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
cg.writeAndFlush("[系统提示]-用户:"+channel.remoteAddress()+"-下线\n");
}
}
上述所有发送的消息最后都加了一个换行符,是因为我们下面配置客户端channel的时候也要要求数据带换行分来进行分割,若不换行它会认为这条消息没有处理完,不会放行到后面执行
客户端实现
Server代码:
public class ClientServer {
public static void main(String[] args) {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap cb = new Bootstrap();
try {
//这里直接用匿名内部类构造channel初始化类
cb.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter()))
.addLast(new StringDecoder(CharsetUtil.UTF_8))
.addLast(new StringEncoder(CharsetUtil.UTF_8))
.addLast(new ChatClientHandler());
}
});
Channel channel = cb.connect("localhost", 9999).sync().channel();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while (true) {
channel.writeAndFlush(br.readLine()+"\r\n");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
}
handler代码:
public class ChatClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg);
}
}
由于客户端接收消息只需要显示就行了,所以handler很简单
运行
我们首先启动服务端,然后依次开三个客户端
第一个客户端连接时:
服务端
客户端1
第二个客户端连接时
服务端
客户端1
客户端2
第三个客户端连接时
服务端
客户端1
客户端2
客户端3
客户端3发送消息时
客户端1
客户端2
客户端3
客户3断开时
客户端1
客户端2
总结
这是个很经典的程序,这里也主要用了一个DelimiterBasedFrameDecoder 的handler初始化channel的管道,使其分割换行发送消息