最近做的項目中使用到了一些基於java的socket長連接的一些功能,用來穿透有關行業的網閘。用到了也就學習了一下,下面是對學習內容的一個筆記,記錄一下也希望有興趣的同學可以參考一下,加深對javasocket的理解。
我們知道在java5之前 我們使用的IO是BIO java5之后是NIO 最新的AIO
BIO 是阻塞IO NIO 是同步非阻塞IO AIO 是異步非堵塞IO
現在我使用的更多的是NIO,但是我們自己去實現一個NIO做的沒有類似的框架給我們做的好,比如MINA NETTY等等,他們是使用NIO來實現的IO操作的框架,關於NETTY和MINA 有空的時候我們也去看看他的源碼,在源碼中是如何使用NIO中諸如selecter channel bytebuffer selectionKey等等這些創建一個NIO必備的東西的。下面只是對原生態的NIO的做一個簡單的實現,知道NIO的原理的什么。首先解釋一些NIO中涉及的這幾個詞語都是什么意思。
selector 是一個選擇器,這個玩意是用來管理具體的通信管道的,說到管道,我好想忘了說為什么要使用NIO ,是這樣的我們知道IO操作對資源CPU是有很大的消耗的,比如我們NIO之前的BIO 在建立連接的時候必須要按照TCP的三次握手才能建立連接,我們知道從網絡層來說的話建立三次握手是很耗時間的。所以NIO在處理創建連接的這個握手上面做了一個優化,什么優化呢,就是連接的創建次數越少越好,這時候就出現了一種叫做管道的概念和技術,管道是對一次連接的一個抽象,通道這個管道就不需要每次都去建立三次握手的連接。哪管道又是誰來管理的呢,這個就是我們說的selector這個東東了,這個東東類似於一個管家,就是這個關鍵管理所有的通信管道,比如那個管道里面接收到數據了,那個管道中可以寫入數據了,等等 都由這個selector來管理調度。說到這里我們知道管道是有socketServerChannel和socketCleintChannel客戶端管道的,這些管道通通都是由selector來進行調度管理的。說到管道中的數據我們就想到了緩存,是的 管道中的數據是通過bytebuffer來進行緩存的。最后看一個叫Selectionkey的東東,這個東東是用來判斷每一個IO事件的狀態的,比如是否就緒等等,他主要由如下的幾種狀態①key.isAccptable:是否可以接受客戶端的連接。②Key.isconnctionable:是否可以連接服務端。③Key.isreadable():緩沖區是否可讀。④Key.iswriteable():緩沖區是否可寫。
這幾個之間的使用關系是這樣的,首先我們會去創建一個通道channel,然后通道要綁定我們的selector,channel.regist(Selector,Selectionkey.OP_Write);這樣才能去管理。當然綁定的時候要去注冊是什么狀態的綁定。上面的selcector需要我們打開,類似於打開一個連接Selectionkey keys= Selector.selectedkeys();這個時候selector就開始管理了,等服務端或者客戶端的channel有可寫,可讀,可連通等等事件的時候,selector就會捕獲到,並將管道中的數據寫到bytebuffer中,等待具體的業務去處理。
好了我上代碼看一下吧
下面是服務端的:
package cn.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
/**
* NIO服務端
* @author 小路
*/
public class NIOServer {
//通道管理器
private Selector selector;
/**
* 獲得一個ServerSocket通道,並對該通道做一些初始化的工作
* @param port 綁定的端口號
* @throws IOException
*/
public void initServer(int port) throws IOException {
// 獲得一個ServerSocket通道
ServerSocketChannel serverChannel = ServerSocketChannel.open();
// 設置通道為非阻塞
serverChannel.configureBlocking(false);
// 將該通道對應的ServerSocket綁定到port端口
serverChannel.socket().bind(new InetSocketAddress(port));
// 獲得一個通道管理器
this.selector = Selector.open();
//將通道管理器和該通道綁定,並為該通道注冊SelectionKey.OP_ACCEPT事件,注冊該事件后,
//當該事件到達時,selector.select()會返回,如果該事件沒到達selector.select()會一直阻塞。
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
}
/**
* 采用輪詢的方式監聽selector上是否有需要處理的事件,如果有,則進行處理
* @throws IOException
*/
@SuppressWarnings("unchecked")
public void listen() throws IOException {
System.out.println("服務端啟動成功!");
// 輪詢訪問selector
while (true) {
//當注冊的事件到達時,方法返回;否則,該方法會一直阻塞
selector.select();
// 獲得selector中選中的項的迭代器,選中的項為注冊的事件
Iterator ite = this.selector.selectedKeys().iterator();
while (ite.hasNext()) {
SelectionKey key = (SelectionKey) ite.next();
// 刪除已選的key,以防重復處理
ite.remove();
// 客戶端請求連接事件
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key
.channel();
// 獲得和客戶端連接的通道
SocketChannel channel = server.accept();
// 設置成非阻塞
channel.configureBlocking(false);
//在這里可以給客戶端發送信息哦
channel.write(ByteBuffer.wrap(new String("向客戶端發送了一條信息").getBytes()));
//在和客戶端連接成功之后,為了可以接收到客戶端的信息,需要給通道設置讀的權限。
channel.register(this.selector, SelectionKey.OP_READ);
// 獲得了可讀的事件
} else if (key.isReadable()) {
read(key);
}
}
}
}
/**
* 處理讀取客戶端發來的信息 的事件
* @param key
* @throws IOException
*/
public void read(SelectionKey key) throws IOException{
// 服務器可讀取消息:得到事件發生的Socket通道
SocketChannel channel = (SocketChannel) key.channel();
// 創建讀取的緩沖區
ByteBuffer buffer = ByteBuffer.allocate(10);
channel.read(buffer);
byte[] data = buffer.array();
String msg = new String(data).trim();
System.out.println("服務端收到信息:"+msg);
ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
channel.write(outBuffer);// 將消息回送給客戶端
}
/**
* 啟動服務端測試
* @throws IOException
*/
public static void main(String[] args) throws IOException {
NIOServer server = new NIOServer();
server.initServer(8000);
server.listen();
}
}
下面是客戶端的:
package cn.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
/**
* NIO客戶端
* @author 小路
*/
public class NIOClient {
//通道管理器
private Selector selector;
/**
* 獲得一個Socket通道,並對該通道做一些初始化的工作
* @param ip 連接的服務器的ip
* @param port 連接的服務器的端口號
* @throws IOException
*/
public void initClient(String ip,int port) throws IOException {
// 獲得一個Socket通道
SocketChannel channel = SocketChannel.open();
// 設置通道為非阻塞
channel.configureBlocking(false);
// 獲得一個通道管理器
this.selector = Selector.open();
// 客戶端連接服務器,其實方法執行並沒有實現連接,需要在listen()方法中調
//用channel.finishConnect();才能完成連接
channel.connect(new InetSocketAddress(ip,port));
//將通道管理器和該通道綁定,並為該通道注冊SelectionKey.OP_CONNECT事件。
channel.register(selector, SelectionKey.OP_CONNECT);
}
/**
* 采用輪詢的方式監聽selector上是否有需要處理的事件,如果有,則進行處理
* @throws IOException
*/
@SuppressWarnings("unchecked")
public void listen() throws IOException {
// 輪詢訪問selector
while (true) {
selector.select();
// 獲得selector中選中的項的迭代器
Iterator ite = this.selector.selectedKeys().iterator();
while (ite.hasNext()) {
SelectionKey key = (SelectionKey) ite.next();
// 刪除已選的key,以防重復處理
ite.remove();
// 連接事件發生
if (key.isConnectable()) {
SocketChannel channel = (SocketChannel) key
.channel();
// 如果正在連接,則完成連接
if(channel.isConnectionPending()){
channel.finishConnect();
}
// 設置成非阻塞
channel.configureBlocking(false);
//在這里可以給服務端發送信息哦
channel.write(ByteBuffer.wrap(new String("向服務端發送了一條信息").getBytes()));
//在和服務端連接成功之后,為了可以接收到服務端的信息,需要給通道設置讀的權限。
channel.register(this.selector, SelectionKey.OP_READ);
// 獲得了可讀的事件
} else if (key.isReadable()) {
read(key);
}
}
}
}
/**
* 處理讀取服務端發來的信息 的事件
* @param key
* @throws IOException
*/
public void read(SelectionKey key) throws IOException{
//和服務端的read方法一樣
}
/**
* 啟動客戶端測試
* @throws IOException
*/
public static void main(String[] args) throws IOException {
NIOClient client = new NIOClient();
client.initClient("localhost",8000);
client.listen();
}
}
其實我們可以看到其實服務端和客戶端在實現方式上大概是差不多的。上面的例子中我們再服務端可客戶端各建立了一個服務端的通道和客戶端的通道,同時設置兩個通道都是非阻塞的。然后將通道綁定到selector上,設置監聽的時間是接收到數據。
關於NIO和BIO的一些區別,其實是很明顯的,或者說是顯而易見的,我們說,NIO的機制可以概括為 增加一個專門用來處理IO的線程,采用輪詢的方式來監聽IO事件,等有IO事件相應的時候去處理,沒有的情況下就一直輪詢堵塞在哪里不做任何處理。關於selector管理的線程之間的通信 通過 wait,notify 等方式通訊。保證每次上下文切換都是有意義的。減少無謂的線程切換。
先寫到這里吧,打算今天晚上將NIO有關的東西都寫完,但是現在要下班了,下班回去后再繼續寫。bruce at beijing