netty学习推荐书_Netty学习之Netty介绍

Netty学习之Netty介绍

前言

本周开始学习Netty,主要的参考资料是《Netty In Action》原版书,这本书写得真好,一开始学习Netty的时候,看得有些云里雾里,后面弄懂之后,再回头看一下这本书,就发现这本书真的言简意赅,精炼地将Netty的各个组件展现出来。

传统的Java网络编程

在传统的Java网络编程中,是基于阻塞形式的IO,在这种形式的IO模型中,由于当数据没有到来的时候,对应的线程会阻塞,所以,需要为每一个请求都分配一个线程(当然,可以通过线程池来复用线程,从而减少线程创建以及销毁的代价),然而,实际上需要消耗的资源还是非常巨大的,比如,如果同一时刻有1k的并发量,那么至少就需要有1k的左右的线程来处理(数据不严谨,但是足够表达意思了)。

如下代码所示

public class OIo {

private static boolean isStop = false;

private static ExecutorService executorService;

public static void main(String[] args) throws IOException {

ServerSocket server = new ServerSocket(8080);

// 通过线程池的方式来处理

executorService = Executors.newCachedThreadPool();

// 监听多个请求

while (!isStop) {

Socket clientSocket = server.accept();

// 每个请求分配对应的线程来处理

// 由于是阻塞IO,所以如果不分配线程来处理,会导致同一时间只能处理

// 一个请求

executorService.submit(new NetworkHandler(clientSocket));

}

}

static class NetworkHandler implements Runnable {

private Socket clientSocket;

public NetworkHandler(Socket socket) {

this.clientSocket = socket;

}

public void run() {

BufferedReader reader = null;

try {

reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

PrintWriter writer = new PrintWriter(clientSocket.getOutputStream());

String requestContent, responseContent;

// 建立连接之后不停读取数据

while((requestContent = reader.readLine()) != null) {

if("DONE".equals(requestContent)) {

break;

}

responseContent = handleRequest(requestContent);

writer.write(responseContent);

}

writer.flush();

reader.close();

writer.close();

} catch (IOException e) {

e.printStackTrace();

}

}

private String handleRequest(String request) {

System.out.println("request: " + request);

return "handed";

}

}

}

显然,上面的这种模式在并发比较大的时候是不太适合使用的,于是,在Java4之后,引入了Nio模式,通过选择器(selector)来监视多个channel(多个连接),当数据到达时负责与对应的channel相互,这样,在建立连接的时候,就不需要为每个连接都分配一个线程了,Nio是基于非阻塞形式的IO,所以,相对于传统的阻塞IO,有了很大的性能提升,然而,由于Java中提供的Nio过于底层,而且重复的操作比较多,所以,很多的大牛们就开始对其进行抽象,封装,Netty就是其中的一个了。

Netty介绍

Netty是一个异步的,事件驱动的网络应用框架。

具有众多优秀的特性

通用的API(支持阻塞跟非阻塞socket)

支持TCP、UDP

逻辑以及网络底层处理分隔

灵活及可扩展的事件模型

高度自定义线程模型

高吞吐量,低延迟

消耗资源低

支持SSL/TSL

社区支持

Netty官网:Netty官网

通过上面的介绍,我们应该明确的是Netty是一个网络框架,所以,它所做的事情其实就是网络的处理(建立连接、发送数据等),这一点需要搞清楚(一开始就是没有弄清楚Netty是什么,所以在学习的时候出现了混乱)。而我们使用Netty,一般是在它上面进行我们的业务操作(比如实现IM逻辑、实现RPC等等),当然,如果有精力研究其源码,也是非常不错的,据说Netty的源码也是非常值得学习的(先挖个坑,后面有机会一定学习、分析一波)

在上面Netty的介绍的时候,我们看到了异步、事件驱动这两个词,先来谈谈异步,与异步对应的还有一个词,同步

同步指的是当请求发出之后,必须等到请求回来,才完成操作,异步则不是,异步是在请求发出之后,立即结束,不用等到请求返回才结束操作。

与这两个词关联的还有阻塞跟非阻塞,阻塞是指请求发出之后,如果没有数据,则对应的操作会被阻塞(具体的就是对应的线程会进入阻塞态,等到数据能获取,再变为可运行态),非阻塞则是指即使没有数据到来,此时对应的线程可以进行其他的操作,而不会被动进入阻塞态,即不会被阻塞。

关于这四个词语的更多的解析及举例,可以参考知乎的这篇问答:怎样理解阻塞非阻塞与同步异步的区别

对于事件驱动,一般是与异步相结合的,对于同步而言,由于操作会等到出结果了才返回,所以直接调用就能获取结果了(成功/失败),然而,对于异步而言,由于调用后立即返回,所以根据返回内容是没法知道结果的,那怎么知道结果呢,一般是通过注册回调函数来实现,当有结果的时候,事件发出者/监管者会调用对应的回调函数来通知我们操作已经完成了,也就是说,整个操作的过程中,是通过事件来进行沟通,而不是通过直接调用来沟通。理清楚这一点对于学习Netty非常重要,因为Netty中是通过事件来进行消息的传递的。

Netty核心组件

Netty是由几个核心的组件构成的

Channels,NIO的基本组件,表示一个打开的连接(网络,文件),可以用于处理一个或者多个不同的I/O操作(读/写)

Callbacks,一个方法,提供给另一个方法的引用,用于后者在合适的时候调用,Netty使用回调函数处理事件,当回调函数触发时,事件能够被实现了ChannelHandler接口的处理器处理(其实就是事件发生时,回调函数被调用)(被动)

Futures,提供了另外一种通知应用操作已经完成的方法(主动),异步操作的结果占位符,通过该方式,可以在后面访问到异步操作的结果,由于JDK中的Future比较笨重,Netty提供了自己的实现ChannelFuture,用于当异步操作执行的时候。并且允许我们向ChannelFuture注册一个或者多个ChannelFutureListener对象,当操作完成时,这些对象的回调方法operationComplete()会被调用,Netty的每个outBoundI/O均返回一个ChannelFuture,也即不阻塞

Events and Handlers,Netty使用不同的事件通知我们状态的改变或者操作的结果,然后我们可以采用不同的机制来处理不同的事件,如:日志记录、数据传输、流控制、应用逻辑等,由于Netty是网络框架,所以事件是根据inbound或者outbound数据流来区分的,如:连接建立或者断开、数据读取、用户事件、错误事件;远程端口打开或者关闭、写或者刷新数据

EventLoop,连接到每个Channel用于处理事件,包括:注册事件、分发事件到ChannelHandler、调度行动,每个EventLoop本身由一个线程来驱动,用于处理一个Channel中所有的I/O事件,并且在其生命周期中不会改变

基本上理清楚上面几个组件的作用以及相互的关系,就能掌握Netty的使用了。

第一个Netty应用

为了形象地认识Netty,这里通过一个简单的小例子来理解,顺便配置下环境

首先导入Netty依赖

io.netty

netty-all

4.1.12.Final

对应的小程序

public class EchoServer {

private final int PORT = 8080;

public static void main(String[] args) {

new EchoServer().start();

}

public void start() {

// 创建事件处理器

EventLoopGroup group = new NioEventLoopGroup();

// 创建启动器

ServerBootstrap bootstrap = new ServerBootstrap();

// 配置启动器

bootstrap.group(group)

// 配置建立连接的处理器

.channel(NioServerSocketChannel.class)

// 配置连接建立后的处理器

// 当一个新连接建立后,就会新建一个childHandler

.childHandler(new ChannelInitializer() {

@Override

protected void initChannel(SocketChannel ch) throws Exception {

// 将逻辑处理器添加到channel对应的pipeline中

ch.pipeline().addLast(new EchoServerHandler());

}

});

try {

// 绑定地址并且建立连接,sync()表示等到连接绑定完成

ChannelFuture future = bootstrap.bind(PORT).sync();

// 阻塞直至连接关闭

future.channel().closeFuture().sync();

} catch (InterruptedException e) {

e.printStackTrace();

}finally {

try {

// 关闭连接,释放资源

group.shutdownGracefully().sync();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

/**

* 逻辑处理器

* 这里是直接将收到的数据发送回去

*/

class EchoServerHandler extends ChannelInboundHandlerAdapter {

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

ByteBuf data = (ByteBuf) msg;

System.out.println("request: " + data.toString(CharsetUtil.UTF_8));

// 将收到的数据发送回去给客户端

ctx.writeAndFlush(data);

}

@Override

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

cause.printStackTrace();

// 关闭连接

ctx.close();

}

}

}

启动服务之后,直接通过telnet程序进行测试即可。

总结

本小节主要是进行Netty的介绍,包括传统阻塞IO的缺点以及Netty的几个特性,最后通过一个简单的小例子展示了Netty的基础用法,后面将详细学习Netty的几个组件及具体作用。

你可能感兴趣的:(netty学习推荐书)