Netty
[TOC]
# Netty
# 319.为什么要 Thrift-Java面试题
- 多语言开发的需要
- 性能问题
# 318.Thrift-Java面试题
Apache Thrift 是 Facebook 实现的一种高效的、支持多种编程语言的远程服务调用的框架。本文将从Java 开发人员角度详细介绍 Apache Thrift 的架构、开发和部署,并且针对不同的传输协议和服务类型给出相应的 Java 实例,同时详细介绍 Thrift 异步客户端的实现,最后提出使用 Thrift 需要注意的事项。
目前流行的服务调用方式有很多种,例如基于 SOAP 消息格式的 Web Service,基于 JSON 消息格式的 RESTful 服务等。其中所用到的数据传输方式包括 XML,JSON 等,然而 XML 相对体积太大,传输效率低,JSON 体积较小,新颖,但还不够完善。本文将介绍由 Facebook 开发的远程服务调用框架Apache Thrift,它采用接口描述语言定义并创建服务,支持可扩展的跨语言服务开发,所包含的代码生成引擎可以在多种语言中,如 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk 等创建高效的、无缝的服务,其传输数据采用二进制格式,相对 XML 和 JSON 体积更小,对于高并发、大数据量和多语言的环境更有优势。
本文将详细介绍 Thrift 的使用,并且提供丰富的实例代码加以解释说明,帮助使用者快速构建服务。
# 317.特点-Java面试题
Protocol Buffer 的序列化 & 反序列化简单 & 速度快的原因是:
- 编码 / 解码 方式简单(只需要简单的数学运算 = 位移等等)
- 采用 Protocol Buffer 自身的框架代码 和 编译器 共同完成
Protocol Buffer 的数据压缩效果好(即序列化后的数据量体积小)的原因是:
- a. 采用了独特的编码方式,如 Varint、Zigzag 编码方式等等
- b. 采用 T – L – V 的数据存储方式:减少了分隔符的使用 & 数据存储得紧凑
# 316.Protoclol Buffer-Java面试题
protocol buffer 是 google 的一个开源项目,它是用于结构化数据串行化的灵活、高效、自动的方法,例如 XML,不过它比 xml 更小、更快、也更简单。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构。
# 315.实现步骤-Java面试题
- 编写远程服务接口,该接口必须继承 java.rmi.Remote 接口,方法必须抛出java.rmi.RemoteException 异常;
- 编写远程接口实现类,该实现类必须继承 java.rmi.server.UnicastRemoteObject 类;
- 运行 RMI 编译器(rmic),创建客户端 stub 类和服务端 skeleton 类;
- 启动一个 RMI 注册表,以便驻留这些服务;
- 在 RMI 注册表中注册服务;
- 客户端查找远程对象,并调用远程方法;
1:创建远程接口,继承 java.rmi.Remote 接口
public interface GreetService extends java.rmi.Remote {
String sayHello(String name) throws RemoteException;
}
2:实现远程接口,继承 java.rmi.server.UnicastRemoteObject 类
public class GreetServiceImpl extends java.rmi.server.UnicastRemoteObject
implements GreetService {
private static final long serialVersionUID = 3434060152387200042L;
public GreetServiceImpl() throws RemoteException {
super();
}
@Override
public String sayHello(String name) throws RemoteException {
return "Hello " + name;
}
}
3:生成 Stub 和 Skeleton;
4:执行 rmiregistry 命令注册服务
5:启动服务
LocateRegistry.createRegistry(1098);
Naming.bind("rmi://10.108.1.138:1098/GreetService", new GreetServiceImpl());
6.客户端调用
GreetService greetService = (GreetService)
Naming.lookup("rmi://10.108.1.138:1098/GreetService");
System.out.println(greetService.sayHello("Jobs"));~~~
# 314.RMI 实现方式-Java面试题
Java 远程方法调用,即 Java RMI(Java Remote Method Invocation)是 Java 编程语言里,一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使 Java 编程人员能够在网络环境中分布操作。RMI 全部的宗旨就是尽可能简化远程接口对象的使用。
# 313.监听消息的线程收到消息,找到 callback 上的锁并唤醒-Java面试题
- 服务端接收到请求并处理后,将 response 结果(此结果中包含了前面的 requestID)发送给客户端,客户端 socket 连接上专门监听消息的线程收到消息,分析结果,取到requestID , 再 从 前 面 的 ConcurrentHashMap 里 面 get(requestID) , 从 而 找 到callback 对象,再用 synchronized 获取 callback 上的锁,将方法调用结果设置到callback 对象里,再调用 callback.notifyAll()唤醒前面处于等待状态的线程。
# 312.synchronized 获取回调对象 callback 的锁并自旋 wait-Java面试题
- 当线程调用 channel.writeAndFlush()发送消息后,紧接着执行 callback 的 get()方法试图获取远程返回的结果。在 get()内部,则使用 synchronized 获取回调对象 callback 的锁,再先检测是否已经获取到结果,如果没有,然后调用 callback 的 wait()方法,释放callback 上的锁,让当前线程处于等待状态。
# 311.存放回调对象 callback 到全局 ConcurrentHashMap-Java面试题
- 将 处 理 结 果 的 回 调 对 象 callback , 存 放 到 全 局 ConcurrentHashMap 里 面put(requestID, callback);
# 310.通讯流程-Java面试题
requestID 生成-AtomicLong
- client 线程每次通过 socket 调用一次远程接口前,生成一个唯一的 ID,即 requestID(requestID 必需保证在一个 Socket 连接里面是唯一的),一般常常使用 AtomicLong从 0 开始累计数字生成唯一 ID;
# 309.通讯过程-Java面试题
核心问题(线程暂停、消息乱序)
如果使用 netty 的话,一般会用 channel.writeAndFlush()方法来发送消息二进制串,这个方法调用后对于整个远程调用(从发出请求到接收到结果)来说是一个异步的,即对于当前线程来说,将请求发送出来后,线程就可以往后执行了,至于服务端的结果,是服务端处理完成后,再以消息的形式发送给客户端的。于是这里出现以下两个问题:
- 怎么让当前线程“暂停”,等结果回来后,再向后执行?
- 如果有多个线程同时进行远程方法调用,这时建立在 client server 之间的 socket 连接上会有很多双方发送的消息传递,前后顺序也可能是随机的,server 处理完结果后,将结果消息发送给 client,client 收到很多消息,怎么知道哪个消息结果是原先哪个线程调用的?如下图所示,线程 A 和线程 B 同时向 client socket 发送请求 requestA 和 requestB,socket 先后将 requestB 和 requestA 发送至 server,而 server 可能将 responseB 先返回,尽管 requestB 请求到达时间更晚。我们需要一种机制保证 responseA 丢给ThreadA,responseB 丢给 ThreadB。
# 308.序列化-Java面试题
目前互联网公司广泛使用 Protobuf、Thrift、Avro 等成熟的序列化解决方案来搭建 RPC 框架,这些都是久经考验的解决方案。
# 307.消息编解码-Java面试题
息数据结构(接口名称+方法名+参数类型和参数值+超时时间+ requestID)
客户端的请求消息结构一般需要包括以下内容:
- 接口名称:在我们的例子里接口名是“HelloWorldService”,如果不传,服务端就不知道调用哪个接口了;
- 方法名:一个接口内可能有很多方法,如果不传方法名服务端也就不知道调用哪个方法;
- 参数类型和参数值:参数类型有很多,比如有 bool、int、long、double、string、map、list,甚至如 struct(class);以及相应的参数值;
- 超时时间:
- requestID,标识唯一请求 id,在下面一节会详细描述 requestID 的用处。
- 服务端返回的消息 : 一般包括以下内容。返回值+状态 code+requestID
# 306.核心流程-Java面试题
- 服务消费方(client)调用以本地调用方式调用服务;
- client stub 接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
- client stub 找到服务地址,并将消息发送到服务端;
- server stub 收到消息后进行解码;
- server stub 根据解码结果调用本地的服务;
- 本地服务执行并将结果返回给 server stub;
- server stub 将返回结果打包成消息并发送至消费方;
- client stub 接收到消息,并进行解码;
- 服务消费方得到最终结果。
RPC 的目标就是要 2~8 这些步骤都封装起来,让用户对这些细节透明。JAVA 一般使用动态代理方式实现远程调用。
# 305.关键技术-Java面试题
- 服务发布与订阅:服务端使用 Zookeeper 注册服务地址,客户端从 Zookeeper 获取可用的服务地址。
- 通信:使用 Netty 作为通信框架。
- Spring:使用 Spring 配置服务,加载 Bean,扫描注解。
- 动态代理:客户端使用代理模式透明化服务调用。
- 消息编解码:使用 Protostuff 序列化和反序列化消息。
# 304.Netty RPC 实现-Java面试题
RPC,即 Remote Procedure Call(远程过程调用),调用远程计算机上的服务,就像调用本地服务一样。RPC 可以很好的解耦系统,如 WebService 就是一种基于 Http 协议的 RPC。这个 RPC 整体框架如下:
# 303.高性能的序列化框架-Java面试题
Netty 默认提供了对 Google Protobuf 的支持,通过扩展 Netty 的编解码接口,用户可以实现其它的高性能序列化框架,例如 Thrift 的压缩二进制编解码框架。
- SO_RCVBUF 和 SO_SNDBUF:通常建议值为 128K 或者 256K。
小包封大包,防止网络阻塞
- SO_TCPNODELAY:NAGLE 算法通过将缓冲区内的小封包自动相连,组成较大的封包,阻止大量小封包的发送阻塞网络,从而提高网络应用效率。但是对于时延敏感的应用场景需要关闭该优化算法。
软中断 Hash 值和 CPU 绑定
- 软中断:开启 RPS 后可以实现软中断,提升网络吞吐量。RPS 根据数据包的源地址,目的地址以及目的和源端口,计算出一个 hash 值,然后根据这个 hash 值来选择软中断运行的 cpu,从上层来看,也就是说将每个连接和 cpu 绑定,并通过这个 hash 值,来均衡软中断在多个 cpu 上,提升网络并行处理性能。
# 302.无锁设计、线程绑定-Java面试题
Netty 采用了串行无锁化设计,在 IO 线程内部进行串行操作,避免多线程竞争导致的性能下降。表面上看,串行化设计似乎 CPU 利用率不高,并发程度不够。但是,通过调整 NIO 线程池的线程参数,可以同时启动多个串行化的线程并行运行,这种局部无锁化的串行线程设计相比一个队列-多个工作线程模型性能更优。
Netty 的 NioEventLoop 读取到消息之后,直接调用ChannelPipeline 的fireChannelRead(Object msg),只要用户不主动切换线程,一直会由 NioEventLoop 调用到用户的 Handler,期间不进行线程切换,这种串行化处理方式避免了多线程操作导致的锁的竞争,从性能角度看是最优的。
# 300.Reactor 多线程模型-Java面试题
Rector 多线程模型与单线程模型最大的区别就是有一组 NIO 线程处理 IO 操作。 有专门一个NIO 线程-Acceptor 线程用于监听服务端,接收客户端的 TCP 连接请求; 网络 IO 操作-读、写等由一个 NIO 线程池负责,线程池可以采用标准的 JDK 线程池实现,它包含一个任务队列和 N个可用的线程,由这些 NIO 线程负责消息的读取、解码、编码和发送;
# 299.Reactor 单线程模-Java面试题
Reactor 单线程模型,指的是所有的 IO 操作都在同一个 NIO 线程上面完成,NIO 线程的职责如下:
- 作为 NIO 服务端,接收客户端的 TCP 连接;
- 作为 NIO 客户端,向服务端发起 TCP 连接;
- 读取通信对端的请求或者应答消息;
- 向通信对端发送消息请求或者应答消息。
由于 Reactor 模式使用的是异步非阻塞 IO,所有的 IO 操作都不会导致阻塞,理论上一个线程可以独立处理所有 IO 相关的操作。从架构层面看,一个 NIO 线程确实可以完成其承担的职责。例如,通过Acceptor 接收客户端的 TCP 连接请求消息,链路建立成功之后,通过 Dispatch 将对应的 ByteBuffer派发到指定的 Handler 上进行消息解码。用户 Handler 可以通过 NIO 线程将消息发送给客户端。
# 298.高效的 Reactor 线程模型-Java面试题
常用的 Reactor 线程模型有三种,Reactor 单线程模型, Reactor 多线程模型, 主从 Reactor 多线程模型。
# 297.内存池(基于内存池的缓冲区重用机制)-Java面试题
随着 JVM 虚拟机和 JIT 即时编译技术的发展,对象的分配和回收是个非常轻量级的工作。但是对于缓冲区 Buffer,情况却稍有不同,特别是对于堆外直接内存的分配和回收,是一件耗时的操作。为了尽量重用缓冲区,Netty 提供了基于内存池的缓冲区重用机制。
# 296.零拷贝(DIRECT BUFFERS 使用堆外直接内存)-Java面试题
- Netty 的接收和发送 ByteBuffer 采用 DIRECT BUFFERS,使用堆外直接内存进行 Socket 读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行 Socket 读写,JVM 会将堆内存 Buffer 拷贝一份到直接内存中,然后才写入 Socket 中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。
- Netty 提供了组合 Buffer 对象,可以聚合多个 ByteBuffer 对象,用户可以像操作一个 Buffer 那样方便的对组合 Buffer 进行操作,避免了传统通过内存拷贝的方式将几个小 Buffer 合并成一个大的Buffer。
- Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环 write 方式导致的内存拷贝问题
# 295.异步通讯 NIO-Java面试题
由于 Netty 采用了异步通信模式,一个 IO 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 IO 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极
大的提升。
# 294.多路复用通讯方式-Java面试题
Netty 架构按照 Reactor 模式设计和实现,它的服务端通信序列图如下:
客户端通信序列图如下:
Netty 的 IO 线程 NioEventLoop 由于聚合了多路复用器Selector,可以同时并发处理成百上千个客户端 Channel,由于读写操作都是非阻塞的,这就可以充分提升 IO 线程的运行效率,避免由于频繁 IO 阻塞导致的线程挂起。
# 293.Netty 高性能-Java面试题
在 IO 编程过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者 IO 多路复用技术进行处理。IO 多路复用技术通过把多个 IO 的阻塞复用到同一个 select 的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程/多进程模型比,I/O 多路复用的最大优势是系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程的运行,降低了系统的维护工作量,节省了系统资源。
与 Socket 类和 ServerSocket 类相对应,NIO 也提供了SocketChannel 和 ServerSocketChannel两种不同的套接字通道实现。
# Netty高性能体现在哪些方面?-Java面试题
1)传输:IO模型在很大程度上决定了框架的性能,相比于bio,netty建议采用异步通信模 式,因为nio一个线程可以并发处理N个客户端连接和读写操作,这从根本上解决了传统同步阻 塞IO一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。正如代码 中所示,使用的是NioEventLoopGroup和NioSocketChannel来提升传输效率。
2)协议:采用什么样的通信协议,对系统的性能极其重要,netty默认提供了对Google Protobuf的支持,也可以通过扩展Netty的编解码接口,用户可以实现其它的高性能序列化框 架。
3)线程:netty使用了Reactor线程模型,但Reactor模型不同,对性能的影响也非常大,下 面介绍常用的Reactor线程模型有三种,分别如下:
Reactor单线程模型:单线程模型的线程即作为NIO服务端接收客户端的TCP连接,又作为 NIO客户端向服务端发起TCP连接,即读取通信对端的请求或者应答消息,又向通信对端发送 消息请求或者应答消息。理论上一个线程可以独立处理所有IO相关的操作,但一个NIO线程同时处理成百上千的链路,性能上无法支撑,即便NIO线程的CPU负荷达到100%,也无法满足 海量消息的编码、解码、读取和发送,又因为当NIO线程负载过重之后,处理速度将变慢,这 会导致大量客户端连接超时,超时之后往往会进行重发,这更加重了NIO线程的负载,最终会 导致大量消息积压和处理超时,NIO线程会成为系统的性能瓶颈。
Reactor多线程模型:有专门一个NIO线程用于监听服务端,接收客户端的TCP连接请求;网 络IO操作(读写)由一个NIO线程池负责,线程池可以采用标准的JDK线程池实现。但百万客户 端并发连接时,一个nio线程用来监听和接受明显不够,因此有了主从多线程模型。 主从Reactor多线程模型:利用主从NIO线程模型,可以解决1个服务端监听线程无法有效处理 所有客户端连接的性能不足问题,即把监听服务端,接收客户端的TCP连接请求分给一个线程 池。因此,在代码中可以看到,我们在server端选择的就是这种方式,并且也推荐使用该线程 模型。在启动类中创建不同的EventLoopGroup实例并通过适当的参数配置,就可以支持上述 三种Reactor线程模型。
# 说说Netty的执行流程?-Java面试题
创建ServerBootStrap实例
设置并绑定Reactor线程池:EventLoopGroup,EventLoop就是处理所有注册到本线程的 Selector上面的Channel
设置并绑定服务端的channel
创建处理网络事件的ChannelPipeline和handler,网络时间以流的形式在其中流转, handler完成多数的功能定制:比如编解码 SSl安全认证
绑定并启动监听端口
当轮训到准备就绪的channel后,由Reactor线程:NioEventLoop执行pipline中的方法,最 终调度并执行channelHandler
# Netty 支持哪些心跳类型设置?-Java面试题
readerIdleTime:为读超时时间(即测试端一定时间内未接受到被测试端消息)。writerIdleTime:为写超时时间(即测试端一定时间内向被测试端发送消息)。allIdleTime:所有类型的超时时间。
# Netty 发送消息有几种方式?-Java面试题
Netty 有两种发送消息的方式:
直接写入 Channel 中,消息从 ChannelPipeline 当中尾部开始移动; 写入和 ChannelHandler 绑定的 ChannelHandlerContext 中,消息从 ChannelPipeline 中 的下一个 ChannelHandler 中移动。
# Netty 中有哪些重要组件?-Java面试题
Channel:Netty 网络操作抽象类,它除了包括基本的 I/O 操作,如 bind、connect、 read、write 等。
EventLoop:主要是配合 Channel 处理 I/O 操作,用来处理连接的生命周期中所发生的事 情。
ChannelFuture:Netty 框架中所有的 I/O 操作都为异步的,因此我们需要 ChannelFuture 的 addListener()注册一个 ChannelFutureListener 监听事件,当操作执行成功或者失败 时,监听就会自动触发返回结果。
ChannelHandler:充当了所有处理入站和出站数据的逻辑容器。ChannelHandler 主要用来 处理各种事件,这里的事件很广泛,比如可以是连接、数据接收、异常、数据转换等。
ChannelPipeline:为 ChannelHandler 链提供了容器,当 channel 创建时,就会被自动分 配到它专属的 ChannelPipeline,这个关联是永久性的。
# Netty 的心跳机制了解么?-Java面试题
在 TCP 保持长连接的过程中,可能会出现断网等网络异常出现,异常发生的时候, client 与 server 之间如果没有交互的话,它们是无法发现对方已经掉线的。为了解决这个问题, 我们就 需要引入 心跳机制。
心跳机制的工作原理是: 在 client 与 server 之间在一定时间内没有数据交互时, 即处于 idle 状 态时, 客户端或服务器就会发送一个特殊的数据包给对方, 当接收方收到这个数据报文后, 也立 即发送一个特殊的数据报文, 回应发送方, 此即一个 PING-PONG 交互。所以, 当某一端收到心 跳消息后, 就知道了对方仍然在线, 这就确保 TCP 连接的有效性.
TCP 实际上自带的就有长连接选项,本身是也有心跳包机制,也就是 TCP 的选项: SO_KEEPALIVE。但是,TCP 协议层面的长连接灵活性不够。所以,一般情况下我们都是在 应用层协议上实现自定义心跳机制的,也就是在 Netty 层面通过编码实现。通过 Netty 实现心 跳机制的话,核心类是 IdleStateHandler 。
# Netty 的零拷贝了解么?-Java面试题
维基百科是这样介绍零拷贝的:“零复制(英语:Zero-copy;也译零拷贝)技术是指计算机 执行操作时,CPU 不需要先将数据从某处内存复制到另一个特定区域。这种技术通常用于通过 网络传输文件时节省 CPU 周期和内存带宽。
在 OS 层面上的 Zero-copy 通常指避免在 用户态(User-space) 与 内核态(Kernel-space) 之 间来回拷贝数据。而在 Netty 层面 ,零拷贝主要体现在对于数据操作的优化。
Netty 中的零拷贝体现在以下几个方面: 使用 Netty 提供的 CompositeByteBuf 类, 可以将多个ByteBuf 合并为一个逻辑上的 ByteBuf, 避免了各个 ByteBuf 之间的拷贝。
ByteBuf 支持 slice 操作, 因此可以将 ByteBuf 分解为多个共享同一个存储区域的 ByteBuf, 避免了内存的拷贝。
通过 FileRegion 包装的FileChannel.tranferTo 实现文件传输, 可以直接将文件缓冲区的数据 发送到目标 Channel, 避免了传统通过循环 write 方式导致的内存拷贝问题。
# Netty 的应用场景了解么?-Java面试题
Netty 主要用来做网络通信 :
作为 RPC 框架的网络通信工具 :我们在分布式系统中,不同服务节点之间经常需要相互调 用,这个时候就需要 RPC 框架了。不同服务节点之间的通信是如何做的呢?可以使用 Netty 来做。比如我调用另外一个节点的方法的话,至少是要让对方知道我调用的是哪个类中的哪个方法以及相关参数吧!
实现一个自己的 HTTP 服务器 :通过 Netty 我们可以自己实现一个简单的 HTTP 服务器,这 个大家应该不陌生。说到 HTTP 服务器的话,作为 Java 后端开发,我们一般使用 Tomcat 比 较多。一个最基本的 HTTP 服务器可要以处理常见的 HTTP Method 的请求,比如 POST 请 求、GET 请求等等。
实现一个即时通讯系统 :使用 Netty 我们可以实现一个可以聊天类似微信的即时通讯系统,这 方面的开源项目还蛮多的,可以自行去 Github 找一找。 实现消息推送系统 :市面上有很多消息推送系统都是基于 Netty 来做的。
# 为什么要用 Netty?-Java面试题
统一的 API,支持多种传输类型,阻塞和非阻塞的。
简单而强大的线程模型。
自带编解码器解决 TCP 粘包/拆包问题。
自带各种协议栈。
真正的无连接数据包套接字支持。
比直接使用 Java 核心 API 有更高的吞吐量、更低的延迟、更低的资源消耗和更少的内存复 制。
安全性不错,有完整的 SSL/TLS 以及 StartTLS 支持。
社区活跃 成熟稳定,经历了大型项目的使用和考验,而且很多开源项目都使用到了 Netty, 比如我们经 常接触的 Dubbo、RocketMQ 等等。
# Netty 是什么?-Java面试题
Netty 是一个 基于 NIO 的 client-server(客户端服务器)框架,使用它可以快速简单地开发网 络应用程序。
它极大地简化并优化了 TCP 和 UDP 套接字服务器等网络编程,并且性能以及安全性等很多方面 甚至都要更好。
支持多种协议 如 FTP,SMTP,HTTP 以及各种二进制和基于文本的传统协议。 用官方的总结就是:Netty 成功地找到了一种在不妥协可维护性和性能的情况下实现易于开 发,性能,稳定性和灵活性的方法。
# UDP协议会有粘包拆包的问题吗?为什么?-Java面试题
UDP不会有这个问题。
因为TCP是“数据流”协议,UDP是“数据报”协议。
UDP协议的数据包之间是没有联系,而且有明确边界的。
# 了解过粘包拆包吗?为什么会出现粘包拆包?怎么处理粘包拆包?-Java面试题
粘包的主要原因:发送方写入数据套接字缓冲区大小;发送方发送的数据大于协议的 MTU(最大传输单元),不得已必须拆包。
如何处理:1、消息长度固定;2、消息之间用分隔符分隔;3、在消息头保留一个字段,用于 描述消息的长度。
# 什么是Reactor模型?Reactor的3种版本都知道吗?-Java面试题
Reactor模式究竟是个什么东西呢?这要从事件驱动的开发方式说起。我们知道,对于应用服 务器,一个主要规律就是,CPU的处理速度是要远远快于IO速度的,如果CPU为了IO操作(例 如从Socket读取一段数据)而阻塞显然是不划算的。好一点的方法是分为多进程或者线程去进 行处理,但是这样会带来一些进程切换的开销,试想一个进程一个数据读了500ms,期间进程 切换到它3次,但是CPU却什么都不能干,就这么切换走了,是不是也不划算?
这时先驱们找到了事件驱动,或者叫回调的方式,来完成这件事情。这种方式就是,应用业务 向一个中间人注册一个回调(event handler),当IO就绪后,就这个中间人产生一个事件, 并通知此handler进行处理。这种回调的方式,也体现了“好莱坞原则”(Hollywood principle)-“Don’t call us, we’ll call you”,在我们熟悉的IoC中也有用到。看来软件开发 真是互通的!
好了,我们现在来看Reactor模式。在前面事件驱动的例子里有个问题:我们如何知道IO就绪 这个事件,谁来充当这个中间人?Reactor模式的答案是:由一个不断等待和循环的单独进程 (线程)来做这件事,它接受所有handler的注册,并负责先操作系统查询IO是否就绪,在就 绪后就调用指定handler进行处理,这个角色的名字就叫做Reactor。
Reactor的3种版本:单线程模式、多线程模式、主从多线程模式