使用Java NIO提高服务端程序的性能

Java NIO增加了新的SocketChannel、ServerSocketChannel等类来提供对构建高性能的服务端程序的支持。 SocketChannel、ServerSocketChannel能够在非阻塞的模式下工作,它们都是selectable的类。在构建服务器或者中间件时,推荐使用Java NIO。

在传统的网络编程中,我们通常使用一个专用线程(Thread)来处理一个Socket连接,通过使用NIO,一个或者很少几个Socket线程就可以处理成千上万个活动的Socket连接。

通常情况下,通过ServerSocketChannel.open()获得一个ServerSocketChannel的实例,通过SocketChannel.open或者serverSocketChannel.accept()获得一个SocketChannel实例。要使ServerSocketChannel或者SocketChannel在非阻塞的模式下操作,可以调用
serverSocketChannel.configureBlocking (false);
或者
socketChannel.configureBlocking (false);

语句来达到目的。通常情况下,服务端可以使用非阻塞的ServerSocketChannel,这样,服务端的程序就可以更容易地同时处理多个socket线程。

下面我们来看一个综合例子,这个例子使用了ServerSocketChannel、SocketChannel开发了一个非阻塞的、能处理多线程的Echo服务端程序,见示例12-14。
【程序源代码】

1 // ==================== Program Discription =====================
2 // 程序名称:示例12-14 : SocketChannelDemo.java
3 // 程序目的:学习Java NIO#SocketChannel
4 // ==============================================================
5 
6 
7 import java.nio.ByteBuffer;
8 import java.nio.channels.ServerSocketChannel;
9 import java.nio.channels.SocketChannel;
10 import java.nio.channels.Selector;
11 import java.nio.channels.SelectionKey;
12 import java.nio.channels.SelectableChannel;
13 
14 import java.net.Socket;
15 import java.net.ServerSocket;
16 import java.net.InetSocketAddress;
17 import java.util.Iterator;
18
19 public class SocketChannelDemo
 
20 
21 {
22  public static int PORT_NUMBER = 23;//监听端口
23  ServerSocketChannel serverChannel;
24  ServerSocket serverSocket ;
25  Selector selector ;
26  private ByteBuffer buffer = ByteBuffer.allocateDirect (1024);
27 
28  public static void main (String [] args)
29   throws Exception
30  {
31   SocketChannelDemo server=new SocketChannelDemo();
32   server.init(args);
33   server.startWork();
34  }
35 
36 
37  public void init (String [] argv)throws Exception
38  {
39   int port = PORT_NUMBER;
40 
41   if (argv.length > 0) { 
42    port = Integer.parseInt (argv [0]);
43   }
44 
45   System.out.println ("Listening on port " + port);
46 
47   // 分配一个ServerSocketChannel
48   serverChannel = ServerSocketChannel.open();
49   // 从ServerSocketChannel里获得一个对应的Socket
50   serverSocket = serverChannel.socket();
51   // 生成一个Selector
52   selector = Selector.open();
53 
54   // 把Socket绑定到端口上
55   serverSocket.bind (new InetSocketAddress (port));
56   //serverChannel为非bolck
57   serverChannel.configureBlocking (false);
58 
59   // 通过Selector注册ServerSocetChannel
60   serverChannel.register (selector, SelectionKey.OP_ACCEPT); 
61   
62  }
63 
64     public void startWork()throws Exception
 
65 
66     {
67      while (true) {
68    
69    int n = selector.select();//获得IO准备就绪的channel数量
70 
71    if (n == 0) {
72     continue; // 没有channel准备就绪,继续执行
73    }
74 
75    // 用一个iterator返回Selector的selectedkeys
76    Iterator it = selector.selectedKeys().iterator();
77 
78    // 处理每一个SelectionKey
79    while (it.hasNext()) {
80     SelectionKey key = (SelectionKey) it.next();
81
82     // 判断是否有新的连接到达
83 if (key.isAcceptable()) {
84           //返回SelectionKey的ServerSocketChannel
85      ServerSocketChannel server =
(ServerSocketChannel) key.channel();
86      SocketChannel channel = server.accept();
87 
88      registerChannel (selector, channel,
89       SelectionKey.OP_READ);
90 
91      doWork (channel);
92     }
93 
94     // 判断是否有数据在此channel里需要读取
95     if (key.isReadable()) {
96        
97      processData (key);
98 
99     }
100 
101     //删除 selectedkeys
102     it.remove();
103    }
104   }
105  }
106  protected void registerChannel (Selector selector,
107   SelectableChannel channel, int ops)
108   throws Exception
109  {
 
110   if (channel == null) {
111    return;  
112   }
113 
114  
115   channel.configureBlocking (false);
116 
117   channel.register (selector, ops);
118  }
119 
120     //处理接收的数据
121  protected void processData (SelectionKey key)
122   throws Exception
123  {
124   
125  
126   SocketChannel socketChannel = (SocketChannel) key.channel();
127   int count;
128 
129   buffer.clear();   // 清空buffer
130 
131   // 读取所有的数据
132   while ((count = socketChannel.read (buffer)) > 0) {
133    buffer.flip();  
134
135    // send the data, don't assume it goes all at once
136    while (buffer.hasRemaining())
137    {
138     //如果收到回车键,则在返回的字符前增加[echo]$字样
139     if(buffer.get()==(char)13)
140     {
141      buffer.clear();
142      buffer.put("[echo]$".getBytes());
143      buffer.flip();
144      
145     }
146     socketChannel.write (buffer);//在Socket里写数据
147    }
148   
149    buffer.clear();  // 清空buffer
150   }
151 
152   if (count < 0) {
153    // count<0,说明已经读取完毕
154    socketChannel.close();
 
155   }
156  }
157 
158 
159  private void doWork (SocketChannel channel)throws Exception
160  {
161   buffer.clear();
162   buffer.put ("
Hello,I am working,please input some thing,and i will echo to you!
[echo] 
$".getBytes());
163   buffer.flip();
164   channel.write (buffer);
165  }
166 
167 }



使用:运行此程序,然后在控制台输入命令telnet localhost 23。

【程序输出结果】如图12-1所示。






图12-1 输出结果



【程序注解】
关于程序的解释已经包含在程序里面了,在这里我们总结以下使用ServerSocket Channel开发服务端程序的过程:
(1)分配一个ServerSocketChannel。
(2)从ServerSocketChannel里获得一个对应的ServerSocket。
(3)生成一个Selector实例。
(4)把ServerSocket绑定到端口上。
(5)设置ServerSocketChannel为非block模式(可选)。
(6)在Selector里注册ServerSocetChannel。
(7)用一个无限循环语句始终查看Selector里是否有IO准备就绪的channel。如果有,就执行对应的处理,如果没有,继续循环。

小 结

在本章我们主要介绍了Java中的网络编程。Java一开始就是一种网络编程语言,到后来才应用到各个方面,所以在Java中进行网络编程远比在C/C++中方便。

我们介绍了几个在网络编程中很重要的类,如InetAddress、URL、URLConnection、Socket、 ServerSocket、DatagramSocket、DatagramPacket、MulticastSocket等。这些类包含了进行基本网络编程的所有内容。要熟练地应用这些类,关键还是要多多练习。

基于套接字的编程基本上是客户/服务器模式,我们具体介绍了编写这种模式的步骤。在实例方面,我们给出了一个基于TCP的套接字客户/服务器程序,与此相对应,还给出了基于UDP的客户/服务器程序。两者的模式是很相似的,其实这也就是编写客户/服务器程序的一般模式。 (T111)

你可能感兴趣的:(java,nio)