> 文章列表 > 用Netty做一个简单的聊天室程序

用Netty做一个简单的聊天室程序

用Netty做一个简单的聊天室程序

目录

需求

POM依赖

服务端

客户端


需求

一个服务端支持多个客户端同时连接,服务端关注客户端的在线,离线情况,客户端关注其他客户端的离线情况、在线情况,发送的消息。


POM依赖

    <dependencies><!-- https://mvnrepository.com/artifact/io.netty/netty-all --><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.87.Final</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

服务端

构思

  • 服务端需要关注客户端的在线情况,意味着客户端与服务端建立连接后需要马上在控制台打印提示信息。
  • 服务端需要关注客户端的离线情况,意味着客户端与服务端断开连接后需要马上在控制台打印提示信息。

实现

package com.ctx.chat.server;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 {public static void main(String[] args) throws Exception {//bossGroup处理连接事件EventLoopGroup bossGroup = new NioEventLoopGroup(1);//workerGroup处理读写事件EventLoopGroup workerGroup = new NioEventLoopGroup(8);try {ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch){ChannelPipeline pipeline = ch.pipeline();//解码器pipeline.addLast("decoder", new StringDecoder());//编码器pipeline.addLast("encoder", new StringEncoder());//加入自定义的Handlerpipeline.addLast(new ChatServerHandler());}});ChannelFuture channelFuture = bootstrap.bind(8099).sync();System.out.println("聊天室服务器启动成功。");channelFuture.channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}
package com.ctx.chat.server;import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;@Slf4j
public class ChatServerHandler extends ChannelInboundHandlerAdapter {//全局单例事件执行器private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);/* 客户端与服务端连接好以后会触发该方法。* @param ctx*/@Overridepublic void channelActive(ChannelHandlerContext ctx) {Channel channel = ctx.channel();//提示除了当前客户端之外的其他客户端有新的用户上线。channelGroup.writeAndFlush("用户_"+channel.remoteAddress()+" 已上线。");//不需要向当前客户端提示已上线。所以添加操作应该放在全局提示之后。channelGroup.add(channel);log.info("用户_{} 已上线。",channel.remoteAddress());}/* 服务端读入客户端发送过来的数据。* @param ctx* @param msg* @throws Exception*/@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {Channel channel = ctx.channel();//把当前客户端发送的消息,转发给其他的客户端。channelGroup.forEach(channelItem -> {if(channelItem != channel ){channelItem.writeAndFlush("用户_"+channel.remoteAddress()+" :"+msg);}/*if(channelItem.remoteAddress().equals( channel.remoteAddress() )){channelGroup.writeAndFlush("用户_"+channel.remoteAddress()+" :"+msg);}*/});}/* 客户端与服务器断开连接后触发该方法。* @param ctx*/@Overridepublic void channelInactive(ChannelHandlerContext ctx) {Channel channel = ctx.channel();channelGroup.writeAndFlush("用户_"+channel.remoteAddress()+" 已下线。");log.info("用户_{} 已下线。",channel.remoteAddress());}
}

客户端

构思

  • 客户端需要关注其他客户端的在线情况,意味着其他后续的客户端与服务端建立连接后,需要服务端马上向其他客户端发送上线通知
  • 客户端需要关注其他客户端的离线情况,意味着其他客户端与服务端断开连接后,需要服务端马上向其他客户端发送离线通知
  • 客户端需要关注其他客户端发送的数据,意味着其他客户端向服务端发送数据后,需要服务端马上向其他客户端转发这些数据

实现

package com.ctx.chat.client;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 lombok.extern.slf4j.Slf4j;import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
@Slf4j
public class ChatClient {public static void main(String[] args) throws Exception {EventLoopGroup group = new NioEventLoopGroup();try {Bootstrap bootstrap = new Bootstrap();bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();//解码器pipeline.addLast("decoder", new StringDecoder());//编码器pipeline.addLast("encoder", new StringEncoder());//加入自定义的Handlerpipeline.addLast(new ChatClientHandler());}});ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8099).sync();Channel channel = channelFuture.channel();log.info("我上线了:{}",channel.localAddress());//Scanner scanner = new Scanner(System.in);while (scanner.hasNextLine()) {String msg = new String(scanner.nextLine().getBytes(StandardCharsets.UTF_8));//向服务端发送数据channel.writeAndFlush(msg);}} finally {group.shutdownGracefully();}}
}
package com.ctx.chat.client;import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;@Slf4j
public class ChatClientHandler extends ChannelInboundHandlerAdapter {/* 客户端读入服务端发送过来的数据。* @param ctx* @param msg*/@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {Channel channel = ctx.channel();log.info("用户_{} :{}",channel.remoteAddress(),msg);}
}