- 浏览: 440094 次
- 性别:
- 来自: 深圳
文章分类
最新评论
-
su6838354:
我有点疑问啊,thread1中的i自增的慢的话,thread2 ...
浅析pthread_cond_wait -
zeronever:
请问pthread_cond_signal有解锁操纵吗?我在p ...
浅析pthread_cond_wait -
paladin1988:
你这帖子真心不错。。
浅谈bitmap算法 -
parabellum_sky:
昨天还有个姑娘让我去考我说会考虑
个人日志
java 网络编程中,不可避免的要谈论NIO,这篇文章就来谈谈对NIO的认识。
IO传统意义上分为File IO,StreamIO,这两个分别对应文件读写和Socket,文件读写IO目前只有阻塞进行读写,而socket由于Selector和ByteBuffer构成了非阻塞IO,但是由于NIO需要单独的线程去遍历selectoryKey,导致线程资源一致被占用,所以AIO出现了,利用监听回调,来取代遍历selectorykey。
一、阻塞IO网络通信
在传统的阻塞IO模型中,大多是通过多线程处理不同的socket,尤其是在服务端,accept会创建不同的channel,然后利用独立的线程进行处理具体的业务逻辑。由于thread的创建和切换需要占用系统资源,当thread数量比较少时,这种模式还是比较好的,但是当thead量比较大时,就会出现许多问题。一个thread 栈linux默认是8M大小,如果客户端通过DDOS进行攻击,建立多个connect,多导致服务端内存占用非常大,导致服务器挂掉。
下面写一个阻塞IO通信模型:
客户端:
上面代码是简单的Client建立socket通道,同服务端通信的例子,这里的通道是一个短链接,每次都需要建立连接,发送数据,然后在关闭。
Socket是一个网络IO模块,分为input,output,这里的读和写都是阻塞的,等待服务端有返回时,才会有响应,这种模式就需要占用多个线程资源。
看一下服务端代码:
这里每次收到一个accept链接请求,都会创建一个socket,然后单独一个线程处理socket请求内容。
Socket client = serverSocket.accept();
这句是阻塞的,等待客户端的链接。
二、NIO使用实例
上面分析了传统的Socket IO通信模型,对于高线程消耗的阻塞模式的IO,采用NIO可以解决这些问题,单个的线程处理所有socket请求,以及同一个线程可以处理不同端口socket请求。
但是NIO的问题也相当明显,线程需要一致占用cpu资源去循环查找selector的key值,处理已经准备好的channel。
客户端代码:
这里建立了两个socketchannel,通过不同的端口连接到同一个服务端,利用一个线程处理这两个socket数据,通过selector监听不同的socket。
服务端代码:
这里打开多个端口监听,在主线程里通过selector进行监听处理。
NIO里关键要理解selector的工作原理,以及底层实现的方式,与linux的epoll,select,poll的区别。
IO传统意义上分为File IO,StreamIO,这两个分别对应文件读写和Socket,文件读写IO目前只有阻塞进行读写,而socket由于Selector和ByteBuffer构成了非阻塞IO,但是由于NIO需要单独的线程去遍历selectoryKey,导致线程资源一致被占用,所以AIO出现了,利用监听回调,来取代遍历selectorykey。
一、阻塞IO网络通信
在传统的阻塞IO模型中,大多是通过多线程处理不同的socket,尤其是在服务端,accept会创建不同的channel,然后利用独立的线程进行处理具体的业务逻辑。由于thread的创建和切换需要占用系统资源,当thread数量比较少时,这种模式还是比较好的,但是当thead量比较大时,就会出现许多问题。一个thread 栈linux默认是8M大小,如果客户端通过DDOS进行攻击,建立多个connect,多导致服务端内存占用非常大,导致服务器挂掉。
下面写一个阻塞IO通信模型:
客户端:
public class Client { public static final String IP_ADDR = "localhost"; // 服务端ip地址,这里采用本地地址 public static final int PORT = 1978; public static void main(String[] args) { while (true) { Socket socket = null; try { //创建一个流套接字并将其连接到指定主机上的指定端口号 socket = new Socket(IP_ADDR, PORT); //读取服务器端数据 DataInputStream input = new DataInputStream(socket.getInputStream()); //向服务器端发送数据 DataOutputStream out = new DataOutputStream(socket.getOutputStream()); System.out.print("请输入: \t"); String str = new BufferedReader(new InputStreamReader(System.in)).readLine(); out.writeUTF(str); String ret = input.readUTF(); System.out.println("服务器端返回过来的是: " + ret); // 如接收到 "OK" 则断开连接 if ("OK".equals(ret)) { System.out.println("客户端将关闭连接"); Thread.sleep(500); break; } out.close(); input.close(); } catch (Exception e) { System.out.println("客户端异常:" + e.getMessage()); } finally { if (socket != null) { try { socket.close(); } catch (IOException e) { socket = null; System.out.println("客户端 finally 异常:" + e.getMessage()); } } } } } } }
上面代码是简单的Client建立socket通道,同服务端通信的例子,这里的通道是一个短链接,每次都需要建立连接,发送数据,然后在关闭。
Socket是一个网络IO模块,分为input,output,这里的读和写都是阻塞的,等待服务端有返回时,才会有响应,这种模式就需要占用多个线程资源。
看一下服务端代码:
public class Service { public static final int PORT = 12345;//监听的端口号 public static void main(String[] args) { System.out.println("服务器启动...\n"); Service server = new Service(); server.init(); } public void init() { try { ServerSocket serverSocket = new ServerSocket(PORT); while (true) { // 一旦有堵塞, 则表示服务器与客户端获得了连接 Socket client = serverSocket.accept(); // 处理这次连接 new HandlerThread(client); } } catch (Exception e) { System.out.println("服务器异常: " + e.getMessage()); } } private class HandlerThread implements Runnable { private Socket socket; public HandlerThread(Socket client) { socket = client; new Thread(this).start(); } public void run() { try { // 读取客户端数据 DataInputStream input = new DataInputStream(socket.getInputStream()); String clientInputStr = input.readUTF();//这里要注意和客户端输出流的写方法对应,否则会抛 EOFException // 处理客户端数据 System.out.println("客户端发过来的内容:" + clientInputStr); // 向客户端回复信息 DataOutputStream out = new DataOutputStream(socket.getOutputStream()); System.out.print("请输入:\t"); // 发送键盘输入的一行 String s = new BufferedReader(new InputStreamReader(System.in)).readLine(); out.writeUTF(s); out.close(); input.close(); } catch (Exception e) { System.out.println("服务器 run 异常: " + e.getMessage()); } finally { if (socket != null) { try { socket.close(); } catch (Exception e) { socket = null; System.out.println("服务端 finally 异常:" + e.getMessage()); } } } } } }
这里每次收到一个accept链接请求,都会创建一个socket,然后单独一个线程处理socket请求内容。
Socket client = serverSocket.accept();
这句是阻塞的,等待客户端的链接。
二、NIO使用实例
上面分析了传统的Socket IO通信模型,对于高线程消耗的阻塞模式的IO,采用NIO可以解决这些问题,单个的线程处理所有socket请求,以及同一个线程可以处理不同端口socket请求。
但是NIO的问题也相当明显,线程需要一致占用cpu资源去循环查找selector的key值,处理已经准备好的channel。
客户端代码:
public class TCPClient{ public static final String IP_ADDR = "localhost";//服务器地址 public static final int PORT_ONE = 1978; public static final int PORT = 12345;//服务器端口号 // 信道选择器 private Selector selector; // 与服务器通信的信道 SocketChannel socketChannel; // 要连接的服务器Ip地址 private String hostIp; // 要连接的远程服务器在监听的端口 private int hostListenningPort; public TCPClient(String HostIp,int HostListenningPort, Selector selector)throws IOException{ this.hostIp=HostIp; this.hostListenningPort=HostListenningPort; initialize(selector); } /** * 初始化 * @throws IOException */ private void initialize(Selector selector) throws IOException{ // 打开监听信道并设置为非阻塞模式 socketChannel = SocketChannel.open(new InetSocketAddress(hostIp, hostListenningPort)); socketChannel.configureBlocking(false); // 打开并注册选择器到信道 socketChannel.register(selector, SelectionKey.OP_READ); // 启动读取线程 // new TCPClientReadThread(selector); } /** * 发送字符串到服务器 * @param message * @throws IOException */ public void sendMsg(String message) throws IOException{ ByteBuffer writeBuffer=ByteBuffer.wrap(message.getBytes("UTF-16")); socketChannel.write(writeBuffer); } public Selector getSelector(){ return selector; } public static void main(String[] args) throws IOException, Throwable{ // 打开并注册选择器到信道 Selector selector = Selector.open(); TCPClient clientOne = new TCPClient(IP_ADDR,PORT_ONE, selector); Thread.sleep(5000); // 段时间内多次创建链接,会出现Address already in use TCPClient clientTwo = new TCPClient(IP_ADDR, PORT, selector); // 启动读取线程 new TCPClientReadThread(selector); clientOne.sendMsg("你好!Nio one!醉里挑灯看剑,梦回吹角连营 我是端口1978"); clientTwo.sendMsg("你好!Nio tow! 我是端口12345"); } }
public class TCPClientReadThread implements Runnable{ private Selector selector; public TCPClientReadThread(Selector selector){ this.selector=selector; new Thread(this).start(); } public void run() { try { while (selector.select() > 0) { // 遍历每个有可用IO操作Channel对应的SelectionKey for (SelectionKey sk : selector.selectedKeys()) { // 如果该SelectionKey对应的Channel中有可读的数据 if (sk.isReadable()) { // 使用NIO读取Channel中的数据 SocketChannel sc = (SocketChannel) sk.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); sc.read(buffer); buffer.flip(); // 将字节转化为为UTF-16的字符串 String receivedString=Charset.forName("UTF-16").newDecoder().decode(buffer).toString(); // 控制台打印出来 System.out.println("接收到来自服务器"+sc.socket().getRemoteSocketAddress()+"的信息:"+receivedString); // 为下一次读取作准备 sk.interestOps(SelectionKey.OP_READ); } // 删除正在处理的SelectionKey selector.selectedKeys().remove(sk); } } } catch (IOException ex) { ex.printStackTrace(); } } }
这里建立了两个socketchannel,通过不同的端口连接到同一个服务端,利用一个线程处理这两个socket数据,通过selector监听不同的socket。
服务端代码:
public class TCPServer{ // 缓冲区大小 private static final int BufferSize = 1024; // 超时时间,单位毫秒 private static final int TimeOut = 3000; // 本地监听端口 private static final int ListenPort = 1978; private static final int listenPortTwo = 12345; public static void Testmain(String[] args) throws IOException{ // 创建选择器 Selector selector = Selector.open(); TCPChannel.openChannel(selector, ListenPort, SelectionKey.OP_ACCEPT); TCPChannel.openChannel(selector, listenPortTwo, SelectionKey.OP_ACCEPT); // 创建一个处理协议的实现类,由它来具体操作 TCPProtocol protocol=new TCPProtocolImpl(BufferSize); // 主线程进行数据的操作 // 反复循环,等待IO while(true){ // 等待某信道就绪(或超时) if(selector.select(TimeOut)==0){ // block System.out.print("独自等待."); continue; } // 取得迭代器.selectedKeys()中包含了每个准备好某一I/O操作的信道的SelectionKey Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator(); while(keyIter.hasNext()){ SelectionKey key=keyIter.next(); try{ if(key.isAcceptable()){ // 有客户端连接请求时 protocol.handleAccept(key); } if(key.isReadable()){ // 从客户端读取数据 protocol.handleRead(key); } if(key.isValid() && key.isWritable()){ // 客户端可写时 protocol.handleWrite(key); } } catch(IOException ex){ // 出现IO异常(如客户端断开连接)时移除处理过的键 keyIter.remove(); continue; } // 移除处理过的键 keyIter.remove(); } } } }
这里打开多个端口监听,在主线程里通过selector进行监听处理。
public class TCPProtocolImpl implements TCPProtocol{ private int bufferSize; public TCPProtocolImpl(int bufferSize){ this.bufferSize=bufferSize; } // 建立通道,并注册到selector进行监听 public void handleAccept(SelectionKey key) throws IOException { SocketChannel clientChannel=((ServerSocketChannel)key.channel()).accept(); clientChannel.configureBlocking(false); clientChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize)); } public void handleRead(SelectionKey key) throws IOException { // 获得与客户端通信的信道 SocketChannel clientChannel=(SocketChannel)key.channel(); // 得到并清空缓冲区 ByteBuffer buffer=(ByteBuffer)key.attachment(); buffer.clear(); // 读取信息获得读取的字节数 long bytesRead=clientChannel.read(buffer); if(bytesRead==-1){ // 没有读取到内容的情况 clientChannel.close(); } else { // 将缓冲区准备为数据传出状态 buffer.flip(); // 将字节转化为为UTF-16的字符串 String receivedString=Charset.forName("UTF-16").newDecoder().decode(buffer).toString(); // 控制台打印出来 System.out.println("接收到来自"+clientChannel.socket().getRemoteSocketAddress()+"的信息:"+receivedString); // 准备发送的文本 String sendString="你好,客户端. @"+new Date().toString()+",已经收到你的信息"+receivedString; buffer=ByteBuffer.wrap(sendString.getBytes("UTF-16")); clientChannel.write(buffer); // 设置为下一次读取或是写入做准备 key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); } } public void handleWrite(SelectionKey key) throws IOException { // do nothing } }
public class TCPChannel { public static void openChannel(Selector selector, int port, int selectorKey) throws IOException { // 打开监听信道 ServerSocketChannel listenerChannel = ServerSocketChannel.open(); // 与本地端口绑定 listenerChannel.socket().bind(new InetSocketAddress(port)); // 设置为非阻塞模式 listenerChannel.configureBlocking(false); // 将选择器绑定到监听信道,只有非阻塞信道才可以注册选择器.并在注册过程中指出该信道可以进行Accept操作 listenerChannel.register(selector, selectorKey); } }
NIO里关键要理解selector的工作原理,以及底层实现的方式,与linux的epoll,select,poll的区别。
发表评论
-
jni方法的注册和调用流程
2015-07-07 17:20 3240JNI在android中起重要作用,是连接java层和dalv ... -
MethodHooker--Hook分析
2015-07-03 17:58 2786Hook的原理是修改java层 ... -
XPosed解析--XposedBridge--main分析
2015-07-02 11:10 5283XposedBridge是Xposed框架替 ... -
XPosed解析--callback_XposedBridge_initNative分析
2015-07-02 10:41 2869callback_XposedBridge_initNativ ... -
ViewGroup onInterceptTouchEvent and OnTouchEvent
2014-09-16 15:46 891ViewGroup 继承View,实现了View各个方法,同时 ... -
libdgx之gdx-vectorpinball分析--整体篇
2013-09-17 09:47 1036gdx-vectorpinball分析--整体篇 gdx-v ... -
Android 归来
2011-10-21 09:09 77时隔半年,重新拾起android开发,相信自己能走下去。
相关推荐
java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java...
Java NIO系列教程(一) Java NIO 概述 Java NIO系列教程(二) Channel Java NIO系列教程(三) Buffer Java NIO系列教程(四) Scatter/Gather Java NIO系列教程(五) 通道之间的数据传输 Java NIO系列教程(六)...
Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java平台中用于替代标准I/O(BIO)模型的一种新机制。NIO在Java 1.4版本引入,提供了更高效的数据处理和通道通信方式,特别适用于高并发、大数据...
Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java标准库提供的一种替代传统的I/O模型的新技术。自Java 1.4版本引入NIO后,它为Java开发者提供了更高效的数据传输方式,尤其是在处理大量并发...
### Java NIO 处理超大数据文件的知识点详解 #### 一、Java NIO简介 Java NIO(New IO)是Java平台上的新输入/输出流API,它提供了与传统IO(即Java IO)不同的数据处理方式。NIO在Java 1.4版本引入,并在后续版本...
Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式。 Java NIO: Channels and Buffers(通道和缓冲区) 标准的IO基于字节流和字符流进行操作的,...
Java NIO 深入探讨了 1.4 版的 I/O 新特性,并告诉您如何使用这些特性来极大地提升您所写的 Java 代码的执行效率。这本小册子就程序员所面临的有代表性的 I/O 问题作了详尽阐述,并讲解了 如何才能充分利用新的 I/O ...
Java NIO(New Input/Output)是Java标准库提供的一种I/O模型,它与传统的 Blocking I/O(IO)相比,提供了更加高效的数据传输方式。在Java NIO中,"新"主要体现在非阻塞和多路复用这两个特性上,这使得NIO更适合于...
Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java标准库提供的一种替代传统I/O模型的新技术。在传统的Java IO模型中,读写操作是阻塞的,即当调用read或write方法时,线程会等待数据准备好或...
Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java从1.4版本开始引入的一种新的I/O模型,它为Java应用程序提供了更高效的数据传输方式。传统的Java I/O模型(BIO)在处理大量并发连接时效率较...
Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java标准库提供的一种替代传统I/O模型的新技术。在Java 1.4版本中引入,NIO提供了一种全新的I/O编程方式,使得Java开发者能够更高效地处理I/O操作...
### Java NIO 实现Socket通信详解 #### 一、NIO与传统IO的区别及优势 在探讨如何使用Java NIO实现Socket通信之前,我们需要先理解NIO(Non-blocking I/O,非阻塞I/O)与传统阻塞I/O之间的区别。 **传统阻塞I/O...
Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java从1.4版本开始引入的一种新的I/O模型,它为Java应用程序提供了更高效、灵活的I/O操作方式。NIO与传统的 Blocking I/O(阻塞I/O)模式相比,...