目录
用netty实现一个简单的“聊天室”
环境要求:
项目结构:
代码详情:
1、ChatServerHandler.java
SimpleChannelInboundHandler:
ChannelGroup:
2、ChatServer.java
DelimiterBasedFrameDecoder:
3、ChatClientHandler.java
4、ChatClient.java
运行效果:
源码地址:
public class ChatServerHandler extends SimpleChannelInboundHandler {
//Netty提供了ChannelGroup接口,该接口继承Set接口
// 因此可以通过ChannelGroup可管理服务器端所有的连接的Channel,然后对所有的连接Channel广播消息。
public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
/**
* 每当服务端收到新的客户端连接时,客户端的channel存入ChannelGroup列表中,并通知列表中其他客户端channel
*
* @param ctx
* @throws Exception
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//获取连接的channel
Channel incomming = ctx.channel();
//通知所有已经连接到服务器的客户端,有一个新的通道加入
for (Channel channel : channelGroup) {
channel.writeAndFlush("[系统消息]-" + incomming.remoteAddress() + "加入\n");
}
channelGroup.add(ctx.channel());
}
/**
* 每当服务端断开客户端连接时,客户端的channel从ChannelGroup中移除,并通知列表中其他客户端channel
*
* @param ctx
* @throws Exception
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
//获取连接的channel
Channel incomming = ctx.channel();
for (Channel channel : channelGroup) {
channel.writeAndFlush("[系统消息]-" + incomming.remoteAddress() + "离开\n");
}
//从服务端的channelGroup中移除当前离开的客户端
channelGroup.remove(ctx.channel());
}
/**
* 服务端监听到客户端活动
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//服务端接收到客户端上线通知
Channel incoming = ctx.channel();
System.out.println("ChatClient:" + incoming.remoteAddress() + "在线");
}
/**
* 服务端监听到客户端不活动
*
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
//服务端接收到客户端掉线通知
Channel incoming = ctx.channel();
System.out.println("ChatClient:" + incoming.remoteAddress() + "掉线");
}
/**
* 每当从服务端读到客户端写入信息时,将信息转发给其他客户端的Channel.
*
* @param channelHandlerContext
* @param o
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
Channel incomming = channelHandlerContext.channel();
//将收到的信息转发给全部的客户端channel
for (Channel channel : channelGroup) {
if (channel != incomming) {
channel.writeAndFlush("[" + incomming.remoteAddress() + "]" + o.toString() + "\n");
} else {
channel.writeAndFlush("[You]" + o.toString() + "\n");
}
}
}
}
入站的Handler可能会继承SimpleChannelInboundHandler或者ChannelInboundHandlerAdapter,而SimpleChannelInboundHandler又是继承于ChannelInboundHandlerAdapter,最大的区别在于SimpleChannelInboundHandler会对没有外界引用的资源进行一定的清理,并且入站的消息可以通过泛型来规定。
public abstract class SimpleChannelInboundHandler extends ChannelInboundHandlerAdapter
Netty提供了ChannelGroup接口,该接口继承Set接口,因此可以通过ChannelGroup可管理服务器端所有的连接的Channel,然后对所有的连接Channel广播消息。
public class ChatServer {
int port;
public ChatServer(int port) {
this.port = port;
}
public void run() throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
//DelimiterBasedFrameDecoder用来解决以特殊符号作为消息结束符的粘包问题
.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()))
.addLast("decoder", new StringDecoder())
.addLast("encoder", new StringEncoder())
.addLast(new ChatServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
System.out.println("ChatServer 启动了");
ChannelFuture f = serverBootstrap.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
System.out.println("ChatServer 关闭了");
}
}
public static void main(String[] args) throws Exception {
int port;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
} else {
port = 8080;
}
new ChatServer(port).run();
}
}
用来解决以特殊符号作为消息结束符的粘包问题,FixedLengthFrameDecoder用来解决定长消息的粘包问题。
public class ChatClientHandler extends SimpleChannelInboundHandler {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
System.out.println(o.toString());
}
}
public class ChatClient {
int port;
String host;
public ChatClient(int port, String host) {
this.port = port;
this.host = host;
}
public void run() {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()))
.addLast("decoder", new StringDecoder())
.addLast("encoder", new StringEncoder())
.addLast(new ChatClientHandler());
}
});
Channel channel = bootstrap.connect(host, port).sync().channel();
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
while (true) {
channel.writeAndFlush(in.readLine() + "\r\n");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new ChatClient(8080, "localhost").run();
}
https://github.com/tianyaning/code.git