NIO示例程序------迴文

1.客戶端程序

package cn.fzmili.archetype.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.nio.charset.Charset;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;

/**
 * 代碼清單 2-4 客戶端的主類
 *
 * @author <a href="mailto:[email protected]">fzmili</a>
 * 程序基本結構: start()---------功能實現的主類。主類中的異常全部由main函數來實現。start()函數只是實現正常邏輯。
 * main()----------實現程序的啓動。
 */
public class EchoClient {

    private InetSocketAddress address;
    private ByteBuffer wBuffer = ByteBuffer.allocate(1024);
    private ByteBuffer rBuffer = ByteBuffer.allocate(1024);
    private Charset charset = Charset.forName("UTF-8");//設置發送的字符編碼

    public EchoClient(String host, int port) {
        this.address = new InetSocketAddress(host, port);
    }

    public EchoClient(InetSocketAddress address) {
        this.address = address;
    }

    public void start() throws Exception {//非阻塞連接版本。
        ///////////初始化//////////////
        final SocketChannel channel;
        channel = SocketChannel.open();//創建但不連接。
        //channel.configureBlocking(false);//設置爲非阻塞模式,阻塞方法將立即返回
        channel.connect(address);//阻塞模式和非阻塞模式的區別在於,非阻塞模式總是返回false,阻塞模式返回值根據實際情況
        //非阻塞模式
        if(channel.isConnectionPending())
            channel.finishConnect();//非阻塞模式下,連接進行時,調用這個完成連接,連接失敗時調用,拋出異常。
        if(!channel.isConnected()){
             System.out.println("connecting fail");
             return;
        }
        //獲取用戶輸入
        System.out.println("Connecting Success!");
        Scanner scanner = new Scanner(System.in);//scanner應該是一個單獨的線程。
        String text=scanner.nextLine();//到第一個換行符爲止。
        text+='\n';//因爲scanner.nextLine()不包含換行符,但服務器需要根據換行符來判斷返回。
        System.out.println("發送的數據:"+text);
        scanner.close();
        System.in.close();
        //網絡交互部分。
        wBuffer.put(charset.encode(text));
        wBuffer.flip();
        channel.write(wBuffer);
        int readBytes=channel.read(rBuffer);//關鍵問題在於這個阻塞,一直沒有數據
        System.out.println("接收的數據:" + getString(rBuffer));
        rBuffer.clear();
        channel.close();
    }

    public static String getString(ByteBuffer buffer) {
        String string = "";
        try {
            for (int i = 0; i < buffer.position(); i++) {
                string += (char) buffer.get(i);//使用絕對定位,不會移動指針。
            }
            return string;
        } catch (Exception ex) {
            ex.printStackTrace();
            return "";
        }
    }

    public static void main(String[] args) throws NumberFormatException {
        InetSocketAddress address;
        switch (args.length) {
            case 0://默認IP,默認端口,用於測試
                address = new InetSocketAddress("127.0.0.1", 9999);
                break;
            case 2://正常情況,如果有問題,則產生異常
            {
                String ip = args[0];
                int port = Integer.parseInt(args[1]);
                address = new InetSocketAddress(ip, port);
            }
            default://其他情況
                System.err.println("Usage: " + EchoClient.class.getSimpleName() + " <port>");
                return;
        }
        try {
            new EchoClient(address).start();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("---主進程結束----");
    }
}

2.服務器程序

package cn.fzmili.archetype.nio;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class EchoServer {

    private InetSocketAddress address;
    private final int bufferSize = 2048;

    public EchoServer(String host, int port) {
        this.address = new InetSocketAddress(host, port);
    }

    public EchoServer(InetSocketAddress address) {
        this.address = address;
    }

    public void start() {
        ///////////////////init()初始化////////////////////////////////
        Selector selector = null;
        try {
            //創建Channel和Selector
            ServerSocketChannel channel = ServerSocketChannel.open();
            selector = Selector.open();
            channel.configureBlocking(false);//將通道設置爲非阻塞
            channel.bind(this.address);
            /*
            *將Channel註冊到selector,並指定感興趣的事件
            *有4種事件:electionKey.OP_CONNECT  SelectionKey.OP_ACCEPT  SelectionKey.OP_READ    SelectionKey.OP_WRITE 
            *OP_CONNECT----用於客戶端,
            *OP_ACCEPT-----用於服務器
            *OP_READ & OP_WRITE--------服務器/客戶端通用
             */
            channel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("Server started .... port:" + address.getPort());
        } catch (Exception e) {
            System.out.println("Error-" + e.getMessage());
            return;
        }
        //////////////////處理連接///////////////////////////////////
        Map<Integer, ByteBuffer> buffers = new HashMap<>();//用於存儲爲每個Channel分配的ByteBuffer,方便迴文。
        try {
            int id = 0;
            while (true) {//對於服務器需要不斷的檢查是否有數據。
                Thread.sleep(1 * 1000);
                /*
                *阻塞,直到有就緒事件爲止。
                *有多個客戶端連接都阻塞在這個地方。每個客戶端都是一個Channel。
                *一個selector可以處理多個channell。這也就是提高併發數的關鍵。
                *select()不阻塞的時候,代表有需要讀寫
                 */
                selector.select();
                ///////////////處理init()中選擇的建/////////////////////////////
                Set<SelectionKey> readySelectionKey = selector.selectedKeys();
                Iterator<SelectionKey> it = readySelectionKey.iterator();
                //////處理每一個連接
                while (it.hasNext()) {
                    SelectionKey channelKey = it.next();
                    //Accept---------客戶端請求TCP連接
                    if (channelKey.isAcceptable()) {
                        /*
                        * 獲取通道 接受連接,
                        * 設置非阻塞模式(必須),同時需要註冊 讀寫數據的事件,這樣有消息觸發時才能捕獲
                        *每個客戶端的Channel是由Selector.channel()獲取的。SelectionKey就是通道的指針。
                         */
                        ServerSocketChannel channel = (ServerSocketChannel) channelKey.channel();
                        channel.accept()
                                .configureBlocking(false)
                                .register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE)//向selector註冊channel,返回與這個Chanenl關聯的SelectionKey,該Key就是Channel的指針,代表該Channel。
                                .attach(++id);//向SelectionKey添加一個標識,由於是Key就代表Channel,所以也是Channel標識。
                        /*
                            上面的程序分解以便理解:
                        channel.accept();
                        channel.configureBlocking(false);
                        SelectionKey key=channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);//向selector註冊channel,返回與這個Chanenl關聯的SelectionKey,該Key就是Channel的指針,代表該Channel。
                        key.attach(++id);//向SelectionKey添加一個標識,由於是Key就代表Channel,所以也是Channel標識。
                        
                         */
                        buffers.put(id, ByteBuffer.allocate(bufferSize));//爲每個連接準備一個緩衝區,並和Channel關聯起來。
                        System.out.println("ChannelID:" + id+"\tConnected");
                    }
                    if (channelKey.isReadable()) {// 讀數據,所有的Channel都是在這讀的,不要想象只有一個Channel。
                        SocketChannel clientChannel = (SocketChannel) channelKey.channel();
                        ByteBuffer buf = buffers.get((Integer) channelKey.attachment());
                        try {
                            int readBytes = clientChannel.read(buf);
                            if(readBytes==-1) continue;// read=-1代表沒有東西讀取,這時候沒有必要輸出。
                            System.out.print("ChannelID:" + channelKey.attachment()+ "\tInput");
                            System.out.print("\tReadBytes:" + readBytes);
                            System.out.println("\tContent:" + getString(buf));//getString採用絕對讀寫,不影響讀寫指針。
                        } catch (Exception e) {
                            clientChannel.close();
                            channelKey.cancel();
                            System.out.println("channelID:"+channelKey.attachment()+"\tClosed");
                            continue;
                        }

                    }
                    if (channelKey.isWritable()) {// 寫數據
                        // System.out.println(channelKey.attachment()+ " - 寫數據事件");
                        SocketChannel clientChannel = (SocketChannel) channelKey.channel();
                        ByteBuffer buf = buffers.get((Integer) channelKey.attachment());
                        /*以下這段代碼是實現迴文的主要代碼
                        *buf.position----------指向下一個要寫入的位置
                        *buf.get(index)------------獲取當前最後一個字符。
                         */
                        if (buf.position() > 0) {//該判斷保證不會超出ByteBuffer的下界限,上界限沒時間搞。
                            if (buf.get(buf.position() - 1) == '\n') {
                                buf.flip();
                                clientChannel.write(buf);
                                buf.clear();
                            }
                        }

                    }
                    // 必須removed 否則會繼續存在,下一次循環還會進來,
                    // 注意removed 的位置,針對一個.next() remove一次
                    it.remove();
                }
            }
        } catch (Exception e) {
            // TODO: handle exception
            System.out.println("Error - " + e.getMessage());
            e.printStackTrace();
        } finally {
            if (selector != null) {
                try {
                    selector.close();
                } catch (IOException e) {
                    System.out.println("Error-" + e.getMessage());
                    e.printStackTrace();
                }
            }
        }
    }

    //一個實用的類,用於轉換字符串。
    public static String getString(ByteBuffer buffer) {
        String string = "";
        try {
            for (int i = 0; i < buffer.position(); i++) {
                string += (char) buffer.get(i);//使用絕對定位,不會移動指針。
            }
            return string;
        } catch (Exception ex) {
            ex.printStackTrace();
            return "";
        }
    }

    public static void main(String[] args) throws Exception {
        InetSocketAddress address;
        switch (args.length) {
            case 0://默認IP,默認端口,用於測試
                address = new InetSocketAddress("127.0.0.1", 9999);
                break;
            case 1://指定綁定端口(IP綁定到所有端口)
            {
                //設置端口值(如果端口參數的格式不正確,則拋出一個NumberFormatException)
                int port = Integer.parseInt(args[0]);
                address = new InetSocketAddress(9999);
                break;
            }
            case 2://正常情況,如果有問題,則產生異常
            {
                String ip = args[0];
                int port = Integer.parseInt(args[0]);
                address = new InetSocketAddress(ip, port);
            }
            default://其他情況
                System.err.println("Usage: " + EchoServer.class.getSimpleName() + " <port>");
                return;
        }

        new EchoServer(address).start();
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章