1. 程式人生 > 實用技巧 >Java NIO-1——示例程式碼-1——網路傳輸

Java NIO-1——示例程式碼-1——網路傳輸

BIO的服務端程式

package com.dingyf.net.bio;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class BioServer {

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        ExecutorService executorService = Executors.newCachedThreadPool();
        while (true) {
            /**
             * @class ServerSocket
             * This class implements server sockets.
             * A server socket waits for requests to come in over the network.
             * It performs some operation based on that request, and then possibly returns a result to the requester.
             *
             * @class Socket
             * This class implements client sockets (also called just "sockets").
             * A socket is an endpoint for communication between two machines.
             *
             * @method accept
             * Listens for a connection to be made to this socket and accepts it.
             * The method blocks until a connection is made.
             */
            /**
             * 在這裡接收到了一個連線請求的Socket
             * [1] ServerSocket serverSocket = new ServerSocket(8080);
             * ServerSocket已經綁定了埠號,所以它只能接收對固定埠號的請求
             * 對比NIO中的Selector,一個Selector上可以註冊多個ServerSocketChannel
             * 其實這沒有什麼意義,因為一段伺服器程式碼不需要暴露多個埠號
             * [2] Socket socket = serverSocket.accept();
             * Set<SelectionKey> selectionKeys = selector.selectedKeys();
             * ServerSocket一次只能接收一個請求,對比Selector.selectedKeys()方法一次則可以接收多個請求
             */
            Socket socket = serverSocket.accept();
            TaskHandler handler = new TaskHandler(socket);
            /**
             * 在這裡將前面接收到的請求拋給執行緒池去執行
             * todo 這裡也可以不把接收到的Socket交給執行緒池去執行,而是自己執行,但是因為一次只能接收一個請求,?所以可能會使客戶端的請求得不到連線
             * todo 這裡如果當前執行緒自己執行的後果就是後續請求由於伺服器執行緒在執行計算而無法得到連線;那麼nio即使一次接收多個請求,不也存在同樣的問題嗎,Selector接收的請求雖然多了,但是計算量也相應增大了,所以後續的請求不也應當得不到連線嗎(得不到連線也就是執行不到selector.select();)
             * todo nio是怎麼處理請求的,如果不是交給執行緒池的話
             * todo 目前來看,我看到的bio和nio編碼方式上的區別只有兩點:
             *          todo 1. nio一次可以接收多個請求
             *          todo 2. nio的讀寫不需要獲取流物件,直接通過channel的讀寫方法
             */
            executorService.submit(handler);
        }
    }


    static class TaskHandler implements Runnable{

        private Socket socket;
        private Scanner scanner;
        private BufferedWriter writer;

        public TaskHandler(Socket socket) {
            this.socket = socket;
            try {
                this.scanner = new Scanner(socket.getInputStream());
                this.writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void run() {
            boolean flag = true;
            while (flag){
                String line = scanner.nextLine();
                System.out.println("Thread ID:" + Thread.currentThread().getId()+" Read from client - " + line);//列印客戶端傳來的訊息
                if(line != null){
                    String writeMsg;
                    if(line.equalsIgnoreCase("bye")){
                        flag = false;
                        writeMsg = "[Exit] " + line + "\n";
                    }else {
                        writeMsg = "[Echo] " + line + "\n";
                    }
                    try {
                        writer.write(writeMsg);//向客戶端傳送訊息
                        writer.flush();
//                        writer.write("------------\n");//向客戶端傳送訊息
//                        writer.flush();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

            scanner.close();
            try {
                writer.close();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

BIO的客戶端程式

package com.dingyf.net.bio;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Random;
import java.util.Scanner;

public class BioClient {

    public static void main(String[] args) throws IOException {
        /**
         * 1. Socket socket = serverSocket.accept();
         * 2. Socket socket = new Socket(InetAddress.getByName("localhost"), 8080);
         * BioServer啟動時,ServerSocket.accept()方法阻塞,當BioClient中new Socket()的方法執行後,Server執行緒被喚醒
         */
        Socket socket = new Socket(InetAddress.getByName("localhost"), 8080);
        Scanner scanner = new Scanner(socket.getInputStream());
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        while (true){
            String input = getInput();
            writer.write(input + "\n");//向服務端傳送訊息
            writer.flush();//一定記得要flush
//            socket.getOutputStream().write((input + "\n").getBytes());
            /**
             * 阻塞指的就是io的讀操作,如果流中沒有內容,bio會一直等待,直到有資料才返回
             */
            String line = scanner.nextLine();
            System.out.println("Thread ID:" + Thread.currentThread().getId()+" Read response from server - " + line);//列印服務端傳來的訊息
            if(input.equalsIgnoreCase("bye")){
                break;
            }
        }
        scanner.close();
        writer.close();
        socket.close();
    }

    private static String getInput(){
        int i = new Random().nextInt(100);
        if(i == 50){
            return "bye";
        }
        return "This is Client Msg " + i;
    }
}

NIO的服務端程式

package com.dingyf.net.nio;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class NioServer {

    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(8080));
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        while (true) {
            /**
             * @method select
             * Selects a set of keys whose corresponding channels are ready for I/O operations.
             * This method performs a blocking selection operation.
             * It returns only after at least one channel is selected,
             * this selector's wakeup method is invoked,
             * or the current thread is interrupted, whichever comes first.
             */
            /**
             * todo select方法也是阻塞的,所以nio的非阻塞指的不會是這裡
             */
            int select = selector.select();//①
            if(select <= 0){
                break;
            }
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                /**
                 * Tips.
                 * ① int select = selector.select();
                 * 如果這裡不remove,在下一個while(true)的迴圈中,①處將返回0
                 */
                iterator.remove();
                /**
                 * @method isAcceptable
                 * Tests whether this key's channel is ready to accept a new socket connection.
                 */
                if (selectionKey.isAcceptable()) {
//                    ServerSocketChannel serverSocketChannel2 = (ServerSocketChannel) selectionKey.channel();
//                    SocketChannel channel = serverSocketChannel2.accept();
                    SocketChannel channel = serverSocketChannel.accept();
                    if (channel != null) {
                        TaskHandler handler = new TaskHandler(channel);
                        executorService.submit(handler);
                    }
                }
            }
        }
    }


    static class TaskHandler implements Runnable {

        private SocketChannel channel;

        public TaskHandler(SocketChannel channel) {
            this.channel = channel;
        }

        @Override
        public void run() {
            ByteBuffer byteBuffer = ByteBuffer.allocate(100);
            try {
                boolean flag = true;
                while (flag) {
                    byteBuffer.clear();
                    int read = channel.read(byteBuffer);//讀取
                    /**
                     * Tips.
                     * ① String line = new String(byteBuffer.array());
                     * ② byteBuffer.put(writeMsg.getBytes());
                     * 這裡如果用程式碼①來讀取,程式碼會在②處無法執行
                     */
                    String line = new String(byteBuffer.array(), 0, read);
                    System.out.println("Thread ID:" + Thread.currentThread().getId() + " Read from client - " + line);//列印客戶端傳來的訊息
                    if (line != null) {
                        String writeMsg;
                        if (line.equalsIgnoreCase("bye\n")) {
                            flag = false;
                            writeMsg = "[Exit] " + line;
                        } else {
                            writeMsg = "[Echo] " + line;
                        }
                        /**
                         * 假設在此處打了斷點,記為: NioServer.Breakpoint_1
                         */
                        byteBuffer.clear();
                        byteBuffer.put(writeMsg.getBytes());//②
                        byteBuffer.flip();
                        channel.write(byteBuffer);//寫入
                    }
                }
                channel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

NIO的客戶端程式

package com.dingyf.net.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;
import java.util.Random;

public class NioClient {

    public static void main(String[] args) throws IOException {
        SocketChannel channel = SocketChannel.open();
        channel.connect(new InetSocketAddress("localhost", 8080));
//        channel.configureBlocking(false);
        ByteBuffer byteBuffer = ByteBuffer.allocate(100);
        while (true) {
            String input = getInput();

            byteBuffer.clear();
            byteBuffer.put((input + "\n").getBytes());
            byteBuffer.flip();
            channel.write(byteBuffer);//向服務端傳送訊息

            byteBuffer.clear();
            /**
             * ① channel.write(byteBuffer);
             * ② channel.configureBlocking(false);
             * ③ int read = channel.read(byteBuffer);
             * 假設在該行打了斷點,記為: NioClient.Breakpoint_1
             * Debug啟動該客戶端,則斷點停在NioClient.Breakpoint_1,F8之後,該執行緒將阻塞,
             * 這時我們進入服務端的Debug Tab,斷點跳到NioServer.Breakpoint_1,我們F8直到①處,再次F8,
             * 回到客戶端的Debug Tab,斷點來到NioClient.Breakpoint_1的下一行
             *
             * 而如果我們添加了②,將客戶端的SocketChannel設定為非阻塞,結果將不一樣
             * 在NioClient.Breakpoint_1處F8之後,斷點將來到下一行,只是此時③處返回的讀到的資料長度為0
             * 如果我們將斷點NioServer.Breakpoint_1放行通過①,那麼③將能夠讀到資料,並返回資料長度
             *
             * 也就是說,nio的非阻塞描述的是:
             * 從通道中讀取資料時,如果此時通道中沒有資料,read方法將繼續執行,只是此時read方法返回的資料長度為0,而ByteBuffer中也不會被填充內容;
             * 相應的,如果是bio或者未將nio的channel設定為非阻塞的情況下,從流(bio)或者通道(nio)中讀取資料時,如果此時流或者通道中沒有資料,此時read方法將阻塞,直到被呼叫方向流或通道中寫入資料才會返回
             *
             * 對於nio的非阻塞,有一點值得注意:
             * 呼叫方需要不斷輪詢請求read方法,才能夠在被呼叫方向channel中寫入資料後將其取出
             * 所以應當設定單獨的執行緒管理這些channel,
             */
            int read = channel.read(byteBuffer);
            String line = new String(byteBuffer.array(), 0, read);
//            String line = new String(byteBuffer.array());
            System.out.println("Thread ID:" + Thread.currentThread().getId() + " Read response from server - " + line);//列印服務端傳來的訊息
            if (input.equalsIgnoreCase("bye")) {
                break;
            }
        }
        channel.close();
    }

    private static String getInput() {
        int i = new Random().nextInt(100);
        if (i == 50) {
            return "bye";
        }
        return "This is Client Msg " + i;
    }
}