从 BIO、NIO 聊到 Netty,最后还要实现个 RPC 框架!
2021-01-07 20:29
标签:无法 数据 通道 能力 简单 ast adf 不同的 soc 大家好,我是 「后端技术进阶」 作者,一个热爱技术的少年。 觉得不错的话,欢迎 star!?( ′???` )比心 老套路,学习某一门技术或者框架的时候,第一步当然是要了解下面这几样东西。 为了让你更好地了解 Netty 以及它诞生的原因,先从传统的网络编程说起吧! 早期的 Java 网络相关的 API( 要通过互联网进行通信,至少需要一对套接字: Socket 网络通信过程如下图所示: https://www.javatpoint.com/socket-programming Socket 网络通信过程简单来说分为下面 4 步: 对应到服务端和客户端的话,是下面这样的。 服务器端: 客户端: 为了便于理解,我写了一个简单的代码帮助各位小伙伴理解。 服务端: 上述服务端的代码只是为了演示,并没有考虑多个客户端连接并发的情况。 客户端: 发送的消息实体类: 首先运行服务端,然后再运行客户端,控制台输出如下: 服务端: 客户端: 很明显,我上面演示的代码片段有一个很严重的问题:只能同时处理一个客户端的连接,如果需要管理多个客户端的话,就需要为我们请求的客户端单独创建一个线程。 如下图所示: 对应的 Java 代码可能是下面这样的: 但是,这样会导致一个很严重的问题:资源浪费。 我们知道线程是很宝贵的资源,如果我们为每一次连接都用一个线程处理的话,就会导致线程越来越好,最好达到了极限之后,就无法再创建线程处理请求了。处理的不好的话,甚至可能直接就宕机掉了。 很多人就会问了:那有没有改进的方法呢? 当然有! 比较简单并且实际的改进方法就是使用线程池。线程池还可以让线程的创建和回收成本相对较低,并且我们可以指定线程池的可创建线程的最大数量,这样就不会导致线程创建过多,机器资源被不合理消耗。 但是,即使你再怎么优化和改变。也改变不了它的底层仍然是同步阻塞的 BIO 模型的事实,因此无法从根本上解决问题。 为了解决上述的问题,Java 1.4 中引入了 NIO ,一种同步非阻塞的 I/O 模型。 Netty 实际上就基于 Java NIO 技术封装完善之后得到一个高性能框架,熟悉 NIO 的基本概念对于学习和更好地理解 Netty 还是很有必要的! NIO 是一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入了 NIO 框架,对应 NIO 中的 N 可以理解为 Non-blocking,已经不在是 New 了(已经出来很长时间了)。 NIO 支持面向缓冲(Buffer)的,基于通道(Channel)的 I/O 操作方法。 NIO 提供了与传统 BIO 模型中的 NIO 包含下面几个核心的组件: 这些组件之间的关系是怎么的呢? Selector(选择器,也可以理解为多路复用器)是 NIO(非阻塞 IO)实现的关键。它使用了事件通知相关的 API 来实现选择已经就绪也就是能够进行 I/O 相关的操作的任务的能力。 简单来说,整个过程是这样的: 相比于传统的 BIO 模型来说, NIO 模型的最大改进是: 一个使用 NIO 编写的 Server 端如下,可以看出还是整体还是比较复杂的,并且代码读起来不是很直观,并且还可能由于 NIO 本身会存在 Bug。 很少使用 NIO,很大情况下也是因为使用 NIO 来创建正确并且安全的应用程序的开发成本和维护成本都比较大。所以,一般情况下我们都会使用 Netty 这个比较成熟的高性能框架来做(Apace Mina 与之类似,但是 Netty 使用的更多一点)。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VQnDTw9w-1598268167131)(https://gitee.com/SnailClimb/netty-practical-tutorial/raw/master/pictures/Nio-Server.png)] 简单用 3 点概括一下 Netty 吧! 用官方的总结就是:Netty 成功地找到了一种在不妥协可维护性和性能的情况下实现易于开发,性能,稳定性和灵活性的方法。 根据官网的描述,我们可以总结出下面一些特点: 这个应该是老铁们最关心的一个问题了,凭借自己的了解,简单说一下,理论上 NIO 可以做的事情 ,使用 Netty 都可以做并且更好。Netty 主要用来做网络通信 : 我们平常经常接触的 Dubbo、RocketMQ、Elasticsearch、gRPC 等等都用到了 Netty。 可以说大量的开源项目都用到了 Netty,所以掌握 Netty 有助于你更好的使用这些开源项目并且让你有能力对其进行二次开发。 实际上还有很多很多优秀的项目用到了 Netty,Netty 官方也做了统计,统计结果在这里:https://netty.io/wiki/related-projects.html 。 RPC 框架源码已经开源了,地址:https://github.com/Snailclimb/guide-rpc-framework 从 BIO、NIO 聊到 Netty,最后还要实现个 RPC 框架! 标签:无法 数据 通道 能力 简单 ast adf 不同的 soc 原文地址:https://www.cnblogs.com/javaguide/p/netty-01.html
还是要从 BIO 说起
传统的阻塞式通信流程
java.net
包) 使用 Socket(套接字)进行网络通信,不过只支持阻塞函数使用。
ServerSocket
对象并且绑定地址(ip)和端口号(port): server.bind(new InetSocketAddress(host, port))
accept()
方法监听客户端请求
Socket
对象并且连接指定的服务器的地址(ip)和端口号(port):socket.connect(inetSocketAddress)
一个简单的 demo
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
资源消耗严重的问题
new Thread(() -> {
// 创建 socket 连接
}).start();
线程池虽可以改善,但终究未从根本解决问题
ThreadFactory threadFactory = Executors.defaultThreadFactory();
ExecutorService threadPool = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new ArrayBlockingQueue(100), threadFactory);
threadPool.execute(() -> {
// 创建 socket 连接
});
再看 NIO
初识 NIO
java.nio
包,提供了 Channel , Selector,Buffer 等抽象。Socket
和 ServerSocket
相对应的 SocketChannel
和 ServerSocketChannel
两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式:
NIO 核心组件解读
select()
方法,这个方法会阻塞;NIO 为啥更好?
使用 NIO 编写代码太难了
重要角色 Netty 登场
Netty 特点
使用 Netty 能做什么?
哪些开源项目用到了 Netty?
后记
上一篇:JS日期格式化转换方法
文章标题:从 BIO、NIO 聊到 Netty,最后还要实现个 RPC 框架!
文章链接:http://soscw.com/index.php/essay/40782.html