用Java.nio.* 进行网络编程

开发 后端
因为打算用java编写异步通信的server和client程序,笔者便学习使用java.nio开发包,其间遇到一些问题,上网却发现网上对它的应用描述的不是很多。所以,笔者不惜班门弄斧,做些简单的讨论,以便大家更进一步的讨论。

前言

因为打算用java编写异步通信的server和client程序,笔者便学习使用java.nio开发包,其间遇到一些问题,上网却发现网上对它的应用描述的不是很多。所以,笔者不惜班门弄斧,做些简单的讨论,以便大家更进一步的讨论。

对相关类的简单介绍

java.nio.*, 据说它提供了一些更加底层的一些功能,如:类似windows环境下的AsyncSocket类的异步操作的功能,能显著降低server端程序的线程管理开销。

因为大多数应用是建立在TCP之上,所以在此只说说SocketChannel,ServerSocketChannel,Selector 和ByteBuffer这几个类.前三个最终都源自channel类。而channel 类,可以理解为在具体I/O或文件对象之上抽象的一个操作对象,我们通过操作channel的读写达到对其对应的文件或I/O对象(包括socket)读写的目的。读写的内容在内存中放在ByteBuffer类提供的缓冲区。总而言之,channel作为一个桥梁,连接了I/O对象和内存中的 ByteBuffer,实现了I/O的更高效的存取。

一个基于TCP的服务器端程序,必然有个侦听端和若干个通信端,它们在nio中由对应的ServerSocketChannel 和SocketChannel类来实现。为了达到异步I/O操作的目的,需要Selector类,它能检测到I/O对象的状态。

SocketChannel类是抽象类,通过调用它的静态函数open(),可生成一个SocketChannel对象,该对象对应一个java.net.Socket,可通过SocketChannel.socket()获得,而其对应的Socket也可通过调用函数getChannel()得到已建立的相应SocketChannel。

SocketChannel与它的socket是一一对应的。SocketChannel的操作与Socket也很相似。

ServerSocketChannel也是通过调用它的静态函数open()生成的,只是它不能直接调用bind()函数来绑定一个地址,需要它对应的ServerSocket来完成绑定工作,一般可按如下步骤做:

  1. ServerSocketChannel ssc = new ServerSocketChannel.open(); 
  2. ssc.socket().bind(InetSocketAddress(host,port)); 

罗嗦了半天,还是看看最简单的C/S实现吧,服务器提供了基本的回射(echo)功能,其中提供了较详细的注释。

源码分析

1.服务器端:

  1. //////////////////////// 
  2. //AsyncServer.java 
  3. // by zztudou@163.com 
  4. //////////////////////// 
  5. import java.nio.channels.SocketChannel; 
  6. import java.nio.channels.ServerSocketChannel; 
  7. import java.nio.channels.SelectionKey; 
  8. import java.nio.channels.Selector; 
  9. import java.nio.ByteBuffer; 
  10. import java.nio.channels.SelectableChannel; 
  11. import java.nio.channels.spi.SelectorProvider; 
  12. import java.net.ServerSocket; 
  13. import java.net.Socket; 
  14. import java.net.InetSocketAddress; 
  15. import java.net.SocketAddress; 
  16. import java.util.Iterator; 
  17. import java.util.LinkedList; 
  18. import java.io.IOException; 
  19.  
  20. class AsyncServer implements Runnable{  
  21. private ByteBuffer r_buff = ByteBuffer.allocate(1024); 
  22. private ByteBuffer w_buff = ByteBuffer.allocate(1024); 
  23. private static int port = 8848
  24.  
  25. public AsyncServer(){ 
  26. new Thread(this).start(); 
  27.  
  28. public void run(){  
  29. try
  30. //生成一个侦听端 
  31. ServerSocketChannel ssc = ServerSocketChannel.open(); 
  32. //将侦听端设为异步方式 
  33. ssc.configureBlocking(false); 
  34. //生成一个信号监视器 
  35. Selector s = Selector.open(); 
  36. //侦听端绑定到一个端口 
  37. ssc.socket().bind(new InetSocketAddress(port)); 
  38. //设置侦听端所选的异步信号OP_ACCEPT 
  39. ssc.register(s,SelectionKey.OP_ACCEPT); 
  40.  
  41. System.out.println("echo server has been set up ......"); 
  42.  
  43. while(true){ 
  44. int n = s.select(); 
  45. if (n == 0) {//没有指定的I/O事件发生 
  46. continue
  47. }  
  48. Iterator it = s.selectedKeys().iterator();  
  49. while (it.hasNext()) { 
  50. SelectionKey key = (SelectionKey) it.next(); 
  51. if (key.isAcceptable()) {//侦听端信号触发 
  52. ServerSocketChannel server = (ServerSocketChannel) key.channel(); 
  53. //接受一个新的连接 
  54. SocketChannel sc = server.accept(); 
  55. sc.configureBlocking(false); 
  56. //设置该socket的异步信号OP_READ:当socket可读时, 
  57.  
  58. //触发函数DealwithData(); 
  59. sc.register(s,SelectionKey.OP_READ); 
  60. }  
  61. if (key.isReadable()) {//某socket可读信号 
  62. DealwithData(key); 
  63. }  
  64. it.remove(); 
  65. catch(Exception e){ 
  66. e.printStackTrace();  
  67.  
  68. public void DealwithData(SelectionKey key) throws IOException{ 
  69. int count; 
  70. //由key获取指定socketchannel的引用 
  71. SocketChannel sc = (SocketChannel)key.channel(); 
  72. r_buff.clear(); 
  73. //读取数据到r_buff 
  74. while((count = sc.read(r_buff))> 0
  75. //确保r_buff可读 
  76. r_buff.flip(); 
  77.  
  78. w_buff.clear(); 
  79. //将r_buff内容拷入w_buff  
  80. w_buff.put(r_buff); 
  81. w_buff.flip(); 
  82. //将数据返回给客户端 
  83. EchoToClient(sc); 
  84.  
  85. w_buff.clear(); 
  86. r_buff.clear(); 
  87.  
  88. public void EchoToClient(SocketChannel sc) throws IOException{ 
  89. while(w_buff.hasRemaining()) 
  90. sc.write(w_buff); 
  91.  
  92. public static void main(String args[]){ 
  93. if(args.length > 0){ 
  94. port = Integer.parseInt(args[0]); 
  95. new AsyncServer(); 

在当前目录下运行:

javac AsynServer.java

后,若无编译出错,接下来可运行:

java AsynServer 或 java AsynServer ×××(端口号)

上述服务程序在运行时,可指定其侦听端口,否则程序会取8848为默认端口。

2.客户端的简单示例:

  1. //////////////////////// 
  2. //AsyncClient.java 
  3. // by zztudou@163.com 
  4. //////////////////////// 
  5. import java.nio.channels.SocketChannel; 
  6. import java.net.InetSocketAddress; 
  7. import java.nio.ByteBuffer; 
  8. import java.nio.channels.Selector; 
  9. import java.nio.channels.SelectionKey; 
  10.  
  11. import java.io.IOException; 
  12. import java.io.BufferedReader; 
  13. import java.io.InputStreamReader; 
  14.  
  15. class AsyncClient{ 
  16. private SocketChannel sc; 
  17. private final int MAX_LENGTH = 1024
  18. private ByteBuffer r_buff = ByteBuffer.allocate(MAX_LENGTH); 
  19. private ByteBuffer w_buff = ByteBuffer.allocate(MAX_LENGTH); 
  20. private static String host ; 
  21. private static int port = 8848
  22.  
  23. public AsyncClient(){ 
  24. try { 
  25. InetSocketAddress addr = new InetSocketAddress(host,port); 
  26. //生成一个socketchannel 
  27. sc = SocketChannel.open(); 
  28.  
  29. //连接到server 
  30. sc.connect(addr); 
  31. while(!sc.finishConnect()) 
  32. ;  
  33. System.out.println("connection has been established!..."); 
  34.  
  35. while(true){ 
  36. //回射消息 
  37. String echo; 
  38. try
  39. System.err.println("Enter msg you'd like to send: "); 
  40. BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 
  41. //输入回射消息 
  42. echo = br.readLine(); 
  43.  
  44. //把回射消息放入w_buff中  
  45. w_buff.clear(); 
  46. w_buff.put(echo.getBytes()); 
  47. w_buff.flip(); 
  48. }catch(IOException ioe){ 
  49. System.err.println("sth. is wrong with br.readline() "); 
  50. }  
  51.  
  52. //发送消息 
  53. while(w_buff.hasRemaining()) 
  54. sc.write(w_buff); 
  55. w_buff.clear();  
  56.  
  57. //进入接收状态 
  58. Rec(); 
  59. //间隔1秒 
  60. Thread.currentThread().sleep(1000); 
  61. }  
  62. }catch(IOException ioe){ 
  63. ioe.printStackTrace(); 
  64. catch(InterruptedException ie){ 
  65. ie.printStackTrace(); 
  66. }  
  67. //////////// 
  68. //读取server端发回的数据,并显示 
  69. public void Rec() throws IOException{ 
  70. int count; 
  71. r_buff.clear();  
  72. count=sc.read(r_buff); 
  73. r_buff.flip();  
  74. byte[] temp = new byte[r_buff.limit()]; 
  75. r_buff.get(temp); 
  76. System.out.println("reply is " + count +" long, and content is: " + new String(temp)); 
  77.  
  78. public static void main(String args[]){ 
  79. if(args.length < 1){//输入需有主机名或IP地址 
  80. try
  81. System.err.println("Enter host name: "); 
  82. BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 
  83. host = br.readLine(); 
  84. }catch(IOException ioe){ 
  85. System.err.println("sth. is wrong with br.readline() "); 
  86. else if(args.length == 1){ 
  87. host = args[0]; 
  88. else if(args.length > 1){ 
  89. host = args[0]; 
  90. port = Integer.parseInt(args[1]); 
  91.  
  92. new AsyncClient(); 

在当前目录下运行:

javac AsynClient.java

后,若无编译出错,确认AsyncServer已经运行的情况下,接下来可运行:

java AsynClient hostname 或 java AsynClient hostname ×××(端口号)

并按提示进行操作即可。

总 结

总的来说,用nio进行网络编程还是很有新意的,服务器端软件能在一个线程中维护与众多客户端的通信连接。笔者在本文中试图用一个典型的回射例子说明如何用nio建立最基本的C/S应用。希望大家能试着用用它。
另外,笔者在实践中也发现nio在应用中存在的一些难题,比如如何应用SocketChannel的继承类,以及如何在socketchannel之上应用SSL(Secure Socket Layer)等等,因而希望这篇文章只是抛砖引玉,引起大家对nio作进一步的讨论。

原文链接:http://lrtlcg.iteye.com/blog/844357

【编辑推荐】

  1. Java NIO的wakeup剖析
  2. Java NIO类库关系图解
  3. 浅析Tomcat NIO 配置
  4. Java NIO API详解
  5. Java NIO基本使用实例
责任编辑:林师授 来源: lrtlcg的博客
相关推荐

2011-12-08 10:24:53

JavaNIO

2011-12-15 09:40:06

Javanio

2010-10-27 10:19:33

UnicodeJava

2011-12-13 17:31:07

2021-06-11 17:26:06

代码Java网络编程

2024-05-17 09:49:44

RustCursive界面

2023-07-28 08:23:05

选择器Java NIO

2011-06-13 12:11:06

javasocket

2011-06-13 11:23:33

javasocket

2011-12-15 10:56:55

JavaNIO

2012-10-23 09:47:01

MapReduceJavaHadoop

2022-02-14 15:07:48

进程FileChanne线程

2019-09-05 14:21:22

JavaNIOBIO

2011-06-17 17:27:29

Objective-CCocoa苹果

2022-12-08 09:10:11

I/O模型Java

2011-04-06 11:36:28

MRTG流量

2010-12-24 10:25:20

SCP 命令

2011-12-07 14:57:44

JavaNIO

2011-12-15 09:55:47

javanio

2011-12-15 11:19:08

JavaNIO
点赞
收藏

51CTO技术栈公众号