在Java中使用NIO进行网络编程

开发 后端
在JDK中,有一个非常有意思的库:NIO(New I/O)。这个库中有3个重要的类,分别是java.nio.channels中Selector和Channel,以及java.nio中的Buffer。

在JDK中,有一个非常有意思的库:NIO(New I/O)。这个库中有3个重要的类,分别是java.nio.channels中Selector和Channel,以及java.nio中的Buffer。

本篇文章我们首先了解一下为什么需要NIO来进行网络编程,然后看看一步一步来讲解如何在网络编程中使用NIO。

为什么需要NIO

使用Java编写过Socket程序的同学一定都知道Socket和SocketServer。当调用某个调用的时候,调用的地方就会阻塞,等待响应。这种方式对于小规模的程序非常方便,但是对于大型的程序就有点力不从心了,当有大量的连接的时候,我们可以为每一个连接建立一个线程来操作。但是这种做法带来的缺陷也是显而易见的:

  1. 硬件能够支持大量的并发。
  2. 并发的数量始终有一个上限。
  3. 各个线程之间的优先级不好控制。
  4. 各个Client之间的交互与同步困难。

我们也可以使用一个线程来处理所有的请求,使用不阻塞的IO,轮询查询所有的Client。这种做法同样也有缺陷:无法迅速响应Client端,同时会消耗大量轮询查询的时间。

所以,我们需要一种poll的模式来处理这种情况,从大量的网络连接中找出来真正需要服务的Client。这正是NIO诞生的原因:提供一种Poll的模式,在所有的Client中找到需要服务的Client。

回到我们刚刚说到的3个最最重要的Class:java.nio.channels中Selector和Channel,以及java.nio中的Buffer。

Channel代表一个可以被用于Poll操作的对象(可以是文件流也可以使网络流),Channel能够被注册到一个Selector中。通过调用Selector的select方法可以从所有的Channel中找到需要服务的实例(Accept,read ..)。

Buffer对象提供读写数据的缓存。相对于我们熟悉的Stream对象,Buffer提供更好的性能以及更好的编程透明性(人为控制缓存的大小以及具体的操作)。

配合BUFFER使用CHANNEL

与传统模式的编程不用,Channel不使用Stream,而是Buffer。我们来实现一个简单的非阻塞Echo Client:

  1. package com.cnblogs.gpcuster; 
  2.  
  3. import java.net.InetSocketAddress; 
  4. import java.net.SocketException; 
  5. import java.nio.ByteBuffer; 
  6. import java.nio.channels.SocketChannel; 
  7.  
  8. public class TCPEchoClientNonblocking { 
  9. public static void main(String args[]) throws Exception { 
  10. if ((args.length < 2) || (args.length > 3))// Testforcorrect#ofargs 
  11. throw new IllegalArgumentException( 
  12. "Parameter(s): <Server> <Word> [<Port>]"); 
  13. String server = args[0];// ServernameorIPaddress 
  14. // ConvertinputStringtobytesusingthedefaultcharset 
  15. byte[] argument = args[1].getBytes(); 
  16. int servPort = (args.length == 3) ? Integer.parseInt(args[2]) : 7
  17. // Createchannelandsettononblocking 
  18. SocketChannel clntChan = SocketChannel.open(); 
  19. clntChan.configureBlocking(false); 
  20. // Initiateconnectiontoserverandrepeatedlypolluntilcomplete 
  21. if (!clntChan.connect(new InetSocketAddress(server, servPort))) { 
  22. while (!clntChan.finishConnect()) { 
  23. System.out.print(".");// Dosomethingelse 
  24. ByteBuffer writeBuf = ByteBuffer.wrap(argument); 
  25. ByteBuffer readBuf = ByteBuffer.allocate(argument.length); 
  26. int totalBytesRcvd = 0;// Totalbytesreceivedsofar 
  27. int bytesRcvd;// Bytesreceivedinlastread 
  28. while (totalBytesRcvd < argument.length) { 
  29. if (writeBuf.hasRemaining()) { 
  30. clntChan.write(writeBuf); 
  31. if ((bytesRcvd = clntChan.read(readBuf)) == -1) { 
  32. throw new SocketException("Connection closed prematurely"); 
  33. totalBytesRcvd += bytesRcvd; 
  34. System.out.print(".");// Dosomethingelse 
  35. System.out.println("Received:" + // converttoStringperdefaultcharset 
  36. new String(readBuf.array(), 0, totalBytesRcvd)); 
  37. clntChan.close(); 

这段代码使用ByteBuffer来保存读写的数据。通过clntChan.configureBlocking(false); 设置后,其中的connect,read,write操作都不回阻塞,而是立刻放回结果。

使用SELECTOR

Selector的可以从所有的被注册到自己Channel中找到需要服务的实例。

我们来实现Echo Server。

首先,定义一个接口:

  1. package com.cnblogs.gpcuster; 
  2.  
  3. import java.nio.channels.SelectionKey; 
  4. import java.io.IOException; 
  5.  
  6. public interface TCPProtocol { 
  7. void handleAccept(SelectionKey key) throws IOException; 
  8.  
  9. void handleRead(SelectionKey key) throws IOException; 
  10.  
  11. void handleWrite(SelectionKey key) throws IOException; 
  12. 我们的Echo Server将使用这个接口。然后我们实现Echo Server: 
  13. import java.io.IOException; 
  14. import java.net.InetSocketAddress; 
  15. import java.nio.channels.SelectionKey; 
  16. import java.nio.channels.Selector; 
  17. import java.nio.channels.ServerSocketChannel; 
  18. import java.util.Iterator; 
  19.  
  20. public class TCPServerSelector { 
  21. private static final int BUFSIZE = 256;// Buffersize(bytes) 
  22. private static final int TIMEOUT = 3000;// Waittimeout(milliseconds) 
  23.  
  24. public static void main(String[] args) throws IOException { 
  25. if (args.length < 1) {// Testforcorrect#ofargs 
  26. throw new IllegalArgumentException("Parameter(s):<Port>..."); 
  27. // Createaselectortomultiplexlisteningsocketsandconnections 
  28. Selector selector = Selector.open(); 
  29. // Createlisteningsocketchannelforeachportandregisterselector 
  30. for (String arg : args) { 
  31. ServerSocketChannel listnChannel = ServerSocketChannel.open(); 
  32. listnChannel.socket().bind( 
  33. new InetSocketAddress(Integer.parseInt(arg))); 
  34. listnChannel.configureBlocking(false);// mustbenonblockingtoregister 
  35. // Registerselectorwithchannel.Thereturnedkeyisignored 
  36. listnChannel.register(selector, SelectionKey.OP_ACCEPT); 
  37. // Createahandlerthatwillimplementtheprotocol 
  38. TCPProtocol protocol = new EchoSelectorProtocol(BUFSIZE); 
  39. while (true) {// Runforever,processingavailableI/Ooperations 
  40. // Waitforsomechanneltobeready(ortimeout) 
  41. if (selector.select(TIMEOUT) == 0) {// returns#ofreadychans 
  42. System.out.print("."); 
  43. continue
  44. // GetiteratoronsetofkeyswithI/Otoprocess 
  45. Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator(); 
  46. while (keyIter.hasNext()) { 
  47. SelectionKey key = keyIter.next();// Keyisbitmask 
  48. // Serversocketchannelhaspendingconnectionrequests? 
  49. if (key.isAcceptable()) { 
  50. protocol.handleAccept(key); 
  51. // Clientsocketchannelhaspendingdata? 
  52. if (key.isReadable()) { 
  53. protocol.handleRead(key); 
  54. // Clientsocketchannelisavailableforwritingand 
  55. // keyisvalid(i.e.,channelnotclosed)? 
  56. if (key.isValid() && key.isWritable()) { 
  57. protocol.handleWrite(key); 
  58. keyIter.remove();// removefromsetofselectedkeys 

我们通过listnChannel.register(selector, SelectionKey.OP_ACCEPT); 注册了一个我们感兴趣的事件,然后调用selector.select(TIMEOUT)等待订阅的时间发生,然后再采取相应的处理措施。

***我们实现EchoSelectorProtocol

  1. package com.cnblogs.gpcuster; 
  2.  
  3. import java.nio.channels.SelectionKey; 
  4. import java.nio.channels.SocketChannel; 
  5. import java.nio.channels.ServerSocketChannel; 
  6. import java.nio.ByteBuffer; 
  7. import java.io.IOException; 
  8.  
  9. public class EchoSelectorProtocol implements TCPProtocol { 
  10. private int bufSize;// SizeofI/Obuffer 
  11.  
  12. public EchoSelectorProtocol(int bufSize) { 
  13. this.bufSize = bufSize; 
  14.  
  15. public void handleAccept(SelectionKey key) throws IOException { 
  16. SocketChannel clntChan = ((ServerSocketChannel) key.channel()).accept(); 
  17. clntChan.configureBlocking(false);// Mustbenonblockingtoregister 
  18. // Registertheselectorwithnewchannelforreadandattachbytebuffer 
  19. clntChan.register(key.selector(), SelectionKey.OP_READ, ByteBuffer 
  20. .allocate(bufSize)); 
  21.  
  22. public void handleRead(SelectionKey key) throws IOException { 
  23. // Clientsocketchannelhaspendingdata 
  24. SocketChannel clntChan = (SocketChannel) key.channel(); 
  25. ByteBuffer buf = (ByteBuffer) key.attachment(); 
  26. long bytesRead = clntChan.read(buf); 
  27. if (bytesRead == -1) {// Didtheotherendclose? 
  28. clntChan.close(); 
  29. else if (bytesRead > 0) { 
  30. // Indicateviakeythatreading/writingarebothofinterestnow. 
  31. key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); 
  32.  
  33. public void handleWrite(SelectionKey key) throws IOException { 
  34. /* 
  35. * Channelisavailableforwriting,andkeyisvalid(i.e.,clientchannel 
  36. * notclosed). 
  37. */ 
  38. // Retrievedatareadearlier 
  39. ByteBuffer buf = (ByteBuffer) key.attachment(); 
  40. buf.flip();// Preparebufferforwriting 
  41. SocketChannel clntChan = (SocketChannel) key.channel(); 
  42. clntChan.write(buf); 
  43. if (!buf.hasRemaining()) {// Buffercompletelywritten? 
  44. // Nothingleft,sonolongerinterestedinwrites 
  45. key.interestOps(SelectionKey.OP_READ); 
  46. buf.compact();// Makeroomformoredatatobereadin 

在这里,我们又进一步对Selector注册了相关的事件:key.interestOps(SelectionKey.OP_READ);

这样,我们就实现了基于NIO的Echo 系统。

原文链接:http://www.cnblogs.com/gpcuster/archive/2009/12/27/1633366.html

【编辑推荐】

  1. Java NIO非阻塞服务器示例
  2. 基于Java NIO的即时聊天服务器模型
  3. Java解读NIO Socket非阻塞模式
  4. 用Java.nio.* 进行网络编程
  5. 利用NIO建立Socket服务器

 

责任编辑:林师授 来源: 逖靖寒的博客
相关推荐

2011-12-07 16:50:29

JavaNIO

2024-02-07 11:44:20

NestJSRxJS异步编程

2021-03-22 08:45:30

异步编程Java

2024-01-09 09:27:57

Rust编程泛型

2024-01-07 17:29:10

编程语言线程Rust

2021-11-29 22:59:34

Go Dockertest集成

2023-10-28 16:22:21

Go接口

2011-12-15 09:40:06

Javanio

2019-12-12 13:50:27

strace追踪系统调用Linux

2023-05-24 16:41:41

React前端

2009-02-27 17:15:05

XMLDOMXPath

2011-06-08 16:56:37

VS2008 Qt

2021-01-05 08:39:51

容器前端流水线

2021-09-21 15:44:02

LinuxOBSWayland

2021-02-20 09:14:35

PythonPygal可视化

2019-01-17 10:25:56

Python编程语言程序员

2009-08-04 09:39:11

2010-06-23 15:21:45

SharepointExchange

2018-10-08 10:02:52

LinuxWondershape网络带宽

2022-10-08 07:30:17

网络安全编程语言C++
点赞
收藏

51CTO技术栈公众号