在前面的章節里,我們討論了Java NIO的基本概念,在這一節里,我們將結合具體的Java Socket編程,討論使用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。
【程序源代碼】
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.Selecti;
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, Selecti.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 // 處理每一個Selecti
79 while (it.hasNext()) {
80 Selecti key = (Selecti) it.next();
81
82 // 判斷是否有新的連接到達
83 if (key.isAcceptable()) {
84 //返回Selecti的ServerSocketChannel
85 ServerSocketChannel server =
(ServerSocketChannel) key.channel();
86 SocketChannel channel = server.accept();
87
88 registerChannel (selector, channel,
89 Selecti.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 (Selecti 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]___FCKpd___0quot;.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]
___FCKpd___0quot;.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的客戶/服務器程序。兩者的模式是很相
似的,其實這也就是編寫客戶/服務器程序的一般模式。