用netty来简单实现一个聊天室功能。
- 项目所需依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.50.Final</version>
</dependency>
客户端
public class server {
public static void main(String[] args) {
EventLoopGroup boss = new NioEventLoopGroup(1);
EventLoopGroup work = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss,work)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast("decoder",new StringDecoder());// 解码器
pipeline.addLast("encoder",new StringEncoder());//编码器
pipeline.addLast(new NettyServerHandler());
}
});
ChannelFuture sync = bootstrap.bind(8888).sync();// netty是一个非阻塞异步 在此处最好保证先执行完 在用服务器做业务 所以加上同步方法sync()
System.out.println("聊天室启动成功");
sync.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
work.shutdownGracefully();
}
}
}
客户端handler
public class NettyServerHandler extends SimpleChannelInboundHandler<String> {
// channelGroup保存了所有和服务器连接的channel
// 可以调用的channelgroup的size方法来查看当前多少连接者
private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 在读取到消息时 触发此方法
@Override
protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception {
Channel channel = ctx.channel();
channelGroup.forEach(ch->{
// 区分当前用户和其他用户
if(ch!=channel){
ch.writeAndFlush("客户端:"+channel.remoteAddress()+"发送了消息"+s+"\n");
}else {
ch.writeAndFlush("自己:"+channel.remoteAddress()+"发送了消息"+s+"\n");
}
});
}
// 有新建链接netty会自动调用这个接口
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel channel=ctx.channel();
channelGroup.writeAndFlush("客户端:"+channel.remoteAddress()+"上线了"+" "+simpleDateFormat.format(new Date()));
channelGroup.add(channel);
System.out.println(channel.remoteAddress()+"上线了");
}
// 当有断开链接 会自动调用这个
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
channelGroup.writeAndFlush("客户端:"+channel.remoteAddress()+"下线了");
System.out.println(channel.remoteAddress()+"下线了");
}
}
服务端
/**
* @Author Weng
* @Description
* @Date 2021/1/18 20:37
*/
public class client {
public static void main(String[] args) {
EventLoopGroup boss = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(boss)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast("decoder",new StringDecoder());
pipeline.addLast("encoder",new StringEncoder());
pipeline.addLast(new NettyClientHandler());
}
});
//连接对应的服务器开放端口
ChannelFuture sync = bootstrap.connect("127.0.0.1", 8888).sync();
Channel channel = sync.channel();
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()){
String s = scanner.nextLine();
channel.writeAndFlush(s);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
}
}
}
客户端 handler
public class NettyClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
// 客户端直接打印出收到的服务端消息
System.out.println(s);
}
}
- 根据netty的核心线程模型,处理用户连接的是一个NioEventLoopGroup(boos),可以通过构造函数生成多个线程(一个线程对应着一个NioEventLoopGroup)来处理用户连接请求。boos会将连接后生成的channel注册到到work内的selector中去,在work中响应用户的消息处理,将所有事件丢到Popeline中去执行。Popeline中有我们自定义的handler(也是我们主要编写的业务逻辑代码处),编码器、解码器也在Popeline中注册的。
- EventLoopGroup boss = new NioEventLoopGroup(1);
- EventLoopGroup work = new NioEventLoopGroup();