1. 程式人生 > >NIO程式設計之ServerSocketChannel用法詳解

NIO程式設計之ServerSocketChannel用法詳解

之前一直看不懂NIO中的ServerSocketChannel和SocketChannel的區別,看了這篇博文,感覺通俗易懂,於是決定分享一下。。。。

在用nio通訊的過程我用以下情景給你模擬:

  1. 學校(ServerSocketChannel) 2。 學校教務處(Selector) 3。 老師 (ServerSocket ) 4。 學生 (SocketChannel) 5。 員工號/學生號(SelectionKey)

學校相當於我們的網路應用程式,一旦學校啟動,學校就不停止,不斷執行,直到學期結束; 要啟動學校就要:

ServerSocketChannel ssc= ServerSocketChannel.open();//新建NIO通道 ssc.configureBlocking( false );//使通道為非阻塞

老師: 相當於服務端的Socket,一個老師對應多個學生,多個學生向老師請教,老師會一一做出回答。而學校要正常運營當然當不了老師,所以在開學之前,必須先聘請專業的老師來任教

ServerSocket ss = ssc.socket();//建立基於NIO通道的socket連線 
ss.bind(new InetSocketAddress("127.0.0.1",SERVERPORT));//新建socket通道的埠 

學校教務處: 老師都有了,但是需要有部門對老師和學生做統一的管理, 如果你去一個學校找一個人,實在是找不到,你可以告訴教務處,那個人是學生還是老師,是老師的話員工編號老師姓名的多少,是學生的話學號和姓名是多少,教務處就會找到告訴你他在哪裡。

//將NIO通道選繫結到擇器,當然繫結後分配的主鍵為skey 
SelectionKey skey = ssc.register( selector, SelectionKey.OP_ACCEPT ); 

ssc註冊了選擇器後,其下的老師ServerSocket就也入了員工冊了。所以老師的編號就是skey

學生(相當於Client): 學校、老師、教務處都有了,現在就可以招生了! 如果有學生來報名:

while(true){//除非學期結束,否則一直等待學生 
int num = selector.select();//獲取通道內是否有選擇器的關心事件, 意思是有多少學生報告 
if(num<1){continue; } 
Set selectedKeys = selector.selectedKeys();//獲取通道內關心事件的集合 ,這裡的集合就是老師和學生的編號集合,如果key是學生的,那就是老學生來問問題,如果key是老師的,那就是招生辦的老師帶著一個新生來註冊
Iterator it = selectedKeys.iterator(); 
while (it.hasNext()) {//遍歷每個key (學生key和老師key) 
....... 
} 
..... 
} 

既然有學生來報告,那有兩種可能,一種是招生老師帶著新生來註冊的,一種是老生來問問題的。 上面的while (it.hasNext()) 體可以這樣寫:

while (it.hasNext()) {//遍歷每個事件 
try{ 
SelectionKey key = (SelectionKey)it.next(); //先得到這個學生的編號key 
 
//判斷是新生報道還是老生問問題 
if ((key.readyOps() & SelectionKey.OP_ACCEPT) 
== SelectionKey.OP_ACCEPT) { 
//這是招生老師的Key說明是新生註冊,先找到招生老師,再由招生老師找到新生,就可以給新生註冊學號了 
ServerSocketChannel serverChanel = (ServerSocketChannel)key.channel(); //通過key把學校和老師找到了
//從serverSocketChannel中創建出與客戶端的連線socketChannel 有了老師才有學生,不可能我教計算機的,來一個想學李小龍的都讓他報名 
SocketChannel sc = serverChanel.accept(); //學生報名成功 
sc.configureBlocking( false ); 
// 把新連線註冊到選擇器,新生被接收後給註冊個新學號 
SelectionKey newKey = sc.register( selector, 
SelectionKey.OP_READ ); //註冊學號成功,並分配學生的許可權 
it.remove(); //新生註冊任務完成了,呵呵 
System.out.println( "Got connection from "+sc ); 
}else 
//讀客戶端資料的事件,此時有客戶端發資料過來,客戶端事件 這是老學生來問問題了。 
if((key.readyOps() & SelectionKey.OP_READ)== SelectionKey.OP_READ){ 
// 讀取資料 ,接受學生的問題 
SocketChannel sc = (SocketChannel)key.channel(); //通過學號知道是誰問的問題 
 
//下面接受問題 
int bytesEchoed = 0; 
while((bytesEchoed = sc.read(echoBuffer))> 0){ 
System.out.println("bytesEchoed:"+bytesEchoed); 
} 
echoBuffer.flip(); 
System.out.println("limet:"+echoBuffer.limit()); 
byte [] content = new byte[echoBuffer.limit()]; 
echoBuffer.get(content); 
String result=new String(content); 
doPost(result,sc); //相應老師會去做回答的,細節自己去寫吧 
echoBuffer.clear(); 
it.remove(); //任務完成,記得上面也是一樣,要remove掉,否則下一次又來一次任務,就死迴圈了 
} 
}catch(Exception e){} 
} 
}

補充你的補充:

ssc.register( selector, SelectionKey.OP_ACCEPT ); 這個方法是把ssc註冊繫結到選擇器selector 這樣下次你想找ssc或者判斷一個物件是不是ssc就可以通過selector來查詢,查詢是通過判斷ssc的key得到的。 至於第二個引數SelectionKey.OP_ACCEPT 你可以理解成ssc的key型別或者操作許可權 如果 ssc是學校老師,那麼繫結成功後 老師就擁有了OP_ACCEPT的許可權或者說他的key型別是SelectionKey.OP_ACCEPT Accept是接受的意思,這是不是很像socket程式設計裡的 accept()方法呢? 是的,沒錯,我們正是通過這個引數給了老師招生和帶學生來註冊的許可權。

而學生呢? 他擁有的許可權為SelectionKey.OP_READ 表示有收發讀取訊息的許可權,即問問題的許可權,因此他不能幫別的學生註冊。

所以你回到上面仔細看看while結構體裡面做了判斷如下:

if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {…} 很明顯,擁有Accept許可權的人只可能是老師,那老師有什麼事會找教務處? 那肯定就是他是招生辦的,招到一個學生來報名來註冊了。 然後,馬上給這個新連上來的客戶端分配了一個key SelectionKey newKey = sc.register( selector, SelectionKey.OP_READ ); 看,這裡只給他OP_READ,而不是Accept哦

另一個if else if((key.readyOps() & SelectionKey.OP_READ)== SelectionKey.OP_READ){

//很明顯,這是這學生,因為所有帶OP_READ的人都是前面由招生辦老師帶過來註冊過的。