Server端
- NioEventLoopGroup 是用来处理I/O操作的多线程事件循环器,Netty 提供了许多不同的 EventLoopGroup 的实现用来处理不同的传输。在这个例子中我们实现了一个服务端的应用,因此会有2个 NioEventLoopGroup 会被使用。第一个经常被叫做‘boss’,用来接收进来的连接。第二个经常被叫做‘worker’,用来处理已经被接收的连接,一旦‘boss’接收到连接,就会把连接信息注册到‘worker’上。如何知道多少个线程已经被使用,如何映射到已经创建的 Channel上都需要依赖于 EventLoopGroup 的实现,并且可以通过构造函数来配置他们的关系。
io.netty.channel Interface EventLoopGroup
- EpollEventLoopGroup
- LocalEventLoopGroup
- MultithreadEventLoopGroup
- NioEventLoop
- NioEventLoopGroup
- OioEventLoopGroup
- SingleThreadEventLoop
- ThreadPerChannelEventLoop
- ThreadPerChannelEventLoopGroup
// 第一步,创建事件循环器
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
- ServerBootstrap 是一个启动 NIO 服务的辅助启动类。你可以在这个服务中直接使用 Channel,但是这会是一个复杂的处理过程,在很多情况下你并不需要这样做
ServerBootstrap serverBootstrap = new ServerBootstrap();
- 设置启动器的group
serverBootstrap.group(bossGroup, workerGroup)
- 指定channel
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
- 设置handler
.childHandler(new ChatInitializer())
- 设置channel参数 ChannelOption channelConfig
// option() 是提供给NioServerSocketChannel 用来接收进来的连接
.option(ChannelOption.SO_BACKLOG, 128)
- 设置childOption参数
// childOption() 是提供给由父管道 ServerChannel 接收到的连接
.childOption(ChannelOption.SO_KEEPALIVE, true)
- 绑定端口,开始接收进来的连接
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
- 等待服务器socket关闭
channelFuture.channel().closeFuture().sync();
- 优雅的关闭channel
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
完整代码
/**
* Server启动类
*/
public class ChatServer {
private int port;
ChatServer(int port) {
this.port = port;
}
public static void main(String[] args) {
int port = 8080;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
}
new ChatServer(port).start();
}
private void start() {
// 1 定义两个事件循环器,boss用于接收连接,worker用于处理连接,
// 一旦boss接收到连接,就会吧信息注册到worker上
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 2. ServerBootstrap 是一个启动 NIO 服务的辅助启动类。
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // 指定使用的channel为NIO
.localAddress(new InetSocketAddress(port)) // 设置socket地址锁使用的端口
.childHandler(new ChatInitializer())
.option(ChannelOption.SO_BACKLOG, 128) // 指定channel实现的配置参数
.childOption(ChannelOption.SO_KEEPALIVE, true);
// 绑定端口,开始接收进来的连接
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
// 等待服务器socket关闭
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 优雅的关闭服务器
// 当EventLoopGroup 被完全地终止,并且对应的所有 channel 都已经被关闭时,
// Netty 会返回一个Future对象来通知你
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
/**
* 用来增加多个的处理类到 ChannelPipeline 上,包括编码、解码、Handler 等
*
* ChannelInitializer 是一个特殊的处理类,他的目的是帮助使用者配置一个新的 Channel。
* 也许你想通过增加一些处理类比如DiscardServerHandler 来配置一个新的 Channel
* 或者其对应的ChannelPipeline 来实现你的网络程序。当你的程序变的复杂时,
* 可能你会增加更多的处理类到 pipline 上,然后提取这些匿名类到最顶层的类上
*/
public class ChatInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 通过socketChannel获取对应的管道
ChannelPipeline channelPipeline = socketChannel.pipeline();
// 通过管道添加handler
// HttpServerCodec是由netty自己提供的助手类,可以理解为拦截器
// 当请求到服务端,我们需要做解码,响应到客户端做编码
// WebSocket基于http协议,所以要有http编解码器
channelPipeline.addLast("HttpServerCodec", new HttpServerCodec());
// 对写大数据流的支持
channelPipeline.addLast(new ChunkedWriteHandler());
// 对httpMessage进行聚合,聚合成FullRequest活FullResponse, 几乎所有的netty编程,都会用的此handler
channelPipeline.addLast(new HttpObjectAggregator(1024 * 64));
// ========================= 以上是用于http协议 =========================
// webSocket服务器处理的协议,用于指定给客户端连接访问的路由: /ws
// 本handler会帮你处理一些繁重的复杂的事
// 会帮你处理握手动作: handshaking(close, ping, pong) ping + pong = 心跳
// 对于webSocket来讲,都是以frames进行传输的,不同的数据类型对应的frames也不同
channelPipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
channelPipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
// 编码和解码
channelPipeline.addLast("decoder", new StringDecoder());
channelPipeline.addLast("encoder", new StringEncoder());
// 添加自定义助手类
channelPipeline.addLast("ChatServerHandler", new ChatHandler());
System.out.println("server is online...");
}
/**
* SimpleChatServerHandler 继承自 SimpleChannelInboundHandler
* 这个类实现了 ChannelInboundHandler接口,ChannelInboundHandler 提供了许多事件处理的接口方法,
* 然后你可以覆盖这些方法。现在仅仅只需要继承 ChannelInboundHandlerAdapter 类而不是你自己去实现接口方法
*/
public class ChatHandler extends SimpleChannelInboundHandler {
// 用于记录和管理所有客户端的channel
public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
/**
* 每当从服务端读到客户端写入信息时,将信息转发给其他客户端的 Channel。
* 其中如果你使用的是 Netty 5.x 版本时,需要把 channelRead0() 重命名为messageReceived()
*
* @param ctx
* @param msg
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("read");
Channel incoming = ctx.channel();
for (Channel channel : channels) {
if (channel != incoming) {
// 打印其他的channel msg
channel.writeAndFlush("[" + incoming.remoteAddress() + " - ID: " + incoming.id().asShortText() + "]" + msg + "\n");
} else {
// 打印自己的channel msg
channel.writeAndFlush("[you]" + msg + "\n");
}
}
channels.add(ctx.channel());
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
System.out.println("chatClient: " + incoming.remoteAddress() + " - ID: " + incoming.id().asShortText() + "注册\n");
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
System.out.println("chatClient: " + incoming.remoteAddress() + " - ID: " + incoming.id().asShortText() + "注销\n");
}
/**
* 服务端监听到客户端活动
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
System.out.println("chatClient: " + incoming.remoteAddress() + " - ID: " + incoming.id().asShortText() + "在线\n");
}
/**
* 服务端监听到客户端不活动
*
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
System.out.println("chatClient: " + incoming.remoteAddress() + " - ID: " + incoming.id().asShortText() + "在线\n");
}
/**
* 当出现 Throwable 对象才会被调用,即当 Netty 由于 IO 错误或者处理器在处理事件时抛出的异常时。
* 在大部分情况下,捕获的异常应该被记录下来并且把关联的 channel 给关闭掉。然而这个方法的处理方式
* 会在遇到不同异常的情况下有不同的实现,比如你可能想在关闭连接之前发送一个错误码的响应消息
*
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
Channel incoming = ctx.channel();
System.out.println("chatClient: " + incoming.remoteAddress() + " - ID: " + incoming.id().asShortText() + "异常\n");
// 出现异常关闭连接
// 打印异常信息
cause.printStackTrace();
ctx.close();
}
/**
* 每当从服务端收到新的客户端连接时,客户端的 Channel 存入ChannelGroup列表中,
* 并通知列表中的其他客户端 Channel
*
* @param ctx
* @throws Exception
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
for (Channel channel : channels) {
channel.writeAndFlush("[client] - " + incoming.remoteAddress() + " - ID: " + incoming.id().asShortText() + "加入\n");
}
channels.add(ctx.channel());
}
/**
* 每当从服务端收到客户端断开时,客户端的 Channel 移除 ChannelGroup 列表中,
* 并通知列表中的其他客户端 Channel
*
* @param ctx
* @throws Exception
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
for (Channel channel : channels) {
channel.writeAndFlush("[client] - " + incoming.remoteAddress() + " - ID: " + incoming.id().asShortText() + "离开\n");
}
channels.add(ctx.channel());
}
}
client
public class ChatClient1 {
private String address;
private int port;
public ChatClient1(String address, int port) {
this.address = address;
this.port = port;
}
public static void main(String[] args) {
new ChatClient1("localhost", 8080).start();
}
private void start() {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("encoder", new StringEncoder());
pipeline.addLast("handler", new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg);
}
});
}
});
Channel channel = bootstrap.connect(address, port).sync().channel();
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
while (true) {
System.out.println("sout:" + in.readLine());
System.out.println(channel.id().asShortText());
channel.writeAndFlush(in.readLine() + "\r\n");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
}