Netty入门
一、目标
开发一个简单的服务器端和客户端
- 客户端向服务器端发送 hello, world
- 服务器仅接收,不返回
引入依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.39.Final</version>
</dependency>
二、实现代码
2.1 服务器端
package org.example.netty.one;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import lombok.extern.slf4j.Slf4j;
/**
* 目标 :客户端像服务端发送Hello World
* 服务端仅接受,不返回
*/
@Slf4j
public class Server {
public static void main(String[] args) {
//负责处理TCP/IP连接的
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
// 负责处理Channel(通道)I/O事件的 可以配置多个线程 有默认线程
NioEventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap()
// 分配boss worker
.group(bossGroup, workGroup)
//选择Socket实现类
/**
* EpollServerSocketChannel
*/
.channel(NioServerSocketChannel.class)
/**
* 下面添加的处理器都是给SocketChannel用的而不是给ServerSocketChannel,
* ChannelInitializer处理器只执行一次,作用:待客户端SocketChannel建立连接后,执行InitChannel以便添加更多的处理器
*
*/
//boss负责处理连接 worker(child) 负责处理读写,决定了 worker(child) 能执行哪些操作(handler)
.childHandler(
//channel 代表和客户端进行数据读写的通道 Initializer 初始化,初始channel通道
//本身也是一个handler,负责添加别的handler 在创建时只是添加了初始化器,并未执行初始化器的内容,
//也就是initChannel方法,创建了连接后才执行的初始化器的初始化方法
new ChannelInitializer<NioSocketChannel>() {
//在连接建立后被初始化
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
// client接收到消息后 先出处理这里面的handler
// SocketChannel的解码器 解码ByteBuf => String
//在连接建立后的初始化时会执行addLast()方法,但是不会执行处理器,处理器是在收发数据时才执行
nioSocketChannel.pipeline().addLast(new StringDecoder());
nioSocketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
// SocketChannel 业务处理器使用上一个处理器的处理结果
//触发了读事件以后要执行的操作
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.debug("msg:{}", msg);
}
});
}
});
// 绑定ServerSocketChannel监听的端口
serverBootstrap.bind(9999);
}
}
2.2 客户端
package org.example.netty.one;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
public class Client {
public static void main(String[] args) throws InterruptedException {
Bootstrap bootstrap = new Bootstrap();
// 创建NioEventLoopGroup 与server端类似
bootstrap.group(new NioEventLoopGroup())
// 选择NioSocketChannel 作为Socket实现类
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
/**
* 连接的时候执行一次
* 作用 : 建立连接以后执行 initChannel 以便添加更多的处理器
*
* @param nioSocketChannel
* @throws Exception
*/
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
// 消息会经过通道handler处理, 将String => ByteBuf 最后发出
//数据经过网络传输,到达服务器端,服务器端 的 handler 先后被触发,走完一个流程
nioSocketChannel.pipeline().addLast(new StringEncoder());
}
});
// 指定连接要连接的服务器地址以及端口
ChannelFuture future = bootstrap.connect("127.0.0.1", 9999);
// connect 是异步的 返回的是Future 所以需要通过sync 阻塞等待连接 还可以通过其他方式
future.sync();
// 写入消息并清空缓冲区
future.channel().writeAndFlush("Hello World");
}
}
2.3 流程梳理
提示
一开始需要树立正确的观念
- 把 channel 理解为数据的通道
- 把 msg 理解为流动的数据,最开始输入是 ByteBuf,但经过 pipeline 的加工,会变成其它类型对象,最后输出又变成 ByteBuf
- 把 handler 理解为数据的处理工序
- 工序有多道,合在一起就是 pipeline,pipeline 负责发布事件(读、读取完成…)传播给每个 handler, handler 对自己感兴趣的事件进行处理(重写了相应事件处理方法)
- handler 分 Inbound 和 Outbound 两类
- 把 eventLoop 理解为处理数据的工人
- 工人可以管理多个 channel 的 io 操作,并且一旦工人负责了某个 channel,就要负责到底(绑定)
- 工人既可以执行 io 操作,也可以进行任务处理,每位工人有任务队列,队列里可以堆放多个 channel 的待处理任务,任务分为普通任务、定时任务
- 工人按照 pipeline 顺序,依次按照 handler 的规划(代码)处理数据,可以为每道工序指定不同的工人