群聊系统
服务端
public class GroupChatServer {
private int port;
public GroupChatServer(int port) {
this.port = port;
}
public void run() throws InterruptedException {
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap sbs = new ServerBootstrap();
sbs.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("decoder", new StringDecoder());
ch.pipeline().addLast("encoder", new StringEncoder());
ch.pipeline().addLast("GCServerHandler", new GroupChatServerHandler());
}
});
System.out.println("server is ready...");
ChannelFuture cf = sbs.bind(port).sync();
cf.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
try {
new GroupChatServer(8888).run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ServerHandler
public class GroupChatServerHandler extends SimpleChannelInboundHandler<String> {
private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
channelGroup.writeAndFlush(sdf.format(new Date()) + " client " + channel.remoteAddress() + " 加入聊天\r\n");
channelGroup.add(channel);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
channelGroup.writeAndFlush(sdf.format(new Date()) + " client " + channel.remoteAddress() + " 离开聊天\r\n");
System.out.println("当前channelGroup大小: " + channelGroup.size());
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(sdf.format(new Date()) + " " + ctx.channel().remoteAddress() + " 上线...");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println(sdf.format(new Date()) + " " + ctx.channel().remoteAddress() + " 离线...");
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
Channel channel = ctx.channel();
channelGroup.forEach(ch -> {
if (channel != ch) {
ch.writeAndFlush(sdf.format(new Date()) + " client " + channel.remoteAddress() + " send: " + msg + "\r\n");
} else {
ch.writeAndFlush(sdf.format(new Date()) + " me " + msg + "\r\n");
}
});
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客户端
public class GroupChatClient {
private final String host;
private final int port;
public GroupChatClient(String host, int port) {
this.host = host;
this.port = port;
}
public void run() throws InterruptedException {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bs = new Bootstrap().group(group).channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("decoder", new StringDecoder());
ch.pipeline().addLast("encoder", new StringEncoder());
ch.pipeline().addLast("GCClientHandler", new GroupChatClientHandler());
}
});
ChannelFuture cf = bs.connect(host, port).sync();
Channel channel = cf.channel();
System.out.println("client channel: " + channel.localAddress() + " is ready...");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String msg = scanner.nextLine();
channel.writeAndFlush(msg);
}
cf.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) {
try {
new GroupChatClient("localhost", 8888).run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ClientHandler
public class GroupChatClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg.trim());
}
}
实现单对单联系
public static Map<String, Channel> channelMap = new HashMap<>();
public static Map<User, Channel> userMap = new HashMap<>();
channelMap.put("id" + channel.hashCode(), channel);
userMap.put(new User(channel.hashCode(), "password"), channel);
if (channelMap.get("id2") != channel) {
Channel id2 = channelMap.get("id2");
id2.writeAndFlush(sdf.format(new Date()) + " client " + channel.remoteAddress() + " 私发消息 send: " + msg + "\r\n");
}
心跳检测机制
- 从服务器的角度,检查读写情况
- 心跳检测,防止服务端检测不到客户端是否断开
- 服务器
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
new ServerBootstrap().group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new IdleStateHandler(3, 5, 7, TimeUnit.SECONDS));
pipeline.addLast(new MyServerIdleHandler());
}
}).bind(8888).sync().channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
public class MyServerIdleHandler extends ChannelInboundHandlerAdapter {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
String eventType = null;
switch (event.state()) {
case READER_IDLE:
eventType = "读空闲";
break;
case WRITER_IDLE:
eventType = "写空闲";
break;
case ALL_IDLE:
eventType = "读写空闲";
break;
}
System.out.println(ctx.channel().remoteAddress() + " 发生事件: " + eventType);
ctx.channel().close();
}
}
}