用Java.nio.* 進(jìn)行網(wǎng)絡(luò)編程
前言
因?yàn)榇蛩阌胘ava編寫異步通信的server和client程序,筆者便學(xué)習(xí)使用java.nio開發(fā)包,其間遇到一些問題,上網(wǎng)卻發(fā)現(xiàn)網(wǎng)上對(duì)它的應(yīng)用描述的不是很多。所以,筆者不惜班門弄斧,做些簡(jiǎn)單的討論,以便大家更進(jìn)一步的討論。
對(duì)相關(guān)類的簡(jiǎn)單介紹
java.nio.*, 據(jù)說它提供了一些更加底層的一些功能,如:類似windows環(huán)境下的AsyncSocket類的異步操作的功能,能顯著降低server端程序的線程管理開銷。
因?yàn)榇蠖鄶?shù)應(yīng)用是建立在TCP之上,所以在此只說說SocketChannel,ServerSocketChannel,Selector 和ByteBuffer這幾個(gè)類.前三個(gè)最終都源自channel類。而channel 類,可以理解為在具體I/O或文件對(duì)象之上抽象的一個(gè)操作對(duì)象,我們通過操作channel的讀寫達(dá)到對(duì)其對(duì)應(yīng)的文件或I/O對(duì)象(包括socket)讀寫的目的。讀寫的內(nèi)容在內(nèi)存中放在ByteBuffer類提供的緩沖區(qū)??偠灾?,channel作為一個(gè)橋梁,連接了I/O對(duì)象和內(nèi)存中的 ByteBuffer,實(shí)現(xiàn)了I/O的更高效的存取。
一個(gè)基于TCP的服務(wù)器端程序,必然有個(gè)偵聽端和若干個(gè)通信端,它們?cè)趎io中由對(duì)應(yīng)的ServerSocketChannel 和SocketChannel類來實(shí)現(xiàn)。為了達(dá)到異步I/O操作的目的,需要Selector類,它能檢測(cè)到I/O對(duì)象的狀態(tài)。
SocketChannel類是抽象類,通過調(diào)用它的靜態(tài)函數(shù)open(),可生成一個(gè)SocketChannel對(duì)象,該對(duì)象對(duì)應(yīng)一個(gè)java.net.Socket,可通過SocketChannel.socket()獲得,而其對(duì)應(yīng)的Socket也可通過調(diào)用函數(shù)getChannel()得到已建立的相應(yīng)SocketChannel。
SocketChannel與它的socket是一一對(duì)應(yīng)的。SocketChannel的操作與Socket也很相似。
ServerSocketChannel也是通過調(diào)用它的靜態(tài)函數(shù)open()生成的,只是它不能直接調(diào)用bind()函數(shù)來綁定一個(gè)地址,需要它對(duì)應(yīng)的ServerSocket來完成綁定工作,一般可按如下步驟做:
- ServerSocketChannel ssc = new ServerSocketChannel.open();
- ssc.socket().bind(InetSocketAddress(host,port));
羅嗦了半天,還是看看最簡(jiǎn)單的C/S實(shí)現(xiàn)吧,服務(wù)器提供了基本的回射(echo)功能,其中提供了較詳細(xì)的注釋。
源碼分析
1.服務(wù)器端:
- ////////////////////////
- //AsyncServer.java
- // by zztudou@163.com
- ////////////////////////
- import java.nio.channels.SocketChannel;
- import java.nio.channels.ServerSocketChannel;
- import java.nio.channels.SelectionKey;
- import java.nio.channels.Selector;
- import java.nio.ByteBuffer;
- import java.nio.channels.SelectableChannel;
- import java.nio.channels.spi.SelectorProvider;
- import java.net.ServerSocket;
- import java.net.Socket;
- import java.net.InetSocketAddress;
- import java.net.SocketAddress;
- import java.util.Iterator;
- import java.util.LinkedList;
- import java.io.IOException;
- class AsyncServer implements Runnable{
- private ByteBuffer r_buff = ByteBuffer.allocate(1024);
- private ByteBuffer w_buff = ByteBuffer.allocate(1024);
- private static int port = 8848;
- public AsyncServer(){
- new Thread(this).start();
- }
- public void run(){
- try{
- //生成一個(gè)偵聽端
- ServerSocketChannel ssc = ServerSocketChannel.open();
- //將偵聽端設(shè)為異步方式
- ssc.configureBlocking(false);
- //生成一個(gè)信號(hào)監(jiān)視器
- Selector s = Selector.open();
- //偵聽端綁定到一個(gè)端口
- ssc.socket().bind(new InetSocketAddress(port));
- //設(shè)置偵聽端所選的異步信號(hào)OP_ACCEPT
- ssc.register(s,SelectionKey.OP_ACCEPT);
- System.out.println("echo server has been set up ......");
- while(true){
- int n = s.select();
- if (n == 0) {//沒有指定的I/O事件發(fā)生
- continue;
- }
- Iterator it = s.selectedKeys().iterator();
- while (it.hasNext()) {
- SelectionKey key = (SelectionKey) it.next();
- if (key.isAcceptable()) {//偵聽端信號(hào)觸發(fā)
- ServerSocketChannel server = (ServerSocketChannel) key.channel();
- //接受一個(gè)新的連接
- SocketChannel sc = server.accept();
- sc.configureBlocking(false);
- //設(shè)置該socket的異步信號(hào)OP_READ:當(dāng)socket可讀時(shí),
- //觸發(fā)函數(shù)DealwithData();
- sc.register(s,SelectionKey.OP_READ);
- }
- if (key.isReadable()) {//某socket可讀信號(hào)
- DealwithData(key);
- }
- it.remove();
- }
- }
- }
- catch(Exception e){
- e.printStackTrace();
- }
- }
- public void DealwithData(SelectionKey key) throws IOException{
- int count;
- //由key獲取指定socketchannel的引用
- SocketChannel sc = (SocketChannel)key.channel();
- r_buff.clear();
- //讀取數(shù)據(jù)到r_buff
- while((count = sc.read(r_buff))> 0)
- ;
- //確保r_buff可讀
- r_buff.flip();
- w_buff.clear();
- //將r_buff內(nèi)容拷入w_buff
- w_buff.put(r_buff);
- w_buff.flip();
- //將數(shù)據(jù)返回給客戶端
- EchoToClient(sc);
- w_buff.clear();
- r_buff.clear();
- }
- public void EchoToClient(SocketChannel sc) throws IOException{
- while(w_buff.hasRemaining())
- sc.write(w_buff);
- }
- public static void main(String args[]){
- if(args.length > 0){
- port = Integer.parseInt(args[0]);
- }
- new AsyncServer();
- }
- }
在當(dāng)前目錄下運(yùn)行:
javac AsynServer.java
后,若無編譯出錯(cuò),接下來可運(yùn)行:
java AsynServer 或 java AsynServer ×××(端口號(hào))
上述服務(wù)程序在運(yùn)行時(shí),可指定其偵聽端口,否則程序會(huì)取8848為默認(rèn)端口。
2.客戶端的簡(jiǎn)單示例:
- ////////////////////////
- //AsyncClient.java
- // by zztudou@163.com
- ////////////////////////
- import java.nio.channels.SocketChannel;
- import java.net.InetSocketAddress;
- import java.nio.ByteBuffer;
- import java.nio.channels.Selector;
- import java.nio.channels.SelectionKey;
- import java.io.IOException;
- import java.io.BufferedReader;
- import java.io.InputStreamReader;
- class AsyncClient{
- private SocketChannel sc;
- private final int MAX_LENGTH = 1024;
- private ByteBuffer r_buff = ByteBuffer.allocate(MAX_LENGTH);
- private ByteBuffer w_buff = ByteBuffer.allocate(MAX_LENGTH);
- private static String host ;
- private static int port = 8848;
- public AsyncClient(){
- try {
- InetSocketAddress addr = new InetSocketAddress(host,port);
- //生成一個(gè)socketchannel
- sc = SocketChannel.open();
- //連接到server
- sc.connect(addr);
- while(!sc.finishConnect())
- ;
- System.out.println("connection has been established!...");
- while(true){
- //回射消息
- String echo;
- try{
- System.err.println("Enter msg you'd like to send: ");
- BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
- //輸入回射消息
- echo = br.readLine();
- //把回射消息放入w_buff中
- w_buff.clear();
- w_buff.put(echo.getBytes());
- w_buff.flip();
- }catch(IOException ioe){
- System.err.println("sth. is wrong with br.readline() ");
- }
- //發(fā)送消息
- while(w_buff.hasRemaining())
- sc.write(w_buff);
- w_buff.clear();
- //進(jìn)入接收狀態(tài)
- Rec();
- //間隔1秒
- Thread.currentThread().sleep(1000);
- }
- }catch(IOException ioe){
- ioe.printStackTrace();
- }
- catch(InterruptedException ie){
- ie.printStackTrace();
- }
- }
- ////////////
- //讀取server端發(fā)回的數(shù)據(jù),并顯示
- public void Rec() throws IOException{
- int count;
- r_buff.clear();
- count=sc.read(r_buff);
- r_buff.flip();
- byte[] temp = new byte[r_buff.limit()];
- r_buff.get(temp);
- System.out.println("reply is " + count +" long, and content is: " + new String(temp));
- }
- public static void main(String args[]){
- if(args.length < 1){//輸入需有主機(jī)名或IP地址
- try{
- System.err.println("Enter host name: ");
- BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
- host = br.readLine();
- }catch(IOException ioe){
- System.err.println("sth. is wrong with br.readline() ");
- }
- }
- else if(args.length == 1){
- host = args[0];
- }
- else if(args.length > 1){
- host = args[0];
- port = Integer.parseInt(args[1]);
- }
- new AsyncClient();
- }
- }
在當(dāng)前目錄下運(yùn)行:
javac AsynClient.java
后,若無編譯出錯(cuò),確認(rèn)AsyncServer已經(jīng)運(yùn)行的情況下,接下來可運(yùn)行:
java AsynClient hostname 或 java AsynClient hostname ×××(端口號(hào))
并按提示進(jìn)行操作即可。
總 結(jié)
總的來說,用nio進(jìn)行網(wǎng)絡(luò)編程還是很有新意的,服務(wù)器端軟件能在一個(gè)線程中維護(hù)與眾多客戶端的通信連接。筆者在本文中試圖用一個(gè)典型的回射例子說明如何用nio建立最基本的C/S應(yīng)用。希望大家能試著用用它。
另外,筆者在實(shí)踐中也發(fā)現(xiàn)nio在應(yīng)用中存在的一些難題,比如如何應(yīng)用SocketChannel的繼承類,以及如何在socketchannel之上應(yīng)用SSL(Secure Socket Layer)等等,因而希望這篇文章只是拋磚引玉,引起大家對(duì)nio作進(jìn)一步的討論。
原文鏈接:http://lrtlcg.iteye.com/blog/844357
【編輯推薦】