文章原始出处:http://navigating.blogbus.com/logs/18024423.html
在Java1.4以前,Java的网络编程是只有阻塞方式的,在Java1.4以及之后,Java提供了非阻塞的网络编程API.从Java的发展来看,由于Java的快速发展,JVM性能的提升,涉足到服务端应用程序开发也越来越多,要求高性能的网络应用越来越多,这是Java推出非阻塞网络编程的最主要原因吧。
对我而言,以前的大部分服务端应用主要是搭建在应用服务器之上,所以通讯这部分工作都是有应用服务器来实现和管理的。这次由于通讯和协议,我们必须自己实现一个能处理大量并发客户端的高性能并行处理的Java服务端程序。因此,选择非阻塞的处理方式也是必然的。我们首先来看看阻塞的处理方式:
在阻塞的网络编程方式中,针对于每一个单独的网络连接,都必须有一个线程对应的绑定该网络连接,进行网络字节流的处理。下面是一段代码:
public static void main(String[] args) { try { ServerSocket ssc = new ServerSocket(23456); while (true) { System.out.println("Enter Accept:"); Socket s = ssc.accept(); try { (new Thread(new Worker(s))).start(); } catch (Exception e) { // TODO e.printStackTrace(); } } } catch (IOException e) { e.printStackTrace(); } } public static class Worker implements Runnable { private Socket s; private boolean running = true;; public Worker(Socket s) { this.s = s; } public void run() { try { InputStream is = s.getInputStream(); OutputStream os = s.getOutputStream(); while (running) { byte[] b = this.readByLength(is, 1024); this.process(b); } } catch (Throwable t) { // TODO t.printStackTrace(); } } private byte[] readByLength(InputStream is, int contLen) throws IOException { byte[] b = new byte[contLen]; int off = 0; int length = 0; while ((length = is.read(b, off, contLen - off)) >= 0) { off = +length; if (off >= contLen) { break; } } return b; } private void process(byte[] b) { } }
在这段代码中,我们看到有两个阻塞的方法,是ServerSocket的accept()方法;和InputStream的read()方式。因此我们需要两类型的线程分别进行处理。而且每一个阻塞方法所绑定的线程的生命周期和网络连接的生命周期是一致的。基于以上的原因,NIO应运而生,一方面,为每一个网络连接建立一个线程对应,同时每一个线程有大量的线程处于读写以外的空闲状态,因此希望降低线程的数量,降低每个空闲状态,提高单个线程的运行执行效率,实际上是在更加充分运用CPU的计算、运行能力(因为,如果有大量的链路存在,就存在大量的线程,而大量的线程都阻塞在read()或者write()方法,同时CPU又需要来回频繁的在这些线程中间调度和切换,必然带来大量的系统调用和资源竞争.);另外一方面希望提高网络IO和硬盘IO操作的性能。在NIO主要出现了三个新特性:
1.数据缓冲处理(ByteBuffer):由于操作系统和应用程序数据通信的原始类型是byte,也是IO数据操作的基本单元,在NIO中,每一个基本的原生类型(boolean除外)都有Buffer的实现:CharBuffer、IntBuffer、DoubleBuffer、ShortBuffer、LongBuffer、FloatBuffer和ByteBuffer,数据缓冲使得在IO操作中能够连续的处理数据流。当前有两种ByteBuffer,一种是Direct ByteBuffer,另外一种是NonDirect ByteBuffer;ByteBuffer是普通的Java对象,遵循Java堆中对象存在的规则;而Direct ByteBuffer是native代码,它内存的分配不在Java的堆栈中,不受Java内存回收的影响,每一个Direct ByteBuffer都是直接分配的一块连续的内存空间,也是NIO提高性能的重要办法之一。另外数据缓冲有一个很重要的特点是,基于一个数据缓冲可以建立一个或者多个逻辑的视图缓冲(View Buffer).比方说,通过View Buffer,可以将一个Byte类型的Buffer换作Int类型的缓冲;或者一个大的缓冲转作很多小的Buffer。之所以称为View Buffer是因为这个转换仅仅是逻辑上,在物理上并没有创建新的Buffer。这为我们操作Buffer带来诸多方便。
2.异步通道(Channel):Channel是一个与操作系统紧密结合的本地代码较多的对象。通过Channel来实现网络编程的非阻塞操作,同时也是其与ByteBuffer、Socket有效结合充分利用非阻塞、ByteBuffer的特性的。在后面我们会看到具体的SocketChannel的用法。
3.有条件的选择(Readiness Selection):大多数操作系统都有支持有条件选择准备就绪IO通道的API,即能够保证一个线程同时有效管理多个IO通道。在NIO中,由Selector(维护注册进来的Channel和这些Channel的状态)、SelectableChannel(能被Selector管理的Channel)和SelectionKey(SelectionKey标识Selector和SelectableChannel之间的映射关系,一旦一个Channel注册到Selector中,就会返回一个SelectionKey对象。SelectionKey保存了两类状态:对应的Channel注册了哪些操作;对应的Channel的那些操作已经准备好了,可以进行相应的数据操作了)结合来实现这个功能的。
NIO的包中主要包含了这样几种抽象数据类型:
- Buffer:包含数据且用于读写的线形表结构。其中还提供了一个特殊类用于内存映射文件的I/O操作。
- Charset:它提供Unicode字符串影射到字节序列以及逆映射的操作。
- Channels:包含socket,file和pipe三种管道,都是全双工的通道。
- Selector:多个异步I/O操作集中到一个或多个线程中(可以被看成是Unix中select()函数的面向对象版本)。
NIO非阻塞的典型编程模型如下:
private Selector selector = null; private static final int BUF_LENGTH = 1024; public void start() throws IOException { if (selector != null) { selector = Selector.open(); } ServerSocketChannel ssc = ServerSocketChannel.open(); ServerSocket serverSocket = ssc.socket(); serverSocket.bind(new InetSocketAddress(80)); ssc.configureBlocking(false); ssc.register(selector, SelectionKey.OP_ACCEPT); try { while (true) { int nKeys = UnblockServer.this.selector.select(); if (nKeys > 0) { Iterator it = selector.selectedKeys().iterator(); while (it.hasNext()) { SelectionKey key = (SelectionKey) it.next(); if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key.channel(); SocketChannel channel = server.accept(); if (channel == null) { continue; } channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_READ); } if (key.isReadable()) { readDataFromSocket(key); } it.remove(); } } } } catch (IOException ioe) { ioe.printStackTrace(); } } /** * @param key * @throws IOException */ private void readDataFromSocket(SelectionKey key) throws IOException { ByteBuffer buf = ByteBuffer.allocate(BUF_LENGTH); SocketChannel sc = (SocketChannel) key.channel(); int readBytes = 0; int ret; try { while ((ret = sc.read(buf.buf())) > 0) { readBytes += ret; } } finally { buf.flip(); } // process buffer // buf.clear(); }
从这段程序,我们基本可以了解到NIO网络编程的一些特点,创建一个SocketServer的方式已经发生了变化,需要指定非阻塞模式,需要创建一个Channel然后注册到Selector中去,同样,建立一个网络连接过程也是一样的模式,然后就是有条件的选择(Readiness Selection).这样,我们的每一个线程只需要处理一类型的网络选择。在代码上,我们发现处理的方式和阻塞完全不一样了,我们需要完全重新考虑如何编写网络通信的模块了:
1.持久连接的超时问题(Timeout),因为API没有直接的支持timeout的参数设置功能,因此需要我们自己实现一个这样功能。
2.如何使用Selector,由于每一个Selector的处理能力是有限的,因此在大量链接和消息处理过程中,需要考虑如何使用多个Selector.
3.在非阻塞情况下,read和write都不在是阻塞的,因此需要考虑如何完整的读取到确定的消息;如何在确保在网络环境不是很好的情况下,一定将数据写进IO中。
4.如何应用ByteBuffer,本身大量创建ByteBuffer就是很耗资源的;如何有效的使用ByteBuffer?同时ByteBuffer的操作需要仔细考虑,因为有position()、mark()、limit()、capacity等方法。
5.由于每一个线程在处理网络连接的时候,面对的都是一系列的网络连接,需要考虑如何更好的使用、调度多线程。在对消息的处理上,也需要保证一定的顺序,比方说,登录消息最先到达,只有登录消息处理之后,才有可能去处理同一个链路上的其他类型的消息。
6.在网络编程中可能出现的内存泄漏问题。
在NIO的接入处理框架上,大约有两种并发线程:
1.Selector线程,每一个Selector单独占用一个线程,由于每一个Selector的处理能力是有限的,因此需要多个Selector并行工作。
2.对于每一条处于Ready状态的链路,需要线程对于相应的消息进行处理;对于这一类型的消息,需要并发线程共同工作进行处理。在这个过程中,不断可能需要消息的完整性;还要涉及到,每个链路上的消息可能有时序,因此在处理上,也可能要求相应的时序性。
当前社区的开源NIO框架实现有MINA、Grizzly、NIO framework、QuickServer、xSocket等,其中MINA和Grizzly最为活跃,而且代码的质量也很高。他们俩在实现的方法上也完全大不一样。(大部分Java的开源服务器都已经用NIO重写了网络部分。 )
不管是我们自己实现NIO的网络编程框架,还是基于MINA、Grizzly等这样的开源框架进行开发,都需要理解确定的了解NIO带来的益处和NIO编程需要解决的众多类型的问题。充足、有效的单元测试,是我们写好NIO代码的好助手:)
Resource:
http://www.cis.temple.edu/~ingargio/cis307/readings/unix4.html#states
《Java NIO》
《GlassFish--开源的Java EE应用服务器》
相关推荐
TCP(传输控制协议)是一种面向连接、可靠的、基于字节流的传输层通信协议,而NIO(非阻塞I/O)则是Java提供的一种高效处理I/O操作的方式。在这个“tcp.zip”压缩包中,我们可能找到了关于使用Java实现TCP服务器和...
Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java平台中用于替代标准I/O(BIO)模型的一种新机制。...学习和理解Java NIO以及Netty的使用,对于提升Java网络编程的能力至关重要。
在提供的文档"tcp nio 服务端、客户端例子--参考《分布式Java应用:基础与实践》.doc"中,可能会包含具体的NIO服务端和客户端的代码示例,这些示例将展示如何使用`ServerSocketChannel`、`SocketChannel`、`Selector...
最后,TCP编程中还可以涉及性能优化,如缓冲区大小的调整、NIO(非阻塞I/O)的使用、SO_REUSEADDR和SO_KEEPALIVE选项的配置等。 总结来说,用Java进行TCP编程主要涉及`ServerSocket`、`Socket`类的使用,数据的读写...
在实际开发中,我们通常会使用一些NIO框架来简化编程,例如: 1. **Netty**:是一个高性能、异步事件驱动的网络应用框架,常用于创建服务器和客户端的TCP、UDP通信。 2. **Grizzly**:是Sun Microsystems(现Oracle...
Java NIO 网络编程初探 1. Java NIO Java 1.4 版本添加了一个新的IO API,称为NIO(New IO)。NIO拥有所有IO的功能,但是操作方法却完全不一样。NIO支持面向缓冲区的、基于通道的IO操作。能够更加高效的进行IO操作。...
7. **网络编程**:Java NIO引入了SocketChannel和ServerSocketChannel,用于实现高效的TCP/IP和UDP/IP通信。这些通道与传统的Socket和ServerSocket相比,提供了非阻塞的特性。 `Pro Java 7 NIO.2`这本书由Anghel ...
第6节将聚焦于Java NIO中的Socket通道操作,这是网络编程中的重要一环。SocketChannel是基于TCP协议的,提供了一种可靠的数据传输方式。我们将学习如何创建和配置SocketChannel,以及如何通过它进行数据的读写。此外...
### Java 下 TCP 文件传输功能实现 #### 网络协议基础 在理解如何利用 Java 实现基于 TCP 的文件传输之前,我们首先需要了解一些基本的网络协议知识。 **TCP/IP (Transmission Control Protocol/Internet Protocol...
1. **网络编程**:Java NIO在服务器端开发中,特别是高并发的TCP连接处理,如聊天服务器、游戏服务器等。 2. **大文件处理**:处理大文件时,NIO的直接内存访问和非阻塞特性可以提高性能。 3. **高性能数据交换**...
Java TCP编程是网络通信的基础,尤其在开发服务器端和客户端应用程序时不可或缺。TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,它确保了数据包在网络中的有序和无损传输。在Java中,...
Java TCP/IP Socket编程是网络通信领域中的核心技术,尤其在Java平台中,Socket是实现客户端与服务器之间通信的基础。原书第二版深入浅出地讲解了Java Socket编程的各个方面,为开发者提供了全面的学习资源。以下是...
Java NIO,即Non-Blocking I/O,是Java在JDK 1.4引入的一套新的I/O API,旨在提供一种更加高效的方式来处理I/O操作,尤其是对于网络编程和高并发场景。NIO的核心概念包括通道(Channel)、缓冲区(Buffer)和选择器...
**JAVA-NIO程序设计完整实例** Java NIO(New IO)是Java 1.4引入的一个新特性,它为Java提供了非阻塞I/O操作的能力,使得Java在处理I/O时更加高效。NIO与传统的BIO(Blocking I/O)模型相比,其核心在于它允许程序...
Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java标准库提供的一种替代传统I/O模型的新技术。尚硅谷的12讲Java NIO课程,旨在深入浅出地讲解这一重要概念,帮助开发者提升程序的性能和效率。...
Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java从...同时,NIO也常用于网络编程,如TCP和UDP通信,以及文件操作。虽然NIO的学习曲线相对较陡,但掌握后,能为复杂IO场景提供强大的解决方案。