Netty是一个NIO客户端服务器框架,可以快速轻松地开发协议服务器和客户端等网络应用程序。它极大地简化并简化了TCP和UDP套接字服务器开发等网络编程。
目前版本:4.1.36
支持:
最低Oracle JDK 1.7
支持最新的Maven
JDK 9的用法:
Netty可以在模块化的JDK9应用程序中作为自动模块的集合使用。模块名称遵循反向dns样式,并且由于历史原因派生自子项目名称,而不是根包。它们列在下面:
io.netty.all
io.netty.buffer
io.netty.codec
io.netty.codec.dns
io.netty.codec.haproxy
io.netty.codec.http
io.netty.codec.http2
io.netty.codec.memcache
io.netty.codec.mqtt
io.netty.codec.redis
io.netty.codec.smtp
io.netty.codec.socks
io.netty.codec.stomp
io.netty.codec.xml
io.netty.common
io.netty.handler
io.netty.handler.proxy
io.netty.resolver
io.netty.resolver.dns
io.netty.transport
io.netty.transport.epoll (native omitted - reserved keyword in Java)
io.netty.transport.kqueue (native omitted - reserved keyword in Java)
io.netty.transport.unix.common (native omitted - reserved keyword in Java)
io.netty.transport.rxtx
io.netty.transport.sctp
io.netty.transport.udt
Automatic modules 不提供任何声明依赖项的方法,因此需要在module-info文件中单独列出每个使用的模块。
Netty的支持:
入门例子:
官方作者开了个玩笑:世界上最简单的协议不是“Hello World”,而是DISCARD,它是一种在没有任何响应的情况下丢弃任何接收数据的协议。意思就说服务器接受到Client的消息,但我什么都不做,直接把消息扔掉。
要实现该DISCARD协议,您唯一需要做的就是忽略所有收到的数据。让我们直接从处理程序实现开始,它处理由Netty生成的I / O事件。
1 服务端代码:
1-1 服务端main代码,关键代码为 sc.pipeline().addLast(new DiscardServerHandler());
Handler为DiscardServer服务器的核心代码类,关键代码意思就是,当有一个Client连接进来时,就会为客户端连接分发一条管道,然后调用其中的Handler进行逻辑处理,所以Client中的所有业务逻辑都是通过handler类来进行处理的,handler类在Netty中很重要,无时无刻都需要使用
package 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;
public class DiscardServer {
private int port;
public DiscardServer(int port){
this.port = port;
}
public void run() throws Exception {
//NioEventLoopGroup是一个处理I/O多线程事件环
//bossGroup接受传入连接,workerGroup通称为工人,一旦boss接受连接并将连接注册到worker
//第一个线程组 是用于接受Client端连接的
EventLoopGroup bossGroup = new NioEventLoopGroup(); //(1)TCP
//第二个线程组 是用于实际的业务处理操作的
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap(); //(2)创建服务器的辅助类,对server进行一些列的配置
//把两个工作线程组加入进来
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) //(3)接受传入连接->boss
.childHandler(new ChannelInitializer<SocketChannel>() { //(4)ChannelInitializer帮助用户(worker)配置新的Channel
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(new DiscardServerHandler());
}
})
/**
* 服务器端TCP内核模块维护2个队列,我们称之为A和B吧
* 客户端向服务器端connect的时候,会发送带有SYN标志的包(第一次握手)
* 服务器端收到客户端发来的SYN时,向客户端发送SYN ACK确认(第二次握手)
* 此时TCP内核模块吧客户端连接加入到A队列中,然后服务器收到客户端发来的ACK时(第三次握手)
* TCP内核模块把客户端连接从A队列移到B队列,连接完成,应用的accept会返回
* 也就是说accept从B队列中取出完成三次握手的连接。
* A队列和B队列的长度之和时backlog.当A,B队列的长度之和大于backlog时,新的连接将会被TCP内核拒绝。
* 所以,如果时backlog过小,可能出现accept速度跟不上,A,B队列满了,导致新的客户端无法连接。
* 要注意的时:backlog对于程序支持的连接数并无影响,backlog影响的只是还没有被accept取出的链接。
*/
//BACKLOG用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放
// 已完成三次握手的请求队列的最大长度
.option(ChannelOption.SO_BACKLOG, 128) //(5)
.option(ChannelOption.SO_RCVBUF, 32*1024) //这是接受缓冲大小
.childOption(ChannelOption.SO_KEEPALIVE, true); //(6)是否保持心跳活动
//绑定端口并开始接受传入的连接
ChannelFuture f = b.bind(port).sync(); //(7)
//猜测(没看过源码):代码执行到这里的时候会启动一个子线程一直对服务器进行监听,直到服务器套接字关闭,然后子线程通知main线程进行结束,但是本例不会出现这种状况,官方说这样能够优雅的关闭服务器,哈哈。
f.channel().closeFuture().sync();
//以下为主动关闭Netty服务器
//f.channel().close();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
if (args.length > 0){
port = Integer.parseInt(args[0]);
}
new DiscardServer(port).run();
}
}
1-2 Handler代码
package netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
/**
* @description 处理服务器端通道,该适配器实现类ChannelInboundHandler接口,
* 该接口提供可以覆盖各种事件处理程序的方法。现在只需要扩展ChannelInboundHandlerAdapter
* 而不是去实现ChannelInboundHandler接口
*/
public class DiscardServerHandler extends ChannelInboundHandlerAdapter { //(1)
// 每当从客户端接受到新数据时,都会使用受到的消息调用此方法,ChannelHandlerContext对象提
// 供各种操作使您可以触发各种I/O的事件和操作。这里通过write()逐字写入接收到的消息,这里的
// 写入只是写到缓冲区而已,并没有写到客户端,通过flush()将缓冲区的内容冲刷到客户端
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //(2)
ByteBuf in = (ByteBuf) msg;
//静静的丢弃接受到的数据,实现discard协议,ByteBuf是引用一个计数对象,测试的可以把in.release()的注释解除,下面的代码全部注释上
//in.release();
try {
while (in.isReadable()){ //(3)
System.out.println((char)in.readByte());
System.out.flush();
}
// System.out.println(in.toString(CharsetUtil.US_ASCII)); //(3)简化版
//(4)response
// ctx.write(msg);
// ctx.flush();
ctx.writeAndFlush(msg);//(4)
} finally {
// ReferenceCountUtil.release(msg);//(5)关闭关联的通道
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//在引发异常时关闭连接
cause.printStackTrace();
ctx.close();
}
}
显示结果
在cmd窗口运行命令:telnet localhost 8080
在窗口下输入一串字符,发现客户端和服务端什么东西都没有输出,那是因为消息被服务端丢弃了,也不做任何响应
PS:window10下telnet默认时关闭的,需要手动开启:控制面板\所有控制面板项\程序和功能
在“启动或关闭window功能”处。
PS:奇怪的是ByteBuf为啥不像NIO那样使用filp()?是因为ByteBuf内部维护了两个指针,一个读指针,一个写指针,读指针值的是起始位,当数据写入的时候,只有写指针在移动,写指针代表的是末位置。