## Netty实现一个群聊系统
要求:
1.使用netty实现一个群聊系统,实现服务器端与客户端的非阻塞通信
2.实现多人群聊
3.可以检测用户上线离线并实现消息转发
服务器端代码
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
/**
* 群聊系统
* */
public class ChatServer {
private int port;
public ChatServer(int port){
this.port=port;
}
public void run(){
EventLoopGroup boss = new NioEventLoopGroup(1);
EventLoopGroup work = new NioEventLoopGroup();
try{
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss,work)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,128)
.childOption(ChannelOption.SO_KEEPALIVE,true)
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//解码器
pipeline.addLast("decode",new StringDecoder());
pipeline.addLast("encode",new StringEncoder());
//pipeline.addLast(new IdleStateHandler(3,5,7, TimeUnit.SECONDS));
pipeline.addLast(new ServerHandler());
}
});
ChannelFuture future = serverBootstrap.bind(port).sync();
System.out.println("服务器已就绪");
future.channel().closeFuture().sync();
System.out.println("服务器已关闭");
}catch (Exception e){
e.printStackTrace();
}finally {
boss.shutdownGracefully();
work.shutdownGracefully();
}
}
public static void main(String[] args) {
new ChatServer(8963).run();
}
}
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.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ServerHandler extends SimpleChannelInboundHandler<String> {
//管理channel的组
private static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 客户端连接上来后输出登录服务器
* */
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
String time = simpleDateFormat.format(new Date());
Channel channel = ctx.channel();
System.out.println(channel.remoteAddress()+"["+time+"]"+"已登录服务器");
}
/**
* 新的客户端连接调用此方法通知所有在线用户
* */
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
String time = simpleDateFormat.format(new Date());
Channel channel = ctx.channel();
channels.writeAndFlush("[系统]"+"["+time+"]"+channel.remoteAddress()+"已上线");
channels.add(channel);
}
/**
* 客户端离开群聊后此方法通知所有在线用户
* */
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
String time = simpleDateFormat.format(new Date());
Channel channel = ctx.channel();
channels.writeAndFlush("[系统]"+time+channel.remoteAddress()+"已下线");
}
/**
* 客户端关闭连接调用此方法
* */
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
String time = simpleDateFormat.format(new Date());
Channel channel = ctx.channel();
System.out.println(channel.remoteAddress()+time+"已断开服务器");
}
/**
* 读取用户发送的消息操作
* */
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
String time = simpleDateFormat.format(new Date());
Channel channel = channelHandlerContext.channel();
channels.forEach(c->{
if(c!=channel){
c.writeAndFlush("[来自"+channel.remoteAddress()+"消息]"+": "+s);
}
});
}
}
## ```客户端
```java
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.util.Scanner;
public class ChatClient {
private String addr;
private int port;
public ChatClient(String addr,int port){
this.addr = addr;
this.port = port;
}
public void run(){
EventLoopGroup group = new NioEventLoopGroup();
try{
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast("decode",new StringDecoder());
pipeline.addLast("encode",new StringEncoder());
pipeline.addLast(new ClientHandler());
}
});
ChannelFuture future = bootstrap.connect(addr, port).sync();
Channel channel = future.channel();
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext()){
channel.writeAndFlush(scanner.nextLine());
}
future.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) {
new ChatClient("127.0.0.1",8963).run();
}
}
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class ClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
System.out.println(s);
}
}
测试
私聊的简单实现思路
群聊系统使用的是private static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); ChannelGroup管理登录的用户,很容易实现消息转发。如果是私聊可以使用自定义数据结构存储用户信息,有用户唯一标识以及对应channel即可。需要私聊获取到对应channel则可以发送消息了
Netty心跳检测
Netty框架心跳检测可以直接添加到pipeiline中就可以了
IdleStateHandler是Netty提供的心跳检测类
readerIdleTime是多长时间没有读
writeIdleTime是多长时间没有写
allIdleTime是多长时间没有进行读或写
需要在下一个pipeiline中的userEventTriggered方法中进行重写