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)