网络协议-BIO实战和NIO编程

本文介绍了网络通信的基本常识,深入探讨了原生JDK的BIO和NIO编程,包括BIO的阻塞特性、RPC框架实现,以及NIO的Reactor模式和三大核心组件。同时,详细解析了Buffer的使用和零拷贝技术在Java生态及Kafka、Netty中的应用。
摘要由CSDN通过智能技术生成

网络通信编程基本常识

         在开发过程中,如果类的名字有 Server 或者 ServerSocket 的,表示这个类是给服务端容纳网络 服务用的,如果类的名字只包含 Socket 的,那么表示这是负责具体的网络读写的。
         ServerSocket 并不负责具体的网络读写,ServerSocket 就只是负责接收客户端连接
后,新启一个 socket 来和客户端进行沟通。这一点对所有模式的通信编程都是适用的。
         在通信编程里,我们关注的其实也就是三个事情: 连接 (客户端连接服务器,服务器等
待和接收连接)、 读网络数据 写网络数据 ,所有模式的通信编程都是围绕着这三件事情进
行的。 服务端提供 IP 和监听端口,客户端通过连接操作向服务端监听的地址发起连接请求,
通过三次握手连接,如果连接成功建立,双方就可以通过套接字进行通信。
        我们后面将学习的 BIO NIO 其实都是处理上面三件事,只是处理的方式不一样。
什么是 Socket?
         Socket 是应用层与 TCP/IP 协议族通信的中间软件抽象层,它是一组接口,一般由操作
系统提供。 在设计模式中, Socket 其实就是一个门面模式 ,它把 复杂的 TCP/IP 协议处理
通信缓存管理 等等都隐藏在 Socket 接口后面,对用户来说,使用一组简单的接口就能进行
网络应用编程,让 Socket 去组织数据,以符合指定的协议。主机 A 的应用程序要能和主机 B
应用程序通信,必须通过 Socket 建立连接。
        客户端连接上一个服务端,就会在客户端中产生一个 socket 接口实例,服务端每接受
一个客户端连接,就会产生一个 socket 接口实例和客户端的 socket 进行通信,有多个客户
端连接自然就有多个 socket 接口实例。

短连接
连接 -> 传输数据 -> 关闭连接
传统 HTTP 是无状态的,浏览器和服务器每进行一次 HTTP 操作,就建立一次连接,但任
务结束就中断连接。
也可以这样说: 短连接是指 SOCKET 连接后发送后接收完数据后马上断开连接。
长连接
连接 -> 传输数据 -> 保持连接 -> 传输数据 -> 。。。 -> 关闭连接。
长连接指建立 SOCKET 连接后不管是否使用都保持连接。
什么时候用长连接,短连接?
         长连接多用于操作频繁,点对点的通讯 。每个 TCP 连接都需要三步握手,这需要时
间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,所以每个操作完后都
不断开,下次处理时直接发送数据包就 OK 了,不用建立 TCP 连接。例如:数据库的连接用
长连接, 如果用短连接频繁的通信会造成 socket 错误,而且频繁的 socket 创建也是对资源
的浪费。
        而像 WEB 网站的 http 服务按照 Http 协议规范早期一般都用短链接 因为长连接对于
服务端来说会耗费一定的资源,而像 WEB 网站这么频繁的成千上万甚至上亿客户端的连接
用短连接会更省一些资源。 但是现在的 Http 协议, Http1.1 ,尤其是 Http2 Http3 已经开始
向长连接演化。
        总之,长连接和短连接的选择要视情况而定。

 原生JDK网络编程-BIO

        BIO,意为 Blocking I/O,即阻塞的 I/O。
        BIO 基本上就是我们上面所说的生活场景的朴素实现。 在 BIO 中类 ServerSocket 负责绑
定 IP 地址,启动监听端口,等待客户连接; 客户端 Socket 类的实例发起连接操作ServerSocket
接受连接后产生一个新的服务端 socket 实例负责和客户端 socket 实例通过输入和输出流进 行通信。

bio 的阻塞,主要体现在两个地方。

         ①若一个服务器启动就绪,那么主线程就一直在等待着客户端的连接,这个等待过程中
主线程就一直在阻塞。
        ②在连接建立之后,在读取到 socket 信息之前,线程也是一直在等待,一直处于阻塞
的状态下的。
        这一点可以通过 cn.tuling.bio 下的 ServerSingle.java 服务端程序看出,启动该程序后,启
动一个 Client 程序实例,并让这个 Client 阻塞住,位置就在向服务器输出具体请求之前,再
启动一个新的 Client 程序实例,会发现尽管新的 Client 实例连接上了服务器,但是 ServerSingle
服务端程序仿佛无感知一样?为何,因为执行的主线程被阻塞了一直在等待第一个 Client
实例发送消息过来。
        所以在 BIO 通信里,我们往往会在服务器的实现上结合线程来处理连接以及和客户端的
通信。

        传统 BIO 通信模型:

         采用 BIO 通信模型的服务端,通常 由一个独立的 Acceptor 线程负
责监听客户端的连接 ,它 接收到客户端连接请求之后为每个客户端创建一个新的线程进行链
路处理 处理完成后,通过输出流返回应答给客户端,线程销毁。即 典型的一请求一应答模
,同时数据的读取写入也必须阻塞在一个线程内等待其完成 代码可见 cn.tuling.bio.Server

         该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程

个数和客户端并发访问数呈 1:1 的正比关系,Java 中的线程也是比较宝贵的系统资源,线程
数量快速膨胀后,系统的性能将急剧下降,随着访问量的继续增大,系统最终就死-掉-了
         为了改进这种一连接一线程的模型,我们可以使用线程池来管理这些线程,实现 1 个或
多个线程处理 N 个客户端的模型(但是底层还是使用的同步阻塞 I/O), 通常被称为“伪异
步 I/O 模型“。
        我们知道,如果使用 CachedThreadPool 线程池 (不限制线程数量,如果不清楚请参考
文首提供的文章),其实除了能自动帮我们管理线程(复用),看起来也就像是 1:1 的客户
端:线程数模型,而使用 FixedThreadPool 我们就 有效的控制了线程的最大数量,保证了系
统有限的资源的控制,实现了 N:M 的伪异步 I/O 模型 代码可见 cn.tuling.bio.ServerPool

         但是,正因为限制了线程数量,如果发生读取数据较慢时(比如数据量大、网络传

输慢等),大量并发的情况下,其他接入的消息,只能一直等待,这就是最大的弊端。

BIO 实战-手写 RPC 框架

为什么要有 RPC
        我们最开始开发的时候,一个应用一台机器,将所有功能都写在一起,比如说比较常见
的电商场景,服务之间的调用就是我们最熟悉的普通本地方法调用。
        随着我们业务的发展,我们需要提示性能了,我们会怎么做?将不同的业务功能放到线
程里来实现异步和提升性能,但本质上还是本地方法调用。
        但是业务越来越复杂,业务量越来越大,单个应用或者一台机器的资源是肯定背负不起
的,这个时候,我们会怎么做?将核心业务抽取出来,作为独立的服务,放到其他服务器上
或者形成集群。这个时候就会请出 RPC ,系统变为分布式的架构。
        为什么说千万级流量分布式、微服务架构必备的 RPC 框架?和 LocalCall 的代码进行比
较, 因为引入 rpc 框架对我们现有的代码影响最小,同时又可以帮我们实现架构上的扩展。
现在的开源 rpc 框架,有什么? dubbo grpc 等等
        当服务越来越多,各种 rpc 之间的调用会越来越复杂,这个时候我们会引入中间件,比
如说 MQ 、缓存,同时架构上整体往微服务去迁移,引入了各种比如容器技术 docker DevOps
等等。最终会变为如图所示来应付千万级流量,但是不管怎样, rpc 总是会占有一席之地。

 

什么是 RPC
RPC Remote Procedure Call ——远程过程调用),它是一种通过网络从远程计算机程
序上请求服务,而不需要了解底层网络的技术。
一次完整的 RPC 同步调用流程:
        1)服务消费方( client )以本地调用方式调用客户端存根;
        2)什么叫客户端存根? 就是远程方法在本地的模拟对象,一样的也有方法名,也有方
法参数,client stub 接收到调用后负责将方法名、方法的参数等包装,并将包装后的信息通
过网络发送到服务端;
        3)服务端收到消息后,交给代理存根在服务器的部分后进行解码为实际的方法名和参
        4) server stub 根据解码结果调用服务器上本地的实际服务;
        5)本地服务执行并将结果返回给 server stub
        6) server stub 将返回结果打包成消息并发送至消费方;
        7) client stub 接收到消息,并进行解码;
        8)服务消费方得到最终结果。
        RPC 框架的目标就是要中间步骤都封装起来,让我们进行远程方法调用的时候感觉到就
像在本地方法调用一样。
RPC HTTP
        rpc 字面意思就是远程过程调用,只是对不同应用间相互调用的一种描述,一种思想。
具体怎么调用?实现方式可以是最直接的 tcp 通信,也可以是 http 方式,在很多的消息中间
件的技术书籍里,甚至还有使用消息中间件来实现 RPC 调用的,我们知道的 dubbo 是基于
tcp 通信的, gRPC Google 公布的开源软件,基于最新的 HTTP2.0 协议,底层使用到了 Netty
框架的支持。所以总结来说, rpc http 是完全两个不同层级的东西,他们之间并没有什么
可比性。
实现 RPC 框架
实现 RPC 框架需要解决的那些问题
         代理问题
        代理本质上是要解决什么问题?要解决的是被调用的服务本质上是远程的服务,但是调
用者不知道也不关心,调用者只要结果,具体的事情由代理的那个对象来负责这件事。既然
是远程代理,当然是要用代理模式了。
        代理(Proxy) 是一种设计模式 , 即通过代理对象访问目标对象 . 这样做的好处是 : 可以在目
标对象实现的基础上 , 增强额外的功能操作 , 即扩展目标对象的功能。那我们这里额外的功能
操作是干什么,通过网络访问远程服务。
        jdk 的代理有两种实现方式: 静态代理 动态代理。
        序列化问题
        序列化问题在计算机里具体是什么?我们的方法调用,有方法名,方法参数,这些可能
是字符串,可能是我们自己定义的 java 的类,但是在网络上传输或者保存在硬盘的时候,
网络或者硬盘并不认得什么字符串或者 javabean ,它只认得二进制的 01 串,怎么办?要进
行序列化,网络传输后要进行实际调用,就要把二进制的 01 串变回我们实际的 java 的类,
这个叫反序列化。 java 里已经为我们提供了相关的机制
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长情知热爱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值