你们好,我是 「后端技术进阶」 做者,一个热爱技术的少年。html
@[toc]java
以为不错的话,欢迎 star!ღ( ´・ᴗ・` )比心git
- Netty 从入门到实战系列文章地址:https://github.com/Snailclimb/netty-practical-tutorial 。
- RPC 框架源码地址:https://github.com/Snailclimb/guide-rpc-framework
老套路,学习某一门技术或者框架的时候,第一步固然是要了解下面这几样东西。github
为了让你更好地了解 Netty 以及它诞生的缘由,先从传统的网络编程提及吧!编程
早期的 Java 网络相关的 API(java.net
包) 使用 Socket(套接字)进行网络通讯,不过只支持阻塞函数使用。后端
要经过互联网进行通讯,至少须要一对套接字:安全
Socket 网络通讯过程以下图所示:服务器
https://www.javatpoint.com/so...微信
Socket 网络通讯过程简单来讲分为下面 4 步:网络
对应到服务端和客户端的话,是下面这样的。
服务器端:
ServerSocket
对象而且绑定地址(ip)和端口号(port): server.bind(new InetSocketAddress(host, port))
accept()
方法监听客户端请求客户端:
Socket
对象而且链接指定的服务器的地址(ip)和端口号(port):socket.connect(inetSocketAddress)
为了便于理解,我写了一个简单的代码帮助各位小伙伴理解。
服务端:
public class HelloServer { private static final Logger logger = LoggerFactory.getLogger(HelloServer.class); public void start(int port) { //1.建立 ServerSocket 对象而且绑定一个端口 try (ServerSocket server = new ServerSocket(port);) { Socket socket; //2.经过 accept()方法监听客户端请求, 这个方法会一直阻塞到有一个链接创建 while ((socket = server.accept()) != null) { logger.info("client connected"); try (ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream()); ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream())) { //3.经过输入流读取客户端发送的请求信息 Message message = (Message) objectInputStream.readObject(); logger.info("server receive message:" + message.getContent()); message.setContent("new content"); //4.经过输出流向客户端发送响应信息 objectOutputStream.writeObject(message); objectOutputStream.flush(); } catch (IOException | ClassNotFoundException e) { logger.error("occur exception:", e); } } } catch (IOException e) { logger.error("occur IOException:", e); } } public static void main(String[] args) { HelloServer helloServer = new HelloServer(); helloServer.start(6666); } }
ServerSocket
的 accept()
方法是阻塞方法,也就是说 ServerSocket
在调用 accept()
等待客户端的链接请求时会阻塞,直到收到客户端发送的链接请求才会继续往下执行代码,所以咱们须要要为每一个 Socket 链接开启一个线程(能够经过线程池来作)。
上述服务端的代码只是为了演示,并无考虑多个客户端链接并发的状况。
客户端:
/** * @author shuang.kou * @createTime 2020年05月11日 16:56:00 */ public class HelloClient { private static final Logger logger = LoggerFactory.getLogger(HelloClient.class); public Object send(Message message, String host, int port) { //1. 建立Socket对象而且指定服务器的地址和端口号 try (Socket socket = new Socket(host, port)) { ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream()); //2.经过输出流向服务器端发送请求信息 objectOutputStream.writeObject(message); //3.经过输入流获取服务器响应的信息 ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream()); return objectInputStream.readObject(); } catch (IOException | ClassNotFoundException e) { logger.error("occur exception:", e); } return null; } public static void main(String[] args) { HelloClient helloClient = new HelloClient(); helloClient.send(new Message("content from client"), "127.0.0.1", 6666); System.out.println("client receive message:" + message.getContent()); } }
发送的消息实体类:
/** * @author shuang.kou * @createTime 2020年05月11日 17:02:00 */ @Data @AllArgsConstructor public class Message implements Serializable { private String content; }
首先运行服务端,而后再运行客户端,控制台输出以下:
服务端:
[main] INFO github.javaguide.socket.HelloServer - client connected [main] INFO github.javaguide.socket.HelloServer - server receive message:content from client
客户端:
client receive message:new content
很明显,我上面演示的代码片断有一个很严重的问题:只能同时处理一个客户端的链接,若是须要管理多个客户端的话,就须要为咱们请求的客户端单首创建一个线程。 以下图所示:
对应的 Java 代码多是下面这样的:
new Thread(() -> { // 建立 socket 链接 }).start();
可是,这样会致使一个很严重的问题:资源浪费。
咱们知道线程是很宝贵的资源,若是咱们为每一次链接都用一个线程处理的话,就会致使线程愈来愈好,最好达到了极限以后,就没法再建立线程处理请求了。处理的很差的话,甚至可能直接就宕机掉了。
不少人就会问了:那有没有改进的方法呢?
固然有! 比较简单而且实际的改进方法就是使用线程池。线程池还可让线程的建立和回收成本相对较低,而且咱们能够指定线程池的可建立线程的最大数量,这样就不会致使线程建立过多,机器资源被不合理消耗。
ThreadFactory threadFactory = Executors.defaultThreadFactory(); ExecutorService threadPool = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(100), threadFactory); threadPool.execute(() -> { // 建立 socket 链接 });
可是,即便你再怎么优化和改变。也改变不了它的底层仍然是同步阻塞的 BIO 模型的事实,所以没法从根本上解决问题。
为了解决上述的问题,Java 1.4 中引入了 NIO ,一种同步非阻塞的 I/O 模型。
Netty 实际上就基于 Java NIO 技术封装完善以后获得一个高性能框架,熟悉 NIO 的基本概念对于学习和更好地理解 Netty 仍是颇有必要的!
NIO 是一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入了 NIO 框架,对应 java.nio
包,提供了 Channel , Selector,Buffer 等抽象。
NIO 中的 N 能够理解为 Non-blocking,已经不在是 New 了(已经出来很长时间了)。
NIO 支持面向缓冲(Buffer)的,基于通道(Channel)的 I/O 操做方法。
NIO 提供了与传统 BIO 模型中的 Socket
和 ServerSocket
相对应的 SocketChannel
和 ServerSocketChannel
两种不一样的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式:
NIO 包含下面几个核心的组件:
这些组件之间的关系是怎么的呢?
Selector(选择器,也能够理解为多路复用器)是 NIO(非阻塞 IO)实现的关键。它使用了事件通知相关的 API 来实现选择已经就绪也就是可以进行 I/O 相关的操做的任务的能力。
简单来讲,整个过程是这样的:
select()
方法,这个方法会阻塞;相比于传统的 BIO 模型来讲, NIO 模型的最大改进是:
一个使用 NIO 编写的 Server 端以下,能够看出仍是总体仍是比较复杂的,而且代码读起来不是很直观,而且还可能因为 NIO 自己会存在 Bug。
不多使用 NIO,很大状况下也是由于使用 NIO 来建立正确而且安全的应用程序的开发成本和维护成本都比较大。因此,通常状况下咱们都会使用 Netty 这个比较成熟的高性能框架来作(Apace Mina 与之相似,可是 Netty 使用的更多一点)。
简单用 3 点归纳一下 Netty 吧!
用官方的总结就是:Netty 成功地找到了一种在不妥协可维护性和性能的状况下实现易于开发,性能,稳定性和灵活性的方法。
根据官网的描述,咱们能够总结出下面一些特色:
这个应该是老铁们最关心的一个问题了,凭借本身的了解,简单说一下,理论上 NIO 能够作的事情 ,使用 Netty 均可以作而且更好。Netty 主要用来作网络通讯 :
咱们日常常常接触的 Dubbo、RocketMQ、Elasticsearch、gRPC 等等都用到了 Netty。
能够说大量的开源项目都用到了 Netty,因此掌握 Netty 有助于你更好的使用这些开源项目而且让你有能力对其进行二次开发。
实际上还有不少不少优秀的项目用到了 Netty,Netty 官方也作了统计,统计结果在这里:https://netty.io/wiki/related... 。
RPC 框架源码已经开源了,地址:https://github.com/Snailclimb...