NIO的基本的操作

服務器的基本寫法:

    //創建一個Channel,這個服務器要綁定端口和Ip地址,這個客戶端只連接這個Ip和端口就可了
        ServerSocketChannel channel=ServerSocketChannel.open();
        //綁定端口和地址
        channel.bind(new InetSocketAddress(InetAddress.getByName("192.168.213.1"),8888));
        //設置爲異步模式,也就是不阻塞的模式
        channel.configureBlocking(false);
        while(true){
            //進行監聽,沒有客戶端連接返回爲null
            SocketChannel ss=channel.accept();
            //打印
            System.out.println("server:"+ss);
            //設置一下時間的間隔
            Thread.sleep(2000);
        }

客戶端的基本寫法:

    public static void main(String[] args) throws Exception {
        //創建一個客戶端的Socket的Channel的接口,這個對象的創建也是要通過靜態方法Open來創建的,同理這個方法只是打開了
        //連接,還沒有綁定到具體的Server
        SocketChannel sc=SocketChannel.open();
        //設置一下我們的客戶端的連接方式
        sc.configureBlocking(false);
        //進行具體的綁定操作
        SocketAddress address=new InetSocketAddress("localhost",8888);
        //進行連接服務器的操作,同時這個方法返回的這個客戶端是否成功連接到Socket上
        //如果是阻塞模式,那麼則ture表示已經成功連接,對於非阻塞模式的返回的false表示正在連接,因爲服務器響應要一點時間
        boolean b=sc.connect(address);
        //finishConnect()表示方法的是完成我們連接的處理
        //也就是說這個方法會嘗試去連接,直到成功
        while(sc.finishConnect()==false){
            System.out.println("正在連接...");
        }
        System.out.println("連接已經完成...");
        System.out.println("client:"+b);

    }

高級的Selector的基本的操作:
我們的服務器的練習代碼如下:

public class MySelectionServerChannel {
    public static void main(String[] args) throws Exception {
        // 先創建一個通道挑選器對象,在這個NIO當中大部分的對象都是用的是Open方法來創建的,同理下面的這個方法也是這樣子的
        // 這個通道挑選器對象一創建就會維護了三個Key對象集合
        Selector selector = Selector.open();
        // 創建一個服務器Channel的通道,使用靜態的Open方法來創建一個已經連接的ServerSocketChannel的對象,
        // 但是他還沒有綁定
        ServerSocketChannel ssc = ServerSocketChannel.open();
        // 進行綁定我們的服務器的端口和ip地址,但是這個ip地址只能是本地的ip地址,不能是別的地方的地址,因爲在我們的
        // Socket編程中的SocketServer只叫我們的綁定一個端口號就可以了,所以說不能這個地址只能是本地的地址
        ssc.bind(new InetSocketAddress("localhost", 8888));
        // 進行配置一下我們的NIO的通信的方式,是否爲阻塞模式,在我們的java io當中的大部分有關IO操作都是阻塞的
        // 所以他的性能有點low,因爲如果我們的想要做多件事的話就只能開多個線程,但是在cpu的在多個線程之間變換的開銷
        // 是相當的大的,所以NIO的主要就是用非阻塞的模式進行開發
        // 方法解釋:ture爲阻塞,false:爲非阻塞
        ssc.configureBlocking(false);

        // 把這個通道向我們的Selector來註冊,同時向我們的Selector註冊的通道一定要是非阻塞的可以
        // 同時在把我們的通道向我們的Selector註冊時要指定我們的這個通道的動作key(也就是感興趣的事件)
        // 目的是爲了在我們的Selector進行挑選select()時把一遍一遍的輪詢(while(true)),如果找到我們的這個事件和我們的通道
        // 註冊時的事件一致,也就是事件發生時,就會把我們的發送這個這個key給挑選形成一個SelectedKey
        // 這個註冊的操作op只有4種,都是在我們的SelectionKey中的成員,分別是OP_ACCEPT,OP_CONNECT,OP_READ,OP_WRITE
        // 但是對於我們的服務器來說只會用到一個事件OP_ACCEPT
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        // 客戶端的Channel
        SocketChannel ss = null;
        // 定義我們的客戶的字節緩衝區
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while (true) {
            // 進行挑選工作,這個方法是阻塞的,所以在通道挑選器的所有通道只有發生一個事件時,都會向下走
            selector.select();
            // 進行取出我們的相應的key,返回的是selectedKey的集合
            Set<SelectionKey> selKeys = selector.selectedKeys();
            // 進行取出相應的Key的操作
            for (Iterator<SelectionKey> it = selKeys.iterator(); it.hasNext();) {
                SelectionKey key = it.next();
                // 如果這個key(也就是這個通道)表示的連接的動作,這個是我們的服務器特有的方法
                if (key.isAcceptable()) {
                    // 得到我們的註冊的通道,同時這個通道是我們的服務器的通道
                    ServerSocketChannel sschannel = (ServerSocketChannel) key.channel();
                    // 進行不斷的客戶端接收請求
                    // 接收客戶端接收的請求,得到我們的客戶端的socketChannel對象
                    ss = sschannel.accept();
                    // 同時這個ss也要設置爲非阻塞的模式,不然會出現問題的
                    ss.configureBlocking(false);

                    // 然後把我們的客戶端的主要的SocketChannel也給註冊到我們的選擇器當中,
                    // 在我們的selector的選擇器當中可以進行標明三種感興趣事件
                    ss.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                    System.out.println("Server:我們的客戶端發送請求了!!!");

                }
                if (key.isConnectable()) {
                    ss = (SocketChannel) key.channel();
                    System.out.println("server:有發送了可以連接的對象...");
                }
                // 可以讀取客戶發送過來的數據
                if (key.isReadable()) {
                    ss = (SocketChannel) key.channel();
                    // 進行clear操作,目的是爲放開我們buffer的限制,同時重新開始讀入數據到字節緩衝區
                    // position=0;limit=capity;
                    buffer.clear();
                    // 進行讀取客戶發送過來的數據,當我們的服務器沒有發送數據過來時,這個read讀取數據返回值爲0,所以這個就有點用while就有點不好了
                    int len = ss.read(buffer);
                    if (len == -1) {
                        // 如果等於-1,說明數據已經讀取完成了,那麼這個通道也就可以關閉了
                        key.cancel();
                    } else if (len >= 0) {
                        // 進行拍板操作
                        buffer.flip();// limit=position;position=0;
                        // 把我們的字節緩衝區轉換成字節數據,目的是爲了轉換成字符串
                        byte byt[] = buffer.array();
                        String str = new String(byt, 0, buffer.limit());

                        System.out.println("server:接收我們的客戶端的數據爲:" + str);
                    }
                }
                // 給我們的客戶端發送數據
                if (key.isWritable()) {
                    ss = (SocketChannel) key.channel();
                    // 首先清空緩衝區的數據position=0;limit=capital;
                    buffer.clear();
                    // 給我們的緩衝區中加入數據
                    buffer.put("hello I'm Server".getBytes());
                    // 進行拍板,這樣子的話就不會寫入緩衝區當中不是我們的數據給客戶端了
                    buffer.flip();
                    // 進行把數據寫入緩衝區當中
                    ss.write(buffer);
                    // buffer.clear();
                    System.out.println("Server:正在給我們的客戶端寫入數據...");
                }
                // System.out.println(key);
                // 進行清空我們的SelectedKey,因爲他不會自動的清除
                it.remove();
                // Thread.sleep(2000);
            }

        }

    }
}

客戶端的操作如下:
簡單客戶端一:

public class MyClientSendTest {
    public static void main(String[] args) throws Exception {
        //創建一個客戶端的Socket的Channel的接口,這個對象的創建也是要通過靜態方法Open來創建的,同理這個方法只是打開了
        //連接,還沒有綁定到具體的Server
        SocketChannel sc=SocketChannel.open();
        //設置一下我們的客戶端的連接方式
        sc.configureBlocking(false);
        //進行具體的綁定操作
        SocketAddress address=new InetSocketAddress("localhost",8888);
        //進行連接服務器的操作,同時這個方法返回的這個客戶端是否成功連接到Socket上
        //如果是阻塞模式,那麼則ture表示已經成功連接,對於非阻塞模式的返回的false表示正在連接,因爲服務器響應要一點時間
        boolean b=sc.connect(address);
        //finishConnect()表示方法的是完成我們連接的處理
        //也就是說這個方法會嘗試去連接,直到成功
        while(sc.finishConnect()==false){
            System.out.println("正在連接...");
        }
        //進行給我們的服務器發送數據
        //創建字符緩衝區數據
        ByteBuffer buf=ByteBuffer.allocate(1024);
        int i=1;
        while(true){

            buf.put(("I'm Client!!!"+i+"\r\n").getBytes());
            i++;
            buf.flip();
            sc.write(buf);
            buf.clear();
        }

    }
}

簡單客戶端二:

public class MyClientSendTest2 {
    public static void main(String[] args) throws Exception {
        //下面我們的要用到是是我們的通道工具方法
        ReadableByteChannel inputChannel=Channels.newChannel(System.in);
        //創建一個和服務器連接的對象,下面的這個操作包括了連接服務器的操作了
        SocketChannel sc=SocketChannel.open(new InetSocketAddress("localhost",8888));
//      //把我們的客戶端進行連接服務器處理,上面的這一步已經做了下面的這一步了
//      sc.connect(remote)

        //進行分配一個緩衝區
        ByteBuffer buffer=ByteBuffer.allocate(1024);
        //現在進行數據的讀寫操作
        while(true){
            //把控制檯輸入的數據輸入緩衝區當中
            inputChannel.read(buffer);
            //把我們的緩衝區當中的數據進行拍板處理
            buffer.flip();
            //然後把我們的緩衝區中的數據寫到服務器當中
            sc.write(buffer);
            System.out.println("我們的已經向服務器發送了數據!!!");
            //進行清空緩衝區處理
            buffer.clear();
        }
    }
}

在我們的客戶端使用Selector的代碼如下:

public class MySelectionClientChannel {
    public static void main(String[] args) throws Exception {
        //創建一個客戶端通道挑選器,主要是負責特定的客戶端接口select的操作
        Selector selector=Selector.open();
        //創建一個客戶端的Socket的Channel的接口,這個對象的創建也是要通過靜態方法Open來創建的,同理這個方法只是打開了
        //連接,還沒有綁定到具體的Server
        SocketChannel sc=SocketChannel.open();
        //設置一下我們的客戶端的連接方式
        sc.configureBlocking(false);
        //進行具體的綁定操作
        SocketAddress address=new InetSocketAddress("localhost",8888);
        //進行連接服務器的操作,同時這個方法返回的這個客戶端是否成功連接到Socket上
        //如果是阻塞模式,那麼則ture表示已經成功連接,對於非阻塞模式的返回的false表示正在連接,因爲服務器響應要一點時間
        boolean b=sc.connect(address);
        //finishConnect()表示方法的是完成我們連接的處理
        //也就是說這個方法會嘗試去連接,直到成功
        while(sc.finishConnect()==false){
            System.out.println("正在連接...");
        }
        //下面是我們的客戶端口和服務器的主要的通信操作代碼,把我們的這個客戶端通道註冊到我們的通道挑選器當中,在
        //客戶端主要感興趣的地方操作是OP_CONNECT,OP_WRITE,OP_READ
        sc.register(selector,SelectionKey.OP_CONNECT|SelectionKey.OP_WRITE|SelectionKey.OP_READ);

        //創建一個buffer
        ByteBuffer buffer=ByteBuffer.allocate(1024);
        int count=1;
        //創建出具體的通道連接器

        //然後進行具體的挑選操作
        while(true){
            //進行挑選出key,這個方法是一具阻塞的方法
            selector.select();
            //如果select能夠往下運行,那麼說明我們的這個挑選器已經挑選出我們當前這個客戶端感興趣的事件了,那麼我們就可
            //拿到具體的事件key集合了
            Set<SelectionKey> selectKeys=selector.selectedKeys();
            //進行取出具體的SelectedKey
            for(Iterator<SelectionKey> it=selectKeys.iterator();it.hasNext();){
                //得到具體的Key
                SelectionKey selectedKey=it.next();
                //然後進行判斷出具體的key的操作
                if(selectedKey.isConnectable()){
                    //得到具體的具體通道對象
                    sc=(SocketChannel) selectedKey.channel();
                    System.out.println("Client:這裏有一個服務器發送過來的請求...");
                }
                //進行具體的讀數據
                if(selectedKey.isReadable()){
//                  String data=SocketChannelTools.readDataFromServer(sc,ByteBuffer.allocate(1024));
                    //先清空buffer
                    buffer.clear();
                    //然後把數據讀入這個緩衝區當中
                    sc.read(buffer);
                    //然後進行拍板操作
                    buffer.flip();

                    String data=new String(buffer.array(),0,buffer.limit());
                    System.out.println("Client:我已經讀取服務器發送了數據="+data);
                }
                //進行具體的寫數據
                if(selectedKey.isWritable()){
//                  SocketChannelTools.writeDataToServer(sc,"hello,I'm Client");
                    //先清空緩衝區數據
                    buffer.clear();
                    //然後把數據寫入這個緩衝區當中 
                    buffer.put(("hello,I'm Client"+count++).getBytes());
                    //然後進行拍板操作
                    buffer.flip();
                    //最後寫入給服務器
                    sc.write(buffer);
                    System.out.println("Client:我已經給服務器發送了數據");
                }
            }
        }

    }

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