天天看點

Java網絡程式設計TCP和UDP的差別網絡程式設計中的關鍵類和接口BIO程式設計NIO程式設計(應用反應器模式)AIO程式設計BIO,NIO,AIO的差別

Java網絡程式設計

  • TCP和UDP的差別
  • 網絡程式設計中的關鍵類和接口
  • BIO程式設計
  • NIO程式設計(應用反應器模式)
  • AIO程式設計
  • BIO,NIO,AIO的差別

TCP和UDP的差別

  • UDP
    • 每個資料報中都給出了完整的位址資訊,是以無需要建立發送方和接收方的連接配接
    • UDP傳輸資料時是有大小限制的,每個被傳輸的資料報必須限定在64KB之内。
    • UDP是一個不可靠的協定,發送方所發送的資料報并不一定以相同的次序到達接收方
    • UDP操作簡單,而且僅需要較少的監護,是以通常用于區域網路高可靠性的分散系統中client/server應用程式。例如視訊會議系統,并不要求音頻視訊資料絕對的正确,隻要保證連貫性就可以了,這種情況下顯然使用UDP會更合理一些。飛秋聊天、淩波桌面共享、網絡視訊會議
  • TCP
    • 面向連接配接的協定,在socket之間進行資料傳輸之前必然要建立連接配接,是以在TCP中需要連接配接時間。
    • TCP傳輸資料無大小限制,一旦連接配接建立起來,雙方的socket就可以按統一的格式傳輸大的資料。
    • TCP是一個可靠的協定,它確定接收方完全正确地擷取發送方所發送的全部資料。
    • TCP在網絡通信上有極強的生命力,例如遠端連接配接(Telnet)和檔案傳輸(FTP)都需要不定長度的資料被可靠地傳輸。但是可靠的傳輸是要付出代價的,對資料内容正确性的檢驗必然占用計算機的處理時間和網絡的帶寬,是以TCP傳輸的效率不如UDP高。

網絡程式設計中的關鍵類和接口

  • InteAddress類 --> 是一個代表IP位址的封裝,沒有構造方法,可以通過兩個靜态方法獲得它的對象
    InetAddress ip = InetAddress.getByName("www.baidu.com");
      InetAddress local = InetAddress.getByAddress(new byte[]{127,0,0,1});
               
  • URL --> 是統一資源定位符的簡稱,它表示Internet上某一資源的位址
  • URLConnection --> 對象可以向所代表的URL連接配接發送請求和讀取URL的資源
  • URLDecoder和URLEncoder -->用于編解碼
    String keyWord = URLDecoder.decode("%E6%9D%8E%E5%88%9A+j2ee", "UTF-8");
      String urlStr = URLEncoder.encode( "ROR靈活開發最佳指南" , "GBK");
               
  • Get方式請求資源
    String urlName = url + "?" + param;
      URL realUrl = new URL(urlName);
      		 
      URLConnection conn = realUrl.openConnection();//打開和URL之間的連接配接
      		 
      conn.setRequestProperty("accept", "*/*");
      conn.setRequestProperty("connection", "Keep-Alive");
      conn.setRequestProperty("user-agent","Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
      		 
      conn.connect();//建立實際的連接配接
               
  • Post方式請求資源
    URL realUrl = new URL(url);
      		 
      URLConnection conn = realUrl.openConnection();//打開和URL之間的連接配接
      		 
      conn.setRequestProperty("accept", "*/*");//設定通用的請求屬性
      conn.setRequestProperty("connection", "Keep-Alive");
      conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); 
      		 
      //發送POST請求必須設定如下兩行
      conn.setDoOutput(true);//可以發送資料 
      conn.setDoInput(true);//可以接收資料
      		 
      out = new PrintWriter(conn.getOutputStream());//擷取URLConnection對象對應的輸出流
      		 
      out.print(param);//發送請求參數
               

BIO程式設計

  • TCP
    • 服務端
      ServerSocket serverSocket =new ServerSocket(10086);//1024-65535的某個端口
        Socket socket = serverSocket.accept();
        InputStream is = socket.getInputStream();
        InputStreamReader isr =new InputStreamReader(is);
        BufferedReader br =new BufferedReader(isr);
        String info =null;
        while((info=br.readLine())!=null){
        	System.out.println("Hello,我是伺服器,用戶端說:"+info);
        }
        socket.shutdownInput();//關閉輸入流
        OutputStream os = socket.getOutputStream();
        PrintWriter pw = new PrintWriter(os);
        pw.write("Hello World!");
        pw.flush();
        		
        //5、關閉資源
        XXX.close();
                 
    • 用戶端
      Socket socket =new Socket("127.0.0.1",10086);
        OutputStream os = socket.getOutputStream();//位元組輸出流
        PrintWriter pw =new PrintWriter(os);//将輸出流包裝成列印流
        pw.write("使用者名:admin;密碼:admin");
        pw.flush();
        socket.shutdownOutput();
        InputStream is = socket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        String info = null;
        while((info=br.readLine())!=null){
        	System.out.println("Hello,我是用戶端,伺服器說:"+info);
        }
        		  
        //4、關閉資源
        XXX.close();
                 
  • UDP
    • 服務端
      DatagramSocket server = new DatagramSocket(5050);
        byte[] recvBuf = new byte[100];
        DatagramPacket recvPacket = new DatagramPacket(recvBuf, recvBuf.length);
        server.receive(recvPacket);
        String recvStr = new String(recvPacket.getData(), 0, recvPacket.getLength());
        System.out.println("Hello World!" + recvStr);
        int port = recvPacket.getPort();
        InetAddress addr = recvPacket.getAddress();
        String sendStr = "Hello ! I'm Server";
        byte[] sendBuf;
        sendBuf = sendStr.getBytes();
        DatagramPacket sendPacket = new DatagramPacket(sendBuf, sendBuf.length, addr, port);
        server.send(sendPacket);
        server.close();
                 
    • 用戶端
      DatagramSocket client = new DatagramSocket();
        String sendStr = "Hello! I'm Client";
        byte[] sendBuf;
        sendBuf = sendStr.getBytes();
        InetAddress addr = InetAddress.getByName("127.0.0.1");
        int port = 5050;
        DatagramPacket sendPacket = new DatagramPacket(sendBuf, sendBuf.length, addr, port);
        client.send(sendPacket);
        byte[] recvBuf = new byte[100];
        DatagramPacket recvPacket = new DatagramPacket(recvBuf, recvBuf.length);
        client.receive(recvPacket);
        String recvStr = new String(recvPacket.getData(), 0, recvPacket.getLength());
        System.out.println("收到:" + recvStr);
        client.close();
                 

NIO程式設計(應用反應器模式)

  • 核心類和接口
    • Channel(通道):NIO把它支援的I/O對象抽象為Channel。它模拟了通信連接配接,類似于最早I/O中的流(Stream),使用者可以通過它讀取和寫入資料。目前常用的類有SocketChannel、ServerSocketChannel、DatagramChannel、FileChannel等。
    • Buffer(緩沖區): Buffer是一塊連續的記憶體區域,一般作為Channel收發資料的載體出現。所有資料都通過Buffer對象來處理。
    • Selector(選擇器):Selector類提供了監控一個和多個通道目前狀态的機制。隻要Channel向Selector注冊了某種特定的事件,Selector就會監聽這些事件是否會發生,一旦發生某個事件,便會通知對應的Channel。使用選擇器,借助單一線程,就可對數量龐大的活動I/O通道實施監控和維護。
    • 在通道上可以注冊我們感興趣的事件。總共有以下四種事件:
      • 服務端接收用戶端連接配接事件 SelectionKey.OP_ACCEPT(16)
      • 用戶端連接配接服務端事件 SelectionKey.OP_CONNECT(8)
      • 讀事件 SelectionKey.OP_READ(1)
      • 寫事件 SelectionKey.OP_WRITE(4)
  • 使用NIO程式設計,大體上可以分為下面三個步驟
    • 向Selector對象注冊感興趣的事件
    • 從Selector中擷取感興趣的事件
    • 根據不同的事件進行相應的處理
  • NIO執行個體代碼
    • 服務端
      public class NIOServer {
            private Selector selector; // 通道管理器
        
            public void initServer(int port) throws IOException {
                ServerSocketChannel serverChannel = ServerSocketChannel.open(); // 獲得一個ServerSocket通道                                   
                serverChannel.configureBlocking(false); // 設定通道為非阻塞                                 
                serverChannel.socket().bind(new InetSocketAddress(port)); // 将該通道對應的ServerSocket綁定到port端口                                   
                this.selector = Selector.open(); // 獲得一個通道管理器
                                                 // 将通道管理器和該通道綁定,并為該通道注冊SelectionKey.OP_ACCEPT事件,注冊該事件後,
                                                 // 當該事件到達時,selector.select()會傳回,如果該事件沒到達selector.select()會一直阻塞。
        
                serverChannel.register(selector, SelectionKey.OP_ACCEPT);
            }
        
            public void listen() throws IOException {
                System.out.println("服務端啟動成功!");
        
                while (true) { // 輪詢通路selector                                      
                    selector.select(); // 當注冊的事件到達時,方法傳回;否則,該方法會一直阻塞                                        
        
                    Iterator ite = this.selector.selectedKeys().iterator(); // 獲得selector中選中的項的疊代器,選中的項為注冊的事件
        
                    while (ite.hasNext()) {
                        SelectionKey key = (SelectionKey) ite.next();
                        ite.remove(); // 删除已選的key,以防重複處理                                            
        
                        if (key.isAcceptable()) { // 用戶端請求連接配接事件
        
                            ServerSocketChannel server = (ServerSocketChannel) key.channel();
                            SocketChannel channel = server.accept(); // 獲得和用戶端連接配接的通道                                             
                            channel.configureBlocking(false); // 設定成非阻塞
                            channel.write(ByteBuffer.wrap(new String("向用戶端發送了一條資訊").getBytes(
                                        "UTF-8")));
                            channel.register(this.selector, SelectionKey.OP_READ);
                        } else if (key.isReadable()) {
                            read(key);
                        }
                    }
                }
            }
        
            public void read(SelectionKey key) throws IOException {
                SocketChannel channel = (SocketChannel) key.channel(); // 伺服器可讀取消息:得到事件發生的Socket通道                                  
                ByteBuffer buffer = ByteBuffer.allocate(64); // 建立讀取的緩沖區
                channel.read(buffer);
        
                byte[] data = buffer.array();
                String msg = new String(data, "UTF-8").trim();
                System.out.println("服務端收到資訊:" + msg);
        
                ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes("UTF-8"));
                channel.write(outBuffer); // 将消息回送給用戶端
            }
        
            public static void main(String[] args) throws IOException {
                NIOServer server = new NIOServer();
                server.initServer(8000);
                server.listen();
            }
        }
                 
    • 用戶端
      public class NIOClient {
            private Selector selector; // 通道管理器
        
            public void initClient(String ip, int port) throws IOException {
                SocketChannel channel = SocketChannel.open(); // 獲得一個Socket通道                                   
                channel.configureBlocking(false); // 設定通道為非阻塞                                   
                this.selector = Selector.open(); // 獲得一個通道管理器                                   
                channel.connect(new InetSocketAddress(ip, port)); // 用channel.finishConnect();才能完成連接配接
                channel.register(selector, SelectionKey.OP_CONNECT);
            }
        
            public void listen() throws IOException {
                while (true) { // 輪詢通路selector
                    selector.select();
        
                    Iterator ite = this.selector.selectedKeys().iterator(); // 獲得selector中選中的項的疊代器
        
                    while (ite.hasNext()) {
                        SelectionKey key = (SelectionKey) ite.next();
                        ite.remove(); // 删除已選的key,以防重複處理                                        
        
                        if (key.isConnectable()) { // 連接配接事件發生
        
                            SocketChannel channel = (SocketChannel) key.channel();
        
                            if (channel.isConnectionPending()) { // 如果正在連接配接,則完成連接配接
                                channel.finishConnect();
                            }
        
                            channel.configureBlocking(false); // 設定成非阻塞
                            channel.write(ByteBuffer.wrap(new String("向服務端發送了一條資訊").getBytes(
                                        "UTF-8")));
                            channel.register(this.selector, SelectionKey.OP_READ);
                        } else if (key.isReadable()) {
                            read(key);
                        }
                    }
                }
            }
        
            public void read(SelectionKey key) throws IOException {
                SocketChannel channel = (SocketChannel) key.channel(); // 伺服器可讀取消息:得到事件發生的Socket通道                                  
                ByteBuffer buffer = ByteBuffer.allocate(64); // 建立讀取的緩沖區
                channel.read(buffer);
        
                byte[] data = buffer.array();
                String msg = new String(data, "UTF-8").trim();
                System.out.println("用戶端收到資訊:" + msg);
            }
        
            public static void main(String[] args) throws IOException {
                NIOClient client = new NIOClient();
                client.initClient("localhost", 8000);
                client.listen();
            }
        }
                 

AIO程式設計

  • 核心類或接口
    • Callable: Callable與Runnable的功能大緻相似,Callable中有一個call()函數,但是call()函數有傳回值
    • Future: Future就是對于具體的Runnable或者Callable任務的執行結果進行取消、查詢是否完成、擷取結果、設定結果操作。get方法會阻塞,直到任務傳回結果
    • RunnableFuture: 它繼承了Runnbale和Futrue這兩個接口
    • FutureTask:
      • 它實作了RunnableFuture,另外它還可以包裝Runnable和Callable,由構造函數注入依賴
      • 它既可以通過Thread包裝來直接執行,也可以送出給ExecuteService來執行
      • 它還可以直接通過get()函數擷取執行結果,該函數會阻塞,直到結果傳回
        • 使用執行個體:
          Future<?> result = mExecutor.submit(new Runnable() {  
            	@Override  
            	public void run() {  
            		fibc(20);  
            	}  
            });
            		
            Future<Integer> result2 = mExecutor.submit(new Callable<Integer>() {  
            	@Override  
            	public Integer call() throws Exception {  
            		return fibc(20);  
            	}  
            });
            		
            FutureTask<Integer> futureTask = new FutureTask<Integer>(  
            	new Callable<Integer>() {  
            	@Override  
            	public Integer call() throws Exception {  
            		return fibc(20);  
            	}  
            });  
            mExecutor.submit(futureTask) ;//送出futureTask
                     
    • 異步Socket :
      • AsynchronousChannel:所有AIO Channel的父類。
      • AsynchronousByteChannel:支援Byte讀寫的Channel
      • AsynchronousDatagramChannel:支援資料包(datagram)讀寫的Channel
      • AsynchronousFileChannel:支援檔案讀寫的Channel
      • AsynchronousServerSocketChannel: 支援資料流讀寫的伺服器端Channel
      • AsynchronousSocketChannel:支援資料流讀寫的用戶端Channel
      • AsynchronousChannelGroup:支援資源共享的Channel分組
      • CompletionHandler: 異步IO操作結果的回調接口,用于定義在IO操作完成後所作的回調工作。AIO的API允許兩種方式來處理異步操作的結果:傳回的Future模式或者注冊CompletionHandler,我更推薦用CompletionHandler的方式
      • 異步Socket方法總結:
        • accept(): 接受一個連接配接,傳回一個Future,可通過Future擷取到Socket的狀态,和資料
        • accept(A attachment, CompletionHandler<AsynchronousSocketChannel,? super A> handler):接受連接配接,并為連接配接綁定一個CompletionHandler處理Socket連接配接
        • bind(SocketAddress local): 把ServerSocket綁定到本地端口上,等待連接配接
        • bind(SocketAddress local, int backlog): 功能和上面一個方法一樣,添加了backlog參數指定隊列中挂起的連接配接的最大個數
        • open(): 開啟一個異步Socket通道
        • open(AsynchronousChannelGroup group): 開啟一個異步Socket通道,并把通道加入到指定的組做資源管理
        • provider(): 傳回這個Channel的建立者
        • setOption(SocketOption name, T value): 配置Socket參數的方法
  • AIO原理

    所謂AIO,異步IO,其主要是針對程序在調用IO擷取外部資料時,是否阻塞調用程序而言的。一個程序的IO調用步驟大緻如下:

    • 程序向作業系統請求資料
    • 作業系統把外部資料加載到核心的緩沖區中
    • 作業系統把核心的緩沖區拷貝到程序的緩沖區
    • 程序獲得資料完成自己的功能
  • AIO代碼執行個體
    • 服務端
      public class AIOServer {
            public static void main(String[] args) throws Exception {
                AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup.withThreadPool(Executors.newFixedThreadPool(
                            10));
                AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(channelGroup);
                server.bind(new InetSocketAddress(5000));
        
                server.accept(server,
                    new CompletionHandler<AsynchronousSocketChannel, AsynchronousServerSocketChannel>() {
                        @Override
                        public void completed(AsynchronousSocketChannel result,
                            AsynchronousServerSocketChannel server) {
                            server.accept(server, this);
        
                            ByteBuffer buffer = ByteBuffer.allocate(1024);
                            Future<Integer> future = result.read(buffer);
        
                            try {
                                future.get();
                                buffer.flip();
        
                                byte[] dst = new byte[buffer.limit()];
                                buffer.get(dst, 0, buffer.limit());
                                System.out.println("server*************  : " +
                                    new String(dst));
        
                                buffer.clear();
                                buffer.put("client received, haha".getBytes());
                                buffer.flip();
                                result.write(buffer);
        
                                buffer.clear();
                                result.read(buffer).get();
                                buffer.flip();
        
                                byte[] dst2 = new byte[buffer.limit()];
                                buffer.get(dst2, 0, buffer.limit());
                                System.out.println("*********server received : " +
                                    new String(dst2));
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
        
                        @Override
                        public void failed(Throwable exc,
                            AsynchronousServerSocketChannel attachment) {
                        }
                    });
                Thread.sleep(100000);
            }
        }
                 
    • 用戶端
      public class AIOClient {
            public static void main(String[] args) throws Exception {
                AsynchronousSocketChannel clientChannel = AsynchronousSocketChannel.open();
                Future future = clientChannel.connect(new InetSocketAddress(
                            "127.0.0.1", 5000));
                Object o = future.get();
        
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                clientChannel.write(byteBuffer.wrap("somchai ....".getBytes()));
        
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                MyObj myObj = new MyObj(clientChannel, readBuffer);
                clientChannel.read(readBuffer, myObj,
                    new CompletionHandler<Integer, MyObj>() {
                        @Override
                        public void completed(Integer result, MyObj attachment) {
                            attachment.myBuffer.flip();
        
                            byte[] dst = new byte[attachment.myBuffer.limit()];
                            attachment.myBuffer.get(dst, 0, attachment.myBuffer.limit());
        
                            System.out.println("client***inner************** b : " +
                                new String(dst));
        
                            ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
                            writeBuffer.put(new String(" I am client ...xixi").getBytes());
                            writeBuffer.flip();
                            attachment.clientChannel.write(writeBuffer);
                        }
        
                        @Override
                        public void failed(Throwable exc, MyObj attachment) {
                        }
                    });
                Thread.sleep(10000);
            }
        }
        
        
        class MyObj {
            AsynchronousSocketChannel clientChannel = null;
            ByteBuffer myBuffer = null;
        
            public MyObj(AsynchronousSocketChannel clientChannel, ByteBuffer myBuffer) {
                this.clientChannel = clientChannel;
                this.myBuffer = myBuffer;
            }
        }
                 

BIO,NIO,AIO的差別

  • 一個IO操作其實分成了兩個步驟:發起IO請求和實際的IO操作
  • 阻塞IO和非阻塞IO的差別在于第一步,發起IO請求是否會被阻塞,如果阻塞直到完成那麼就是傳統的阻塞IO,如果不阻塞,那麼就是非阻塞IO
  • 同步IO和異步IO的差別就在于第二個步驟是否阻塞,如果請求程序被IO讀寫阻塞,那麼就是同步IO
  • 什麼是同步、異步
    • 同步: 指使用者程序發起了實際的IO操作之後,等待或輪詢的檢視IO操作是否完成
    • 異步: 指使用者程序發起了實際的IO操作之後,便開始做自己的事情,而當IO操作已經完成的時候會得到IO完成的通知
  • 什麼是阻塞和非阻塞
    • 阻塞: 讀取或者寫入函數将一直等待
    • 非阻塞: 讀取或者寫入函數會立即傳回一個狀态值
  • 同步、異步和阻塞、非阻塞的組合
    • 同步阻塞(BIO): 使用者程序在發起一個IO操作以後,必須等待IO操作的完成,隻有當真正完成了IO操作以後,使用者程序才能運作
    • 同步非阻塞(NIO): 使用者程序發起一個IO操作以後,可傳回做其它事情,但是使用者程序需要時不時的詢問IO操作是否就緒,這就要求使用者程序不停的去詢問,進而引入不必要的CPU資源浪費
    • 異步非阻塞(AIO): 使用者程序發起一個IO操作後便立即傳回,等IO操作真正的完成以後,應用程式會得到IO操作完成的通知,此時使用者程序隻需要對資料進行處理就好了,不需要進行實際的IO讀寫操作,因為真正的IO讀取或者寫入操作已經由核心完成了

繼續閱讀