Netty网络通讯是一个高性能、异步事件驱动的网络应用框架,由JBOSS团队开发;它简化了TCP、UDP、WebSocket等协议的编程工作;文章详细介绍了Netty的核心概念、环境搭建、基本使用及高级特性;并通过实践案例展示了如何构建简单的聊天室应用。
Netty简介与环境搭建 什么是NettyNetty是一个异步事件驱动的网络应用框架,由JBOSS团队开发,最初用于构建高性能的、基于Java NIO的网络应用。Netty提供了一个成熟的API,简化了TCP、UDP、HTTP、WebSocket等协议的编程工作,使开发者可以专注于业务逻辑的实现,而不是底层网络的细节。
Netty的核心优势- 高性能:Netty采用了先进的设计模式和非阻塞IO模型,确保了网络应用的高效率和低延迟。
- 灵活:支持多种传输协议,如TCP、UDP、HTTP、WebSocket等,可以方便地扩展和定制。
- 稳定:经过长时间的实战考验,Netty具有优秀的稳定性和可靠性。
- 易用性:丰富的文档和社区支持使其易于理解和使用。
为了开始使用Netty,你需要准备以下开发环境:
- Java开发环境:确保已安装JDK 8或更高版本。
- IDE:推荐使用IntelliJ IDEA或Eclipse等集成开发环境。
- 搭建Maven项目:创建一个新的Maven项目,并在
pom.xml
文件中添加Netty的依赖。
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.68.Final</version>
</dependency>
</dependencies>
- 项目结构:确保项目结构中包含以下主要文件和目录:
src/main/java
:用于存放Java源代码。src/main/resources
:用于存放资源文件。pom.xml
:Maven配置文件。
事件驱动编程是一种编程范式,其中程序的执行流程由外部事件驱动。不同于传统的阻塞式编程模式,事件驱动模型允许程序在等待事件发生时释放CPU资源。在Netty中,事件驱动模型是通过事件循环(Event Loop)实现的,每个Event Loop负责一组相关的网络通道(Channels),并为它们处理事件。这种设计使得Netty能够在多个连接上同时处理事件,而不会阻塞任何单个连接的操作。
举例说明,我们可以通过创建一个简单的服务器来展示这一概念:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
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 SimpleServer {
public void start(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new SimpleServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
class SimpleServerHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("Received: " + msg);
ctx.writeAndFlush("Echo: " + msg);
}
}
上述代码中,ServerBootstrap
负责服务器的初始化和配置,包括Event Loop组和其他通道选项。SimpleServerHandler
实现了一个简单的回显服务器,它接收客户端发送的消息并回显。
通道(Channel)是Netty的核心概念,表示一个双向的通信通道,它封装了网络端点(如IP地址和端口)和输入输出流(如ByteBuffer)。Netty提供了多种类型的通道,如NioServerSocketChannel
、NioSocketChannel
等。通道管理器(ChannelManager)通常是客户端或服务器端的逻辑组件,它负责管理一组通道的创建、关闭和事件处理。虽然Netty并没有直接提供ChannelManager这一组件,但你可以通过管理一组Event Loop来实现类似的功能,具体可以使用EventLoopGroup
来管理一组事件循环,如下面的示例所示:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class ChannelManagerExample {
public void start(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new SimpleServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new ChannelManagerExample().start(8080);
}
class SimpleServerHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("Received: " + msg);
ctx.writeAndFlush("Echo: " + msg);
}
}
}
在上面的示例中,EventLoopGroup
用于管理一组事件循环,ChannelInitializer
用于初始化通道的处理管道。
事件循环(Event Loop)是Netty的另一个核心组件,它负责处理通道上的事件,如连接建立、数据读写、错误等。每个Event Loop都负责一组通道的事件处理,从而实现并发处理多个连接的能力。在Netty中,可以使用EventLoopGroup
来创建和管理一组Event Loop。通常,服务器端使用两个Event Loop组,一个用于监听新连接(Boss Group),另一个用于处理已建立连接的IO操作(Worker Group)。
Netty的编解码器(ChannelHandler)是用于处理网络数据的组件,这些组件可以添加到通道的处理流水线(ChannelPipeline)中。每个ChannelPipeline都包含一个或多个ChannelHandler,这些处理器按照添加顺序依次处理数据。例如,可以使用StringDecoder
和StringEncoder
来处理字符串消息,将接收到的字节数据解码为字符串,将发送的字符串编码为字节数据:
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class SimpleServerHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("Received: " + msg);
ctx.writeAndFlush("Echo: " + msg);
}
}
在上述示例中,SimpleServerHandler
继承自SimpleChannelInboundHandler<String>
,用于处理字符串消息。当从通道中读取到数据时,会调用channelRead0
方法,处理接收到的字符串消息,并发送回显消息。
编写一个简单的Netty服务器,步骤如下:
- 创建
EventLoopGroup
实例,用于处理I/O操作。 - 创建
ServerBootstrap
实例,配置服务器。 - 绑定服务器到指定端口。
下面是一个简单的服务端示例:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
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 SimpleServer {
public void start(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new SimpleServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new SimpleServer().start(8080);
}
}
class SimpleServerHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("Received: " + msg);
ctx.writeAndFlush("Echo: " + msg);
}
}
客户端的连接建立
编写一个简单的Netty客户端,步骤如下:
- 创建
EventLoopGroup
实例,用于处理I/O操作。 - 创建
Bootstrap
实例,配置客户端。 - 连接到服务器。
下面是一个简单的客户端示例:
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
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;
public class SimpleClient {
public void start(String host, int port) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new SimpleClientHandler());
}
});
ChannelFuture f = b.connect(host, port).sync();
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new SimpleClient().start("localhost", 8080);
}
}
class SimpleClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("Received: " + msg);
}
}
简单的消息发送与接收
在上述示例中,服务端和客户端已经实现了基本的消息发送与接收功能。客户端发送字符串消息,服务端接收并回显消息,客户端再接收回显的消息并打印。
Netty的高级特性 长连接与心跳机制长连接是指客户端与服务器在一定时间内保持连接状态,而不需要频繁地建立和断开连接。心跳机制是一种保持长连接活跃的方法,通过周期性地发送心跳包(通常是空消息),检测连接是否仍然有效。
Netty提供了一种简单的方法来实现心跳机制。可以使用IdleStateHandler
来处理空闲状态,例如超时未接收到数据时,会触发空闲状态事件。
下面是一个使用IdleStateHandler
实现心跳机制的例子:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
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.handler.timeout.IdleStateHandler;
public class HeartbeatServer {
public void start(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new IdleStateHandler(0, 0, 10));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new SimpleHeartbeatHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new HeartbeatServer().start(8080);
}
}
class SimpleHeartbeatHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("Received: " + msg);
ctx.writeAndFlush("Echo: " + msg);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateHandler) {
ctx.writeAndFlush("Heartbeat");
}
}
}
在上述示例中,IdleStateHandler
配置为每10秒触发一次空闲状态事件,当检测到空闲状态时,会发送一个心跳包。
零拷贝技术是一种减少数据在内存中复制次数的技术,可以提高数据传输的效率。Netty通过使用CompositeByteBuf
和DIRECT_BUFFER
等特性,减少了数据的拷贝次数,从而提高了性能。CompositeByteBuf
允许你将多个ByteBuf组合成一个逻辑上的ByteBuf,从而减少内存的分配和拷贝次数。
import io.netty.buffer.ByteBuf;
import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled;
public class ZeroCopyExample {
public static void main(String[] args) {
CompositeByteBuf composite = Unpooled.compositeBuffer();
composite.addComponents(true, // release flag
Unpooled.wrappedBuffer("Hello".getBytes()), // Component 1
Unpooled.wrappedBuffer(" World".getBytes()) // Component 2
);
ByteBuf message = composite;
System.out.println("Message: " + message.toString());
}
}
在上述示例中,CompositeByteBuf
将两个ByteBuf组合在一起,减少了内存的分配和拷贝次数。
Netty采用异步非阻塞IO模型,通过使用NIO
(New IO)库实现了高效的网络I/O操作。在异步非阻塞模式下,线程不会因为I/O操作而阻塞,而是可以继续处理其他任务,从而提高了系统的整体性能。EventLoop
机制是Netty异步非阻塞模型的核心部分。每个EventLoop
负责处理一组相关的通道,当有事件发生时,EventLoop会异步执行处理逻辑。这种设计使得Netty能够高效地处理大量并发连接。
合理的线程池配置可以显著提高Netty的性能。以下是一些优化线程池配置的建议:
- Boss和Worker线程数:根据系统的CPU核心数和负载情况,适当调整Boss和Worker线程数。
- 队列大小:合理设置队列大小,避免队列过长导致延迟增加。
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
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 OptimizedServer {
public void start(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(4);
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new SimpleServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new OptimizedServer().start(8080);
}
}
在上述示例中,Boss线程数设置为1,Worker线程数设置为4,可以根据实际情况进行调整。
缓冲区大小选择缓冲区大小的选择直接影响到性能。通常,选择一个合适的缓冲区大小可以提高数据读写的效率。Netty提供了多种缓冲区类型,如ByteBuf
、CompositeByteBuf
等,可以根据需要选择合适的缓冲区类型。
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
public class BufferSizeExample {
public static void main(String[] args) {
ByteBuf buffer = Unpooled.directBuffer(1024, 1024);
buffer.writeBytes("Hello, World".getBytes());
System.out.println("Buffer size: " + buffer.readableBytes());
}
}
在上述示例中,使用了直接内存缓冲区Unpooled.directBuffer
,并设置了容量和最大容量。
选择合适的传输协议可以显著提高网络通信的效率。Netty支持多种传输协议,如TCP、UDP、WebSocket等。根据应用的需求选择合适的协议,并进行相应的优化。
服务端示例使用TCP协议:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
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 ProtocolOptimizationExample {
public void start(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new SimpleServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new ProtocolOptimizationExample().start(8080);
}
}
客户端示例使用TCP协议:
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
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;
public class ProtocolOptimizationClient {
public void start(String host, int port) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new SimpleClientHandler());
}
});
ChannelFuture f = b.connect(host, port).sync();
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new ProtocolOptimizationClient().start("localhost", 8080);
}
}
网络拥塞控制与流量控制
网络拥塞控制和流量控制是网络通信中的重要技术。Netty通过内置的拥塞控制机制,如TCP_NODELAY
、SO_RCVBUF
等,来优化网络通信。
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
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 CongestionControlExample {
public void start(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new SimpleServerHandler());
}
})
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.SO_RCVBUF, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new CongestionControlExample().start(8080);
}
}
在上述示例中,启用了TCP_NODELAY
选项,禁用了Nagle算法,提高了数据传输的速度。
聊天室应用通常包含服务端和客户端两个部分。服务端负责管理多个客户端的连接和消息广播,客户端负责发送和接收消息。
服务端的主要职责包括:
- 监听客户端连接。
- 处理客户端消息。
- 消息广播。
客户端的主要职责包括:
- 连接到服务端。
- 发送和接收消息。
为了实现消息的广播,服务端需要维护一个客户端列表,并在接收到消息时将消息广播给所有在线的客户端。
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
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 java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
public class ChatServer {
private AtomicInteger idCounter = new AtomicInteger(0);
private CopyOnWriteArrayList<SocketChannel> clients = new CopyOnWriteArrayList<>();
public void start(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new ChatServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public void broadcastMessage(String message, SocketChannel sender) {
for (SocketChannel client : clients) {
if (client != sender) {
client.writeAndFlush(message + "\n");
}
}
}
public static void main(String[] args) throws Exception {
new ChatServer().start(8080);
}
}
class ChatServerHandler extends SimpleChannelInboundHandler<String> {
private ChatServer server;
public ChatServerHandler(ChatServer server) {
this.server = server;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
SocketChannel sender = ctx.channel();
server.broadcastMessage(msg, sender);
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
SocketChannel client = ctx.channel();
server.clients.add(client);
String id = "Client-" + server.idCounter.incrementAndGet();
server.broadcastMessage(id + " joined the chat", ctx.channel());
ctx.writeAndFlush("Welcome to the chat, " + id + "\n");
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
SocketChannel client = ctx.channel();
server.clients.remove(client);
String id = "Client-" + server.idCounter.decrementAndGet();
server.broadcastMessage(id + " left the chat", ctx.channel());
}
}
在上述服务端示例中,ChatServer
维护了一个客户端列表,当有新的客户端连接时,会在这个列表中添加该客户端,并广播一条消息给所有客户端。当客户端断开连接时,会从列表中移除该客户端,并广播一条离线消息。
为了更好地管理客户端的状态,可以在服务端实现用户登录和退出的处理逻辑。
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
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 java.util.concurrent.ConcurrentHashMap;
public class AuthChatServer {
private ConcurrentHashMap<String, SocketChannel> users = new ConcurrentHashMap<>();
private AtomicInteger idCounter = new AtomicInteger(0);
public void start(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new AuthChatServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public void broadcastMessage(String message, SocketChannel sender) {
for (SocketChannel client : users.values()) {
if (client != sender) {
client.writeAndFlush(message + "\n");
}
}
}
public static void main(String[] args) throws Exception {
new AuthChatServer().start(8080);
}
}
class AuthChatServerHandler extends SimpleChannelInboundHandler<String> {
private AuthChatServer server;
public AuthChatServerHandler(AuthChatServer server) {
this.server = server;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
SocketChannel client = ctx.channel();
if (msg.startsWith("login:")) {
String username = msg.substring(6);
if (server.users.putIfAbsent(username, client) == null) {
server.broadcastMessage(username + " logged in.", client);
client.writeAndFlush("Login successful, " + username + "\n");
} else {
client.writeAndFlush("Username already taken.\n");
}
} else if (msg.startsWith("logout:")) {
String username = msg.substring(7);
SocketChannel removed = server.users.remove(username);
if (removed != null) {
server.broadcastMessage(username + " logged out.", removed);
removed.writeAndFlush("Logout successful.\n");
}
} else {
client.writeAndFlush("Unknown command.\n");
}
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
SocketChannel client = ctx.channel();
server.users.put("Client-" + server.idCounter.incrementAndGet(), client);
server.broadcastMessage("New user joined the chat.", client);
client.writeAndFlush("Welcome to the chat.\n");
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
SocketChannel client = ctx.channel();
String username = server.users.inverse().get(client);
if (username != null) {
server.users.remove(username);
server.broadcastMessage(username + " left the chat.", client);
}
}
}
客户端示例:
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
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 java.util.Scanner;
public class AuthChatClient {
public void start(String host, int port) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new AuthChatClientHandler());
}
});
ChannelFuture f = b.connect(host, port).sync();
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
AuthChatClient client = new AuthChatClient();
client.start("localhost", 8080);
}
}
class AuthChatClientHandler extends SimpleChannelInboundHandler<String> {
private Scanner scanner = new Scanner(System.in);
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("Received: " + msg);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.print("Enter username: ");
String username = scanner.nextLine();
ctx.writeAndFlush("login:" + username);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Disconnected from the server.");
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateHandler) {
System.out.print("Enter 'logout': ");
String message = scanner.nextLine();
if ("logout".equals(message)) {
ctx.writeAndFlush("logout:" + message);
} else {
System.out.println("Received: " + message);
ctx.writeAndFlush("message:" + message);
}
}
}
}
在上述示例中,服务端实现了简单的用户登录和退出功能。当客户端发送login:
命令时,会尝试登录,并广播登录消息。当客户端发送logout:
命令时,会尝试退出,并广播退出消息。客户端断开连接时,会自动从用户列表中移除。
共同学习,写下你的评论
评论加载中...
作者其他优质文章