目录
简介
Netty是 一个异步事件驱动的网络应用程序框架,一个基于NIO客户端服务器框架,支持多种协议(自定义的传输协议),可以灵活快速的搭建与开发,支持自定义扩展时间模型和通用的通信API并且Byte Buffer实现了零拷贝;用于快速开发可维护的高性能协议服务器和客户端。学netty之前可以先了解一下NIO模型。
特性
- 使用各种传输类型的统一API--阻塞和非阻塞嵌套字。
- 基于灵活且扩展的事件模型,可将关注点明确分离。
- 高度可定制的线程模型-单线程,一个或多个线程池。
- 从3.1版本后真正的无连接数据报套接字支持。
- 更高的吞吐量,更低的延迟
- 减少资源消耗
- 减少不必要的内存复制
- 完整的SSL/TLS和StartTLS支持
NIO简单介绍
nio是种异步事件驱动模型,该模型种有3个重要部分:Buffer、Channel、Selector。Nio是基于Channel和Buffer进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中,Selector用于监听多个通道的事件(例如连接、读写操作),因此单个线程可以监听多个数据通道。说白了,NIO网络之间的通信与交互主要用到Channel和Buffer。NIO详细介绍后面会在IO模型中详细介绍。
事件模型图:
入门
下面我想以例子的入门学习netty。
DiscardDemo(只有接受没有响应的Server)
DiscardServer案例:
public class DiscardServer {
private int port;
public DiscardServer(int port){
this.port = port;
}
public static void main(String[] args) {
new DiscardServer(8080).start();
}
private void start() {
//主处理事件,主要是用于通信连接作用
EventLoopGroup bossGroup = new NioEventLoopGroup();
//工作处理事件,主要是用于通信的读写等操作
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup,workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new DiscardHandler());//这里是通过pipeline来将你的处理器追加到channel中。
}
}).option(ChannelOption.SO_BACKLOG,2)
.childOption(ChannelOption.SO_KEEPALIVE,true);
//异步启动服务。
ChannelFuture sync = bootstrap.bind(port).sync();
//异步关闭channel。
sync.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
//优雅停服
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
DiscardHandler:
public class DiscardHandler extends ChannelInboundHandlerAdapter {
/**
* 读取客户端发来的消息
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
System.out.println("收到客户端的信息。");
ByteBuf byteBuf = (ByteBuf) msg;
while (byteBuf.isReadable()) {
System.out.println((char) byteBuf.readByte());
}
} finally {
ReferenceCountUtil.release(msg);
}
}
//出现异常的时候会触发
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}
模拟telnet通信的客户端和服务器
telnetServer
public class TelnetServer {
private int port ;
public TelnetServer(int port) {
this.port = port;
}
public static void main(String[] args) {
new TelnetServer(8080).start();
}
private void start() {
EventLoopGroup boss = new NioEventLoopGroup(1);
EventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss,worker)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,128)
.childOption(ChannelOption.SO_KEEPALIVE,true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
//这里使用了字符串的编解码处理Handler,是因为TelnetServerHandler继承了SimpleChannelInboundHandler,
// 而SimpleChannelInboundHandler,netty已经帮你处理了字符串的编译和解码操作,所以需要加上
.addLast(new StringDecoder())
.addLast(new StringEncoder())
.addLast(new TelnetServerHandler());
}
});
ChannelFuture channelFuture = bootstrap.bind(port).sync();
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
TelnetServerHandler
public class TelnetServerHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
System.out.println("收到客户端发来的数据:" + s);
//把接收到的字符串加个?返回回去
channelHandlerContext.writeAndFlush(s+"?");
//还有种写法,客户端和服务端通信主要是通过channel来进行交互,这种写法主要是为了下面群聊系统的伏笔
// channelHandlerContext.channel().writeAndFlush(s+"?");
}
}
TelnetClient
public class TelnetClient {
private static final String IP = "127.0.0.1";
private static final int port = 8080;
public static void main(String[] args) {
EventLoopGroup worker = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(worker)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new StringDecoder())
.addLast(new StringEncoder())
.addLast(new TelnetClientHandler());
}
});
Channel channel = bootstrap.connect(IP, port).sync().channel();
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
ChannelFuture write = null;
while (true) {
String line = reader.readLine();
if (line == null ){
break;
}
if ("bye".equals(line)) {
channel.closeFuture().sync();
break;
}
write = channel.writeAndFlush(line);
if (write != null ){
write.sync();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
worker.shutdownGracefully();
}
}
}
TelnetClientHandler
public class TelnetClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
System.out.println("收到服务端发来的信息:"+s);
}
}
基于上面的telnet案例来做个简易的群聊系统
ChatServer
public class ChatServer {
private int port;
public ChatServer(int port) {
this.port = port;
}
public static void main(String[] args) {
new ChatServer(8080).start();
}
private void start() {
EventLoopGroup boss = new NioEventLoopGroup(1);
EventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss,worker)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,1024)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel
.pipeline()
//这里主要是处理粘包的问题,字符串必须是以\r\n结尾才能被认为是一个完整的数据信息。
.addLast(new DelimiterBasedFrameDecoder(1024, Delimiters.lineDelimiter()))
.addLast(new StringDecoder())
.addLast(new StringEncoder())
//myHandler
.addLast(new MyServerHandler());
}
})
.childOption(ChannelOption.SO_KEEPALIVE,true);
bootstrap.bind(port).sync().channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
MyServerHandler
public class MyServerHandler extends SimpleChannelInboundHandler<String> {
public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
SocketAddress socketAddress = channel.remoteAddress();
String welcome = new Date() +"欢迎"+socketAddress+"来到聊天室\r\n";
channel.writeAndFlush(welcome);
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
channels.add(channel);
for (Channel c : channels) {
if (c == channel) {
continue;
}
c.writeAndFlush(c.remoteAddress()+"上线"+"\r\n");
}
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
channels.remove(channel);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception {
Channel channel = ctx.channel();
SocketAddress socketAddress = channel.remoteAddress();
System.out.println(socketAddress+"说:"+s);
// channel.writeAndFlush(socketAddress+"说:"+s);
// ctx.writeAndFlush("aaaa");
for (Channel c : channels) {
if (c == channel) {
continue;
}
c.writeAndFlush(socketAddress+"说:"+s+"\r\n");
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
Channel channel = ctx.channel();
SocketAddress socketAddress = channel.remoteAddress();
String leave = socketAddress+"离开了聊天室"+"\r\n";
for (Channel c : channels) {
if (c == channel) {
continue;
}
c.writeAndFlush(leave);
}
}
}
ChatClient
public class ChatClient {
private static final int PORT = 8080;
private static final String IP = "127.0.0.1";
public static void main(String[] args) {
EventLoopGroup worker = new NioEventLoopGroup(1);
try {
Bootstrap b = new Bootstrap();
b.group(worker)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, Delimiters.lineDelimiter()))
.addLast(new StringDecoder())
.addLast(new StringEncoder())
//myHandler
.addLast(new MyClientHandler());
}
});
Channel channel = b.connect(IP, PORT).sync().channel();
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
ChannelFuture writeAndFlush = null;
while (true) {
try {
String line = reader.readLine();
if (line == null) {
break;
}
if ("bye".equals(line)) {
channel.closeFuture().sync();
break;
}
writeAndFlush = channel.writeAndFlush(line + "\r\n");
if (writeAndFlush != null) {
writeAndFlush.sync();
}
} catch (IOException e) {
System.out.println("写数据异常。。。"+e.getMessage());
}
}
} catch (InterruptedException e) {
System.out.println("异常。。。"+e.getMessage());
} finally {
worker.shutdownGracefully();
}
}
}
MyClientHandler
public class MyClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
System.out.println(s);
}
}
上面的例子都是通过netty中example项目中的案例进行演变和研究,后面会持续更新