Hello World
1 maven导入依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.63.Final</version>
</dependency>
2 编写服务器端
public class NettyServer {
public static void main(String[] args) {
//负责处理链接操作 1表示创建一个线程
EventLoopGroup boss = new NioEventLoopGroup(1);
//负责会话操作
NioEventLoopGroup server = new NioEventLoopGroup(10);
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss,server);
serverBootstrap.channel(NioServerSocketChannel.class);
//定义channel创建handler
serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel channel) throws Exception {
System.out.println("建立channel--->"+channel.remoteAddress());
//为channel添加管道,用于处理数据
//添加解析为字符串的管道
channel.pipeline().addLast(new StringDecoder());
//添加接受到数据时的处理
channel.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("接收到数据-->"+msg);
super.channelRead(ctx, msg);
}
});
}
});
serverBootstrap.bind(8888);
}
}
3编写客户端
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(new NioEventLoopGroup(1));
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//添加将字符串转为bytebuf的
pipeline.addLast(new StringEncoder());
}
});
//设置连接地址
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8888));
//开始链接,同步模式 阻塞至链接成功
channelFuture.sync();
//获取channel 写入数据
Channel channel = channelFuture.channel();
channel.writeAndFlush("hello World");
}
}
先启动服务器,再启动客户端,服务器能成功收到客户端的消息。
netty内组件的使用
Nettty 有如下几个核心组件:
-
EventLoop
-
Channel
-
ChannelFuture
-
ChannelHandler
-
ChannelPipeline
EventLoopGroup
EventLoopGroup:事件循环对象是netty重要组件之一,类似于线程池用于处理任务
public static void main(String[] args) {
//最常用的 功能最全面,能处理 io任务,普通任务和定时任务
//设置 loopGroup 内的线程数量为2(默认值为cpu核心数*2)
EventLoopGroup loopGroup = new NioEventLoopGroup(2);
// 获取下一时间循环对象,循环对象是按顺序获取的
loopGroup.next(); //获取的是第1个 eventloop
loopGroup.next(); //获取的是第2个 eventloop
loopGroup.next();//获取的是第1个 eventloop
//获取一个eventloop 用于执行任务
EventLoop eventLoop = loopGroup.next();
//往线程内添加任务
eventLoop.submit( () -> {
System.out.println("打印-----");
});
//执行一个延时任务 5秒后执行
eventLoop.schedule( ()->{
System.out.println("延时任务");
},5, TimeUnit.SECONDS);
//定时任务 interval 10秒后开始 每1秒打印一次
eventLoop.scheduleAtFixedRate(()->{
System.out.println("定时任务");
},10,1,TimeUnit.SECONDS);
//停止
loopGroup.shutdownGracefully();
}
Channel和ChannelFuture
Channel 是 Netty 网络操作抽象类,它除了包括基本的 I/O 操作,如 bind、connect、read、write 之外,还包括了 Netty 框架相关的一些功能,如获取该 Channe l的 EventLoop。
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup loopGroup = new NioEventLoopGroup(1);
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(loopGroup); //设置事件循环组
bootstrap.channel(NioSocketChannel.class); //设置管道类型
bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringEncoder()); //添加转换string的handler
}
});
//连接服务器
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8888));
//同步
//ChannelFuture channelFuture = localhost.sync();
//System.out.println("连接服务器成功");
//start(channelFuture.channel(),loopGroup);
//异步
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
System.out.println("-------与服务器建立连接成功!");
start(channelFuture.channel(),loopGroup);
}
});
}
public static void start(Channel channel,EventLoopGroup group) throws InterruptedException {
Scanner scanner = new Scanner(System.in);
String s = "";
do{
System.out.println("请输入===>");
s = scanner.nextLine();
channel.writeAndFlush(s); //向客户端写入并刷出数据
}while(!"n".equals(s));
System.out.println("正在与服务器断开连接");
ChannelFuture close = channel.close(); //断开连接
// close.sync(); //会阻塞直到连接断开 同步
close.addListener((ChannelFutureListener) future -> { //设置断开连接成功后的回调
System.out.println("服务器连接以断开!");
group.shutdownGracefully();
});
}
}
Handler
我们可以通过获取channel的pipeline后为channel添加handler处理channel的各种事件,常用的Handler有:
ChannelInboundHandlerAdapter : 处理客户端方面的操作
ChannelOutboundHandlerAdapter: 处理服务器端方面的操作
ChannelInboundHandlerAdapter
public static void main(String[] args) {
ServerBootstrap bootstrap = new ServerBootstrap();
NioEventLoopGroup boss = new NioEventLoopGroup(1);
NioEventLoopGroup worker = new NioEventLoopGroup(10);
bootstrap.group(boss,worker);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
//获取 pipeline
ChannelPipeline pipeline = ch.pipeline();
//将ByteBuf转换为String的handler(netty内置的)
pipeline.addLast(new StringDecoder());
//添加自定义的handler
pipeline.addLast( new ChannelInboundHandlerAdapter(){
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("注册事件");
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("取消注册事件");
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("有新客户端连接接入。。。"+ctx.channel().remoteAddress());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("失去连接(客户端关闭)");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("接受到channel发来的数据-->"+msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("channel(客户端异常关闭)");
}
});
}
});
}
执行结果:
注册事件
有新客户端连接接入。。。/127.0.0.1:52061
接受到channel发来的数据-->a
channel(客户端异常关闭)
失去连接(客户端关闭)
取消注册事件
ChannelOutboundHandlerAdapter
pipeline.addLast(new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("服务器向客户端写入数据");
super.write(ctx,msg,promise);
}
});
注意: 添加的handler是按顺序执行的,从写了handler的某一个方法后必须调用父类的方法才能够执行下一个handler。
ChannelInboundHandler是按照先后顺序执行的,ChannelOutboundHandler相反。
示例:
public static void main(String[] args) {
ServerBootstrap bootstrap = new ServerBootstrap();
NioEventLoopGroup boss = new NioEventLoopGroup(1);
NioEventLoopGroup worker = new NioEventLoopGroup(10);
bootstrap.group(boss,worker);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("handler[1] 接受数据-->"+msg);
//对数据进行修改。
super.channelRead(ctx, msg+"__abc"); //必须调用父类的该方法才能执行下一个handler
}
});
pipeline.addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("handler[2] 接受数据-->"+msg);
super.channelRead(ctx, msg); //必须调用父类的该方法才能执行下一个handler
}
});
pipeline.addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("handler[3] 接受数据-->"+msg);
ctx.channel().writeAndFlush("aabbcc");
super.channelRead(ctx, msg); //必须调用父类的该方法才能执行下一个handler
}
});
pipeline.addLast(new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("handler[4] 向客户端写入数据--->"+msg);
super.write(ctx, msg, promise);
}
});
pipeline.addLast(new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("handler[5] 向客户端写入数据--->"+msg);
super.write(ctx, msg, promise);
}
});
pipeline.addLast(new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("handler[6] 向客户端写入数据--->"+msg);
super.write(ctx, msg, promise);
}
});
}
});
bootstrap.bind(8888);
}
执行结果:可以看到OutBoundHandler是从后往前运行的
handler[1] 接受数据-->hello
handler[2] 接受数据-->hello__abc
handler[3] 接受数据-->hello__abc
handler[6] 向客户端写入数据--->aabbcc
handler[5] 向客户端写入数据--->aabbcc
handler[4] 向客户端写入数据--->aabbcc
ByteBuf
public static void main(String[] args) {
//创建 堆内存
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(10);
//创建 直接内存
//直接内存读写效率高,但是创建和销毁的速度慢,不受堆内存管理,要注意关闭
ByteBuf byteBuf1 = ByteBufAllocator.DEFAULT.directBuffer(10);
//写入boolean
byteBuf.writeBoolean(true);
//写入字符串
byteBuf.writeCharSequence("hello", StandardCharsets.UTF_8);
//写入byte数组
byteBuf.writeBytes("aa".getBytes());
//写入short
byteBuf.writeShort(1);
//写入int
byteBuf.writeInt(10);
byteBuf.writeLong(1L);
byteBuf.writeFloat(1F);
byteBuf.writeDouble(1D);
}
处理粘包半包
FixedLengthFrameDecoder
定长结构解析器可以分割客户端发来的数据,只有数据达到指定长度后才会触发一次操作
代码示例:
public static void main(String[] args) throws InterruptedException {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(new NioEventLoopGroup(1));
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringEncoder());
}
});
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8888));
channelFuture.sync();
Channel channel = channelFuture.channel();
channel.writeAndFlush("aaaaa"); //写入5个
channel.writeAndFlush("bbbbb"); //写入5个
channel.writeAndFlush("cccccd");//写入6个
channel.writeAndFlush("dddd"); //写入4个
}
server端
public static void main(String[] args) {
ServerBootstrap bootstrap = new ServerBootstrap();
NioEventLoopGroup boss = new NioEventLoopGroup(1);
NioEventLoopGroup worker = new NioEventLoopGroup(1);
bootstrap.group(boss,worker);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//必须放在第一位 5表示5个字符
pipeline.addLast(new FixedLengthFrameDecoder(5));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("获取到客户端消息-->"+msg);
}
});
}
});
bootstrap.bind(8888);
}
控制台打印结果: 每五个数据打印一次
获取到客户端消息-->aaaaa
获取到客户端消息-->bbbbb
获取到客户端消息-->ccccc
获取到客户端消息-->ddddd
LineBasedFrameDecoder和DelimiterBaseFrameDecoder
以分隔符的形式分割字符
LineBasedFrameDecoder:以换行符(\n)分割
DelimiterBaseFrameDecoder: 自定义分隔符
代码示例:
server端
public static void main(String[] args) {
ServerBootstrap bootstrap = new ServerBootstrap();
NioEventLoopGroup boss = new NioEventLoopGroup(1);
NioEventLoopGroup worker = new NioEventLoopGroup(1);
bootstrap.group(boss,worker);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//以$_$分割
ByteBuf delimiter = Unpooled.copiedBuffer("$_$".getBytes());
//必须放在第一位 10 代表如果10个字节内没有换行符则抛出 TooLongFrameException
pipeline.addLast(new DelimiterBasedFrameDecoder(10,delimiter));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("获取到客户端消息-->"+msg);
}
});
}
});
bootstrap.bind(8888);
}
client端
public static void main(String[] args) throws InterruptedException {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(new NioEventLoopGroup(1));
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringEncoder());
}
});
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8888));
channelFuture.sync();
Channel channel = channelFuture.channel();
channel.writeAndFlush("张张$_$"); //写入五个字节
channel.writeAndFlush("bbbbbbb$_$");//写入五个字节
channel.writeAndFlush("cccccd$_$");//写入6个字节
channel.writeAndFlush("dddd$_$");//写入4个字节
}
LengthFieldBasedFrameDecoder
指定消息的长度分割数据
代码示例:
server端
public static void main(String[] args) {
ServerBootstrap bootstrap = new ServerBootstrap();
NioEventLoopGroup boss = new NioEventLoopGroup(1);
NioEventLoopGroup worker = new NioEventLoopGroup(1);
bootstrap.group(boss,worker);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//必须放在第一位 表示最大长度为1024字节
//第一个0,4表示从下标第0个到第3个为消息长度值
//最后的04表示去掉4个字节长度标识(int4个字节)
pipeline.addLast(new LengthFieldBasedFrameDecoder(1024,0,4,0,4));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("获取到客户端消息-->"+msg);
}
});
}
});
bootstrap.bind(8888);
}
clent端
public static void main(String[] args) throws InterruptedException {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(new NioEventLoopGroup(1));
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringEncoder());
}
});
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8888));
channelFuture.sync();
Channel channel = channelFuture.channel();
send(channel,"张张张张张");
send(channel,"cccccd");
send(channel,"dddd");
}
public static void send(Channel channel,String msg){
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer();
byteBuf.writeInt(msg.getBytes().length); //发消息之前先写内容长度
byteBuf.writeCharSequence(msg, StandardCharsets.UTF_8);//写入内容
channel.writeAndFlush(byteBuf); //发送到服务器
}