1. 程式人生 > >java NIO非阻塞方式的Socket程式設計

java NIO非阻塞方式的Socket程式設計

1.非阻塞方式的Socket程式設計:

傳統阻塞方式的Socket程式設計,在讀取或者寫入資料時,TCP程式會阻塞直到客戶端和服務端成功連線,UDP程式會阻塞直到讀取到資料或寫入資料。阻塞方式會影響程式效能,JDK5之後的NIO引入了非阻塞方式的Socket程式設計,非阻塞方式的Socket程式設計主要是使用Socket通道和Selector通道選擇器,將Socket通道註冊到通道選擇器上,通過通道選擇器選擇通道已經準備好的事件的進行相應操作。

NIO Socket程式設計中有一個主要的類Selector,這個類似一個觀察者,只要我們把需要探知的套接字通道socketchannel註冊到Selector,程式不用阻塞等待,可以並行做別的事情,當有事件發生時,Selector會通知程式,傳回一組SelectionKey,程式讀取這些Key,就會獲得註冊過的socketchannel,然後,從這個Channel中讀取和處理資料。

Selector內部原理實際是在做一個對所註冊的channel的輪詢訪問,不斷的輪詢(目前就這一個演算法),一旦輪詢到一個channel有所註冊的事情發生,比如資料來了,他就會站起來報告,交出一把鑰匙,讓我們通過這把鑰匙來讀取這個channel的內容。

2.使用NIO非阻塞方式Socket實現服務端和客戶端程式:

通過下面一個簡單的客戶端/服務端程式說明一下NIO socket的基本API和步驟。

import java.net.*;
import java.util.*;
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;


public class NIOSocketServer{
public static final int PORT = 8080;
public static void main(String[] args)throws IOException{
//NIO的通道channel中內容讀取到位元組緩衝區ByteBuffer時是位元組方式儲存的,
//對於以字元方式讀取和處理的資料必須要進行字符集編碼和解碼
String encoding = System.getProperty(“file.encoding”);
//載入位元組編碼集
Charset cs = Charset.forName(encoding);
//分配兩個位元組大小的位元組緩衝區
ByteBuffer buffer = ByteBuffer.allocate(16);
SocketChannel ch = null;
//開啟服務端的套接字通道
ServerSocketChannel ssc = ServerSocketChannel.open();
//開啟通道選擇器
Selector sel = Selector.open();
try{
//將服務端套接字通道連線方式調整為非阻塞模式
ssc.configureBlocking(false);
//將服務端套接字通道繫結到本機服務端埠
ssc.socket().bind(new InetSocketAddress(PORT));
//將服務端套接字通道OP_ACCEP事件註冊到通道選擇器上
SelectionKey key = ssc.register(sel, SelectionKey.OP_ACCEPT);
System.out.println(“Server on port:” + PORT);
while(true){
//通道選擇器開始輪詢通道事件
sel.select();
Iterator it = sel.selectedKeys().iterator();
While(it.hasNext()){
//獲取通道選擇器事件鍵
SelectionKey skey = (SelectionKey)it.next();
it.remove();
//服務端套接字通道傳送客戶端連線事件,客戶端套接字通道尚未連線
if(skey.isAcceptable()){
//獲取服務端套接字通道上連線的客戶端套接字通道
ch = ssc.accept();
System.out.println(“Accepted connection from:” + ch.socket());
//將客戶端套接字通過連線模式調整為非阻塞模式
ch.configureBlocking(false);
//將客戶端套接字通道OP_READ事件註冊到通道選擇器上
ch.register(sel, SelectionKey.OP_READ);
}
//客戶端套接字通道已經連線
else{
//獲取建立此通道選擇器事件鍵的套接字通道
ch = (SocketChannel)skey.channel();
//將客戶端套接字通道資料讀取到位元組緩衝區中
ch.read(buffer);
//使用字符集解碼位元組緩衝區資料
CharBuffer cb = cs.decode((ByteBuffer)buffer.flip());
String response = cb.toString();
System.out.println(“Echoing:” + response) ;
//重繞位元組緩衝區,繼續讀取客戶端套接字通道資料
ch.write((ByteBuffer)buffer.rewind());
if(response.indexOf(“END”) != -1) ch.close();
buffer.clear();
}
}
}
}finally{
if(ch != null) ch.close();
ssc.close();
sel.close();
}
}
}

import java.net.*;
import java.util.*;
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;


public class NIOSocketClient{
private static final int CLIENT_PORT = 10200;
public static void main(String[] args) throws IOException{
SocketChannel sc = SocketChannel.open();
Selector sel = Selector.open();
try{
sc.configureBlocking(false);
sc.socket.bind(new InetSocketAddress(CLIENT_PORT));
sc.register(sel, SelectionKey.OP_READ | SelectionKey.OP_WRITE
| SelectionKey.OP_CONNECT);
int i = 0;
boolean written = false;
boolean done = false;
String encoding = System.getProperty(“file.encoding”);
Charset cs = Charset.forName(encoding);
ByteBuffer buf = ByteBuffer.allocate(16);
while(!done){
sel.select();
Iterator it = sel.selectedKeys().iterator();
while(it.hasNext()){
SelectionKey key = (SelectionKey)it.next();
It.remove();
//獲取建立通道選擇器事件鍵的套接字通道
sc = (SocketChannel)key.channel();
//當前通道選擇器產生連線已經準備就緒事件,並且客戶端套接字
//通道尚未連線到服務端套接字通道
if(key.isConnectable() && !sc.isConnected()){
InetAddress addr = InetAddress.getByName(null);
//客戶端套接字通道向服務端套接字通道發起非阻塞連線
boolean success = sc.connect(new InetSocketAddress(
addr, NIOSocketServer.PORT));
//如果客戶端沒有立即連線到服務端,則客戶端完成非立即連線操作
if(!success) sc.finishConnect();
}
//如果通道選擇器產生讀取操作已準備好事件,且已經向通道寫入資料
if(key.isReadable() && written){
if(sc.read((ByteBuffer)buf.clear()) > 0){
written = false;
//從套接字通道中讀取資料
String response = cs.decode((ByteBuffer)buf.flip()).toString();
System.out.println(response);
if(response.indexOf(“END”) != -1) done = true;
}
}
//如果通道選擇器產生寫入操作已準備好事件,並且尚未想通道寫入資料
if(key.isWritable() && !written){
//向套接字通道中寫入資料
if(i < 10) sc.write(ByteBuffer.wrap(new String(“howdy ” + i +
 ‘\n’).getBytes()));
else if(i == 10)sc.write(ByteBuffer.wrap(newString(“END”).
getBytes()));
written = true;
i++;
}
}
}
}finally{
sc.close();
sel.close();
}
}
}