Netty 群聊的实现
Ⅰ 传入信息实体类
public class MessageProtocol {
private int len; //具体内容转化成字节后的长度
private byte[] content; //具体内容转化成的字节数组
public int getLen() {
return len;
}
public void setLen(int len) {
this.len = len;
}
public byte[] getContent() {
return content;
}
public void setContent(byte[] content) {
this.content = content;
}
}
Ⅱ、解码器
public class NettyDeCoder extends ReplayingDecoder<Void> {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
//将得到的二进制码转化成具体的内容
int length = byteBuf.readInt(); //获取第一个内容的字节长度
byte[] content = new byte[length];
byteBuf.readBytes(content); //读取对应长度的字节到字节数组中
//封装成 MessageProtocol 对象 传递给下一个 handler 业务
MessageProtocol messageProtocol = new MessageProtocol();
messageProtocol.setLen(length);
messageProtocol.setContent(content);
list.add(messageProtocol);
}
}
Ⅲ、编码器
public class NettyEnCoder extends MessageToByteEncoder<MessageProtocol> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, MessageProtocol messageProtocol, ByteBuf byteBuf) throws Exception {
byteBuf.writeInt(messageProtocol.getLen());
byteBuf.writeBytes(messageProtocol.getContent());
}
}
Ⅳ、服务器端
public class BossServer {
public static void main(String[] args) {
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workGroup = new NioEventLoopGroup();
try {
//创建服务启动对象
ServerBootstrap bootstrap = new ServerBootstrap();
//使用链式编程来进行设置
bootstrap.group(bossGroup, workGroup) //设置两个线程组
.channel(NioServerSocketChannel.class) //使用NioSocketChannel 作为服务器的通道实现
.option(ChannelOption.SO_BACKLOG, 128) //设置线程队列得到的连接个数
.childOption(ChannelOption.SO_KEEPALIVE, true) //设置保持活动连接的状态
.childHandler(new BossServerChannelInitializer()); //具体业务实现
//绑定端口 并且生成了一个ChannelFuture 对象 启动服务
ChannelFuture channelFuture = bootstrap.bind(8989).sync();
//对关闭通道进行监听,并没有立即关闭
channelFuture.channel().closeFuture().sync();
}catch (Exception e){
System.out.println("server 出现异常...");
} finally{
workGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
public class BossServerChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new NettyDeCoder()); //添加解码器
pipeline.addLast(new NettyEnCoder()); //添加编码器
pipeline.addLast(new BossServerHandler()); //添加具体业务
}
}
public class BossServerHandler extends SimpleChannelInboundHandler<MessageProtocol> {
//定义一个 channel 组 管理所有的 channel
private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
//定义一个Map
private static Map<Date,String> map = new HashMap<>();
//接收数据并处理
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, MessageProtocol messageProtocol) throws Exception {
byte[] content = messageProtocol.getContent();
String message = new String(content,"utf-8").toString();
System.out.println("用户:"+channelHandlerContext.channel().remoteAddress()+" 服务端接收数据:"+ message+" channelGroup size:"+channelGroup.size());
Channel currentChannel = channelHandlerContext.channel();
for(Channel channel:channelGroup){ //群发
if(channel != currentChannel){
transpond("[陌生人]:"+message,channel); //发送给其他人
}else{
transpond("[自己]:"+message,channel); //发送给自己,根据自己的需要
}
}
}
//表示 channel 出于活跃状态,如提示 XX上线
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx.channel().remoteAddress()+"上线");
}
//表示 channel 出于活跃状态,如提示 XX下线
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx.channel().remoteAddress()+"下线");
}
//将当前的channel 加入到 channelGroup
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
//将该客户加入聊天的信息推送个其它在线的客户端,该方法会将 channelGroup 中的所有channel 遍历,并发送消息
channelGroup.writeAndFlush("[客户端] "+channel.remoteAddress()+" 加入聊天");
channelGroup.add(ctx.channel());
}
//断开连接,将 xx 用户离开信息推送给当前在线的客户
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
channelGroup.writeAndFlush("[客户端] "+channel.remoteAddress()+" 离开了 \n");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
//转发给其它用户
public void transpond(String message,Channel channel){
byte[] content ;
int length;
content = message.getBytes(Charset.forName("utf-8"));
length = message.getBytes(Charset.forName("utf-8")).length;
MessageProtocol msgProtocol = new MessageProtocol();
msgProtocol.setContent(content);
msgProtocol.setLen(length);
channel.writeAndFlush(msgProtocol);
}
}
Ⅴ、客户端
public class WorkerClient {
public static void main(String[] args) {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
try{
bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.handler(new WorkerClientChannelInitializer());
//启动客户端 连接服务器
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1",8989).sync();
channelFuture.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
eventLoopGroup.shutdownGracefully();
}
}
}
public class WorkerClientChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new NettyDeCoder());
pipeline.addLast(new NettyEnCoder());
pipeline.addLast(new WorkerClientHandler());
}
}
public class WorkerClientHandler extends SimpleChannelInboundHandler<MessageProtocol> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, MessageProtocol messageProtocol) throws Exception {
int length = messageProtocol.getLen();
byte[] content = messageProtocol.getContent();
String message = new String(content,"utf-8");
System.out.println( message);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//客户端发送数据
new Thread(() ->sendMessage(ctx)).start();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
public void sendMessage(ChannelHandlerContext ctx){
//客户端发送数据
Scanner in = new Scanner(System.in);
String message = null;
try{
while((message =in.nextLine())!=null) {
// message = "测试...";
byte[] content = message.getBytes(Charset.forName("utf-8"));
int length = message.getBytes(Charset.forName("utf-8")).length;
System.out.println("===> " +message+" ,remoteAdd :"+ctx.channel().remoteAddress()+",localAd:"+ctx.channel().localAddress());
MessageProtocol messageProtocol = new MessageProtocol();
messageProtocol.setContent(content);
messageProtocol.setLen(length);
ctx.channel().writeAndFlush(messageProtocol);
}
}finally {
in.close();
}
}
}
特别注意:在 WorkerClientHandler 类方法 channelActive中,我这里使用的是 lambada 表达式 结合多线程,在这里千万不要直接使用:
while((message =in.nextLine())!=null)
这行代码,会出现问题。具体如下:
1.Server 和 Client 都正常启动,但 Client 启动后会监听键盘输入
2.Client 端正常输入,结束后又返回继续监听键盘;Server端接收到第一次传来的信息,群发(发给自己和其他的Client);在这里就出现问题了,Client 端的线程都在监听键盘输入,那哪个线程来处理 Server 端传来的信息了。
3.总结:所以我们看到的是 Client1 发送一段消息, Server 端接收到消息,但群发(转发)后的消息没有显示,Client1 、Client2 都没有显示Server 发来的消息。