1.非阻塞方式的Socket編程:

傳統阻塞方式的Socket編程,在讀取或者寫入數據時,TCP程序會阻塞直到客戶端和服務端成功連接,UDP程序會阻塞直到讀取到數據或寫入數據。阻塞方式會影響程序性能,JDK5之后的NIO引入了非阻塞方式的Socket編程,非阻塞方式的Socket編程主要是使用Socket通道和Selector通道選擇器,將Socket通道注冊到通道選擇器上,通過通道選擇器選擇通道已經准備好的事件的進行相應操作。

NIO socket簡單例子如下:

import java.net.*;
import java.nio.channels.*;
import java.util.*;
import java.io.*;
public class NIOSocket{
private static final int CLINET_PORT = 10200;
private static final int SEVER_PORT = 10201;
//面向流的連接套接字的可選擇通道
private SocketChannel ch;
//通道選擇器
private Selector sel;
public static void main(String[] args) throws IOException{
//打開套接字通道
ch = SocketChannel.open();
//打開一個選擇器
sel = Selector.open();
try{
//獲取與套接字通道關聯的套接字,並將該套接字綁定到本機指定端口
ch.socket().bind(new InetSocketAddress(CLINET_PORT));
//調整此通道為非阻塞模式
ch.configureBlocking(false);
//為通道選擇器注冊通道,並指定操作的選擇鍵集
ch.register(sel, SelectionKey.OP_READ | SelectionKey.OP_WRITE |
SelectionKey.OP_CONNECT);
//選擇通道上注冊的事件,其相應通道已為I/O操作准備就緒
sel.select();
//返回選擇器的已選擇鍵集
Iterator it = sel.selectedKeys().iterator();
while(it.hasNext()){
//獲取通道的選擇器的鍵
SelectionKey key = (SelectionKey)it.next();
it.remove();
//如果該通道已經准備好套接字連接
if(key.isConnectable()){
InetAddress addr = InetAddress.getLocalHost();
System.out.println(“Connect will not block”);
//調用此方法發起一個非阻塞的連接操作,如果立即建立連接,則此方法//返回true.否則返回false,且必須在以后使用finishConnect()完成連接操作
//此處建立和服務端的Socket連接
if(!ch.connect(new InetSocketAddress(addr, SEVER_PORT))){
//完成非立即連接操作
ch.finishConnect();
}
}
//此通道已准備好進行讀取
if(key.isReadable()){
System.out.println(“Read will not block”);
}
//此通道已准備好進行寫入
if(key.isWritable()){
System.out.println(“Write will not block”);
}
}
} finally{
ch.close();
sel.close();
}
}
}

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

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

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

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

(1).服務端程序:

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();
}
}
}

(2).客戶端程序:

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();
}
}
}