網絡應用程序一個很重要的工作是傳輸數據。傳輸數據的過程不一樣取決於使用哪種“交通工具“,但是傳輸的方式都是一樣的:都是以字節碼傳輸。JAVA開發網絡程序傳輸數據的過程和方式是被抽象了的,我們不需要關注底層接口,只需要使用Java API 或其他網絡框架就能達到數據傳輸的目的。發送數據和接收數據都是字節碼。
Socket網絡編程我就不多啰嗦了,這裡我通過兩個簡單的示例比較下阻塞式IO(OIO)和非阻塞式IO(NIO)。
OIO中,每個線程只能處理一個channel,該線程和該channel綁定。也就是同步的,客戶端在發送請求後,必須得在服務端有回應後才發送下一個請求。所以這個時候的所有請求將會在服務端得到同步。
NIO中,每個線程可以處理多個channel。也就是異步的,客戶端在發送請求後,不必等待服務端的回應就可以發送下一個請求,這樣對於所有的請求動作來說將會在服務端得到異步,這條請求的鏈路就象是一個請求隊列,所有的動作在這裡不會得到同步的。
你可能使用過Java提供的網絡接口工作過,遇到過想從阻塞傳輸切換到非阻塞傳輸的情況,這種情況是比較困難的,因為阻塞IO和非阻塞IO使用的API有很大的差異。當我們想切換傳輸方式時要花很大的精力和時間來重構代碼。
先看一個傳統的阻塞IO傳輸實現的Socket服務端:
/**
* 傳統阻塞IO(OIO),原始socket
*
* <p>Title: PlainOioServer</p>
* @author wyx
* @date 2016-6-15 下午1:36:04
*/
public class PlainOioServer {
public void server(int port) throws Exception{
// bind server to port
final ServerSocket socket = new ServerSocket(port);
while(true){
// accept connection
final Socket clientSocket = socket.accept();
System.out.println("Accepted connection form " + clientSocket);
// create new thread to handle connection
new Thread(new Runnable() {
@Override
public void run() {
OutputStream out;
try {
out = clientSocket.getOutputStream();
// write message to connected client
out.write("Hi!\r\n".getBytes(Charset.forName("UTF-8")));
out.flush();
// close connection once message written and flushed
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start(); // start thread to begin handling
}
}
}
上面的方式很簡潔,但是這種阻塞模式在大連接的情況就會有嚴重的問題,如:客戶端連接超時,服務器響應嚴重延遲等。為了解決這一問題,我們可以使用異步網絡處理所有的並發連接,但問題在於NIO和OIO的API是完全不同的,所以一個用OIO開發的網絡應用程序想要使用NIO重構代碼幾乎是重新開發。
下面代碼是使用Java NIO實現的例子:
/**
* 傳統非阻塞式IO(NIO),原始socket
*
* <p>Title: PlainNioServer</p>
* @author wyx
* @date 2016-6-15 下午1:46:09
*/
public class PlainNioServer {
public void server(int port) throws Exception{
System.out.println("Listening for connections on port " + port);
// open selector that handles channels
Selector selector = Selector.open();
// open ServerSocketChannel
ServerSocketChannel serverChannel = ServerSocketChannel.open();
// get ServerSocket
ServerSocket serverSocket = serverChannel.socket();
// bind server to port
serverSocket.bind(new InetSocketAddress(port));
// set to non-blocking
serverChannel.configureBlocking(false);
// register ServerSocket to selector and specify than it is interested in new accepted clients
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes());
while(true){
// Wait for new events that are ready for process. this will block until something happens
int n = selector.select();
if(n > 0){
// Obtain all SelectionKey instances that received enents
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while(iter.hasNext()){
SelectionKey key = iter.next();
iter.remove();
//Check if event was because new client ready to get accepted
if(key.isAcceptable()){
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
System.out.println("Accepted connection from " + client);
client.configureBlocking(false);
// Accept client and register it to seletor
client.register(selector, SelectionKey.OP_WRITE, msg.duplicate());
}
// Check if event was because socket is ready to write data
if(key.isWritable()){
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buff = (ByteBuffer) key.attachment();
// Write date to connected client
while(buff.hasRemaining()){
if(client.write(buff) == 0){
break;
}
}
client.close();
}
}
}
}
}
}
如你所見,即使它們實現的功能時候一樣的,但是代碼完全不同。根據不同需求選用不同的實現方式,當然,也可以直接選擇流行的網絡傳輸框架實現,如:Netty。以便於後期維護。