Netty 是由 JBOSS 提供的基于NIO一个 Java 开源网络编程框架。Netty 提供异步的、基于事件驱动的网络应用程序框架,用以快速开发高性能、高可靠性的网络 IO 程序。作为当前最流行的 NIO 框架,Netty应用很广泛, hadoop、dubbo等底层RPC都是基于Netty实现的。以下是实现的章节:
Netty 是基于NIO的网络编程框架,那么了解Netty之前,先来熟悉下BIO和NIO。
- 阻塞式IO-BIO
BIO 是block(阻塞) IO的简称,由于主要涉及网络编程部分,所有主要介绍下BIO中的网络IO,也就是Socket编程。在JDK1.4之前的版本,建立网络连接只能采用BIO,其实目前也有很多系统依然采用这种的方式建立网络关系,目前公司的系统 也是采用这种方式。通过BIO建立网络连接,需要现在服务端启用一个ServerSocket来接收接收处理返回信息,在客户启用一个Socket来对服务端进行通信。在默认的情况下,客户端发送请求会先咨询服务端是否有线程响应,如果没有则会一直等待或者直接遭到拒绝,这就是阻塞IO。
下面实现一个简单Socket通信:
/**
* 服务器端程序
* @author Administrator
*/
public class Server {
public static void main(String[] args) throws Exception {
//1.创建 ServerSocket 对象
ServerSocket ss=new ServerSocket(8888);
while (true) {
//2.监听客户端
Socket s = ss.accept(); //阻塞
//3.从连接中取出输入流来接收消息
InputStream is = s.getInputStream(); //阻塞
byte[] b = new byte[10];
is.read(b);
String clientIP = s.getInetAddress().getHostAddress();
System.out.println(clientIP + "说:" + new String(b).trim());
//4.从连接中取出输出流并回话
OutputStream os = s.getOutputStream();
os.write("太高了".getBytes());
os.close();
}
}
}
/**
* 客户端程序
* @author Administrator
*/
public class Client {
public static void main(String[] args) throws Exception {
while (true) {
//1.创建 Socket 对象
Socket s = new Socket("127.0.0.1", 8888);
//2.从连接中取出输出流并发消息
OutputStream os = s.getOutputStream();
System.out.println("请输入房租:");
Scanner sc = new Scanner(System.in);
String msg = sc.nextLine();
os.write(msg.getBytes());
//3.从连接中取出输入流并接收回话
InputStream is = s.getInputStream(); //阻塞
byte[] b = new byte[20];
is.read(b);
System.out.println("小明说:" + new String(b).trim());
//4.关闭资源
s.close();
}
}
}
在上面的例子运行过程中可以看出,如果服务端没有返回数据,那么客户端会一直等待,程序也就会阻塞在这里,直到返回或者失败。
- 那什么又是不阻塞IO呢?
从JDK1.4开始,Java提供了一种的新的网络编程模式也就是NIO,全称是 java non-blocking IO,也就是非阻塞式IO,由于改进了一系列的输入/输出的新特性所以也称之为NEW IO。由于NIO的网络通道是非阻塞的NIO,基于事件驱动,非常适用于服务器需要为维持大量连接,但是数据量不大的情况,比如及时通讯等场景。NIO和BIO有着相同的目的和作用,但是实现方式却大不相同:
1、数据的处理方式:BIO是以流的形式处理数据,NIO是以块的形式的处理数据,块IO的处理效率要不比流IO高很多。
2、BIO是阻塞式的,NIO是非阻塞式的,NIO可以提供非阻塞式的高伸缩性网络。
NIO的具体操作(网络IO):
NIO有三大核心包括:Channel(通道),Buffer(缓冲区), Selector(选择器)。NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据。总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道。
Selector(选择器):能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,并且避免了多线程之间的上下文切换导致的开销。
Channel:网络 IO 通道,具体负责进行读写操作。NIO 总是把缓冲区的数据写入通道,或者把通道里的数据读到缓冲区。
Buffer(缓冲区):缓冲区就像一个数组,可以保存多个相同类型的数据。
通过NIO实现Socket通讯的三种方式:
1、一个客户端连接用一个线程,优点:程序编写简单;缺点:如果连接非常多,分配的线程也会非常多,服务器可能会因为资源耗尽而崩溃。
2、把每一个客户端连接交给一个拥有固定数量线程的连接池,优点:程序编写相对简单,可以处理大量的连接。确定:线程的开销非常大,连接如果非常多,排队现象会比较严重。
3、使用 Java 的 NIO,用非阻塞的 IO 方式处理。这种模式可以用一个线程,处理大量的客
户端连接。
代码实现一个NIO示例:
/**
* 网络服务器端程序
* @author Administrator
*/
public class Server {
public static void main(String[] args) throws Exception{
//1. 获取一个 ServerSocketChannel 通道
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
//2. 得到一个 与之关联的Selector 对象
Selector selector=Selector.open();
//3. 绑定端口号8090
serverSocketChannel.bind(new InetSocketAddress(8090));
//4. 设置非阻塞方式
serverSocketChannel.configureBlocking(false);
//5. 把 ServerSocketChannel 对象注册给 Selector 对象
//SelectionKey-通道与选择器的注册关系(OP_ACCEP-有新的网络连接可以 accept,值为 16)
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//6. 开会干活
while(true){
//6.1 监控客户端
if(selector.select(2000)==0){ //nio 非阻塞式的优势
System.out.println("Server:没有客户请求,去做别的事");
continue;
}
//6.2 得到 SelectionKey,判断通道里的事件
Iterator<SelectionKey> keyIterator=selector.selectedKeys().iterator();
while(keyIterator.hasNext()){
SelectionKey key=keyIterator.next();
if(key.isAcceptable()){ //客户端连接请求事件
System.out.println("OP_ACCEPT");
//接受一个连接,返回代表这个连接的通道对象
SocketChannel socketChannel=serverSocketChannel.accept();
//接受一个连接,返回代表这个连接的通道对象
socketChannel.configureBlocking(false);
//注册一个选择器并设置监听事件
//SelectionKey.OP_READ代表了读操作
//分配一个1M的字节缓冲区
socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if(key.isReadable()){ //读取客户端数据事件
SocketChannel channel=(SocketChannel) key.channel();
ByteBuffer buffer=(ByteBuffer) key.attachment();
channel.read(buffer);
System.out.println("客户端发来数据:"+new String(buffer.array()));
}
//6.3 手动从集合中移除当前 key,防止重复处理
keyIterator.remove();
}
}
}
}
/**
* 网络客户端程序
* @author Administrator
*/
public class Client {
public static void main(String[] args) throws Exception{
//1. 得到一个网络通道
SocketChannel channel=SocketChannel.open();
//2. 设置非阻塞方式
channel.configureBlocking(false);
//3. 提供服务器端的 IP 地址和端口号
InetSocketAddress address=new InetSocketAddress("127.0.0.1",8090);
//4. 连接服务器端
if(!channel.connect(address)){
while(!channel.finishConnect()){ //nio 作为非阻塞式的优势
System.out.println("Client:连接服务器端的同时,可以去干别的事情");
}
}
//5. 得到一个缓冲区并存入数据
String msg="hello,Server";
//将 byte 数组包装到缓冲区中
ByteBuffer writeBuf = ByteBuffer.wrap(msg.getBytes());
//6. 发送数据
channel.write(writeBuf);
System.in.read();
}
}
NIO和BIO了解完了,下面通过表格的方式来对比下这两种网络IO的区别和共同点:
对比总结 | BIO | NIO |
IO方式 | 同步阻塞 | 同步非阻塞(多路复用) |
可靠性 | 好 | 差 |
吞吐量 | 低 | 高 |
对BIO和NIO有了一个比较全面的认识之后,下一篇就正式开始进入Netty框架学习。