網路程式設計Socket的阻塞和非阻塞IO
阿新 • • 發佈:2019-01-08
網路應用程式一個很重要的工作是傳輸資料。傳輸資料的過程不一樣取決於使用哪種“交通工具“,但是傳輸的方式都是一樣的:都是以位元組碼傳輸。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。以便於後期維護。
本文永久更新連結地址:http://www.linuxidc.com/Linux/2016-07/133496.htm
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。以便於後期維護。
本文永久更新連結地址:http://www.linuxidc.com/Linux/2016-07/133496.htm