需求:在群聊基础上,增加私聊功能
Server
package com.lian.groupprivatechat;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
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;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.GenericFutureListener;
public class Server {
//监听端口
private Integer port;
public Server(Integer port) {
this.port = port;
}
//编写run方法,处理客户端的请求
public void run() throws InterruptedException {
//创建两个线程组
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//创建workerGroup启动类
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap
.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 {
ChannelPipeline pipeline = ch.pipeline();
//向pipeline里加入解码器
pipeline.addLast("decoder",new StringDecoder());
向pipeline里加入编码器
pipeline.addLast("encoder",new StringEncoder());
向pipeline里加入自定义业务处理器
pipeline.addLast(new ServerHandler());
}
});
System.out.println("netty server start");
//绑定端口
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
//添加监听器
channelFuture.addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
public void operationComplete(Future<? super Void> future) throws Exception {
if (channelFuture.isSuccess()){
System.out.println("server is startting...");
}else if (channelFuture.isDone()){
System.out.println("server started success...");
}
}
});
//监听关闭通道
channelFuture.channel().closeFuture().sync();
//添加监听器
channelFuture.addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
public void operationComplete(Future<? super Void> future) throws Exception {
if (channelFuture.isCancelled()){
System.out.println("server is stopping...");
}else if (channelFuture.isCancellable()){
System.out.println("server was stopped...");
}
}
});
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new Server(9999).run();
}
}
ServerHandler
package com.lian.groupprivatechat;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class ServerHandler extends SimpleChannelInboundHandler<String> {
/**
* 定义一个channle 组,管理所有的channel
* GlobalEventExecutor.INSTANCE) 是全局的事件执行器,是一个单例
*/
private static DefaultChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
/**
* 为了实现私聊功能,这里key存储用户的唯一标识,
* 客户端的端口号
* 当然 这个集合也需要自己去维护 用户的上下线 不能像 ChannelGroup那样自己去维护
*/
private static Map<String, Channel> map = new HashMap<>();
//创建日期
SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");
/**
* 服务器读取客户端的数据
* @param ctx
* @param msg
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
//获取当前通道
Channel channel = ctx.channel();
/**
* 私聊,这里简单判断 如果内容里边包含#那么就是私聊
*/
if (msg.contains("#")){
String id = msg.split("#")[0];
String body = msg.split("#")[1];
Channel userChannel = map.get(id);
System.out.println("userChannel: "+userChannel);
//求出远程地址的 key值9609 /127.0.0.1:9609
String key = channel.remoteAddress().toString().split(":")[1];
userChannel.writeAndFlush(sdf.format(new Date())+"\n client "+key+" say : "+body);
}
//判断当前消息是不是自己发送的,循环遍历channelGroup组中的channel
channelGroup.forEach(ch->{
//如果循环遍历的不是当前的channel,就转发消息给其他客户
if (channel != ch){
ch.writeAndFlush("client"+channel.remoteAddress()+" send message "+msg+"\n");
}else {
//如果循环遍历到的是当前channel,就回显自己发送的消息给自己
ch.writeAndFlush("self send message "+msg+"\n");
}
});
}
/**
* handlerAdded 表示连接建立,一旦连接,第一个被执行
* 将当前channel 加入到 channelGroup
* 建立连接以后第一个调用的方法
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
/**
* 将该客户加入聊天的信息推送给其它在线的客户端
* 该方法会将 channelGroup 中所有的channel 遍历,并发送 消息,
* 我们不需要自己遍历
*/
channelGroup.writeAndFlush("client: "+channel.remoteAddress()+" join chatting"+" time is "+sdf.format(new Date())+"\n");
channelGroup.add(channel);
// /127.0.0.1:9394
String key = channel.remoteAddress().toString().split(":")[1];
map.put(key, channel);
}
/**
* 表示channel 处于活动状态, 提示 xx上线
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
System.out.println(channel.remoteAddress()+" on-line...");
}
/**
* 表示channel 处于不活动状态, 提示 xx离线了
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
System.out.println(channel.remoteAddress()+" off-line...");
//下线移除
String key = channel.remoteAddress().toString().split(":")[1];
map.remove(key);
}
/**
* 断开连接,将xx客户离开的消息推送给当前在线的其他客户,有人下线了通知到其他在线的人
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
channelGroup.writeAndFlush("client "+channel.remoteAddress()+" leaving\n");
System.out.println("channelGroup size"+channelGroup.size());
System.out.println("map size: "+map.size());
}
/**
* 捕获异常
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//出现异常关闭通道
ctx.channel().close();
}
}
Client
package com.lian.groupprivatechat;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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 io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import java.util.Scanner;
public class Client {
private final String host;
private final Integer port;
public Client(String host, Integer port) {
this.host = host;
this.port = port;
}
public void run() throws Exception {
NioEventLoopGroup clientEventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(clientEventLoopGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("decoder",new StringDecoder());
pipeline.addLast("encoder",new StringEncoder());
pipeline.addLast(new ClientHandler());
}
});
//绑定主机和端口
ChannelFuture channelFuture = bootstrap.connect(host,port).sync();
//添加监听器
channelFuture.addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
public void operationComplete(Future<? super Void> future) throws Exception {
if (channelFuture.isSuccess()){
System.out.println("client is startting...");
}else if (channelFuture.isDone()){
System.out.println("client is start success...");
}
}
});
Channel channel = channelFuture.channel();
System.out.println("----------"+channel.localAddress()+"-----------");
//创建扫描器
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()){
String msg = scanner.nextLine();
//将控制台上的数据写入到channel中
channel.writeAndFlush(msg+"\r\n");
}
//监听关闭通道
channelFuture.channel().closeFuture().sync();
scanner.close();
}finally {
clientEventLoopGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new Client("127.0.0.1",9999).run();
}
}
ClientHandler
package com.lian.groupprivatechat;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class ClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg.trim());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.channel().close();
}
}
测试
----------/127.0.0.1:13983-----------
client�� /127.0.0.1:14023 join chatting time is 2021-04-12 20:23:25
2021-04-12 20:23:35
client 14023 say : you are a son
//id # message
14023 # i am your parent
self send message
----------/127.0.0.1:14023-----------
13983 # you are a son
client/127.0.0.1:13983 send message
2021-04-12 20:24:00
client 13983 say : i am your parent