使用NIO搭建一個聊天室

搭建一個一臺服務端多臺客戶端連接的聊天室

首先梳理一下核心點:

      服務端有一個本地Map,記錄了所有連接的信息,以達到給所有人發送的效果

編碼時的公共流程:

1.先打開各自的channel和selector,然後channel註冊到selector上,註冊事件C:CONNECT;S:ACCEPT

2.然後進行一個死循環,第一行是selector.select()該事件會阻塞自己知道感興趣的事件發生。

3.事件發生後進行selector.selectedKeys(),返回一個Set<SelectionKey>,因爲可能同時發生多個感興趣的事件。

4.進行各自的操作

5.在操作完之後需要清空調用clear()對set集合清空。

客戶端:

isConnectable()與isReadable()是處於同一級的,分別監聽連接和讀取

連接:isisConnectable->isConnectionPending->finishConnect纔算正式建立好連接;再把建立連接成功的信息寫到通道里.write(buffer);在開啓一個線程一直監聽本地鍵盤輸入;最後把讀事件註冊到selector上

讀取時:從管道中.read(buffer)即可讀取到buffer中

服務端:

例子監聽了Accept和Read事件:

當有連接的時候:吧該管道感興趣的時間設爲讀並放進本地map中

當管道有數據可讀的時候:先從管道讀數據,然後遍歷map向所有連接端寫數據

源碼:

server.java

package com.rikka.nio;

import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.*;

/**
 * @Author Yan1less
 * @Date 2019/4/21 12:55
 * @Description 實現分發給所有客戶端
 **/
public class NioServer {

    private static Map<String,SocketChannel> clientMap = new HashMap<>();

    //服務器端只有一個線程
    public static void main(String[] args) throws Exception{
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        ServerSocket serverSocket = serverSocketChannel.socket();
        serverSocket.bind(new InetSocketAddress(8899));


        Selector selector = Selector.open();
        //把channel註冊到selector上
        //ServerSocketChannel關注連接事件
        serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);

        while (true){
            try {
                //這個方法會一直阻塞,直到發生了其感興趣的事件
                selector.select();

                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                selectionKeys.forEach(selectionKey -> {
                    //SocketChannel關注讀取事件
                    final SocketChannel client;
                    try{
                        if(selectionKey.isAcceptable()){
                            //這裏爲什麼能強行向下轉型呢?是因爲上面只把serverSocketChannel註冊到了OP_ACCEPT事件上
                            ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
                            //到這一步ServerSocketChannel的任務完成了,我們接下來只需要從client進行操作
                            client = server.accept();
                            client.configureBlocking(false);
                            client.register(selector,SelectionKey.OP_READ);

                            String key = "【"+UUID.randomUUID().toString() +"】";
                            clientMap.put(key,client);
                        }else if(selectionKey.isReadable()){
                            client = (SocketChannel)selectionKey.channel();
                            //一個不夠用咋整
                            ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                            int count = client.read(readBuffer);
                            if(count>0){
                                readBuffer.flip();

                                Charset charset = Charset.forName("utf-8");
                                String receivedMessage = String.valueOf(charset.decode(readBuffer).array());

                                System.out.println(client + ": " + receivedMessage);

                                String senderKey = null;
                                for(Map.Entry<String,SocketChannel> entry : clientMap.entrySet()){
                                    if(client == entry.getValue()){
                                        senderKey = entry.getKey();
                                        break;
                                    }
                                }

                                for(Map.Entry<String,SocketChannel> entry : clientMap.entrySet()){
                                    SocketChannel value = entry.getValue();
                                    ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
                                    writeBuffer.put((senderKey+" : "+receivedMessage).getBytes());
                                    //把數據放到buffer裏稱爲讀
                                    writeBuffer.flip();
                                    value.write(writeBuffer);
                                }
                            }

                        }
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                });
                selectionKeys.clear();
            }catch (Exception ex){
                ex.printStackTrace();
            }
        }

    }
}

client.java

package com.rikka.nio;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.time.LocalDateTime;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Author Yan1less
 * @Date 2019/4/21 13:49
 * @Description TODO
 **/
public class NioClient {
    public static void main(String[] args) throws Exception {
        try{
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);

            Selector selector = Selector.open();
            socketChannel.register(selector,SelectionKey.OP_CONNECT);
            socketChannel.connect(new InetSocketAddress("127.0.0.1",8899));

            while (true){
                selector.select();

                Set<SelectionKey> selectionKeys = selector.selectedKeys();

                selectionKeys.forEach(selectionKey -> {
                    if(selectionKey.isConnectable()){
                        SocketChannel client = (SocketChannel) selectionKey.channel();
                        //判斷這個連接是不是正在進行的狀態
                        if(client.isConnectionPending()){
                            try {
                                //這樣連接纔算建立好了
                                client.finishConnect();
                                ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
                                writeBuffer.put((LocalDateTime.now()+"連接成功").getBytes());
                                writeBuffer.flip();
                                client.write(writeBuffer);
                                ExecutorService executorService = Executors.newSingleThreadExecutor(Executors.defaultThreadFactory());
                                executorService.submit(()->{
                                   while(true){
                                       try{
                                           writeBuffer.clear();
                                           InputStreamReader input = new InputStreamReader(System.in);
                                           BufferedReader br = new BufferedReader(input);
                                           String sendMessage = br.readLine();
                                           writeBuffer.put(sendMessage.getBytes());
                                           writeBuffer.flip();
                                           client.write(writeBuffer);
                                       }catch (Exception ex){
                                           ex.printStackTrace();
                                       }
                                   }
                                });
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }

                        try {
                            client.register(selector,SelectionKey.OP_READ);
                        } catch (ClosedChannelException e) {
                            e.printStackTrace();
                        }
                    }else if(selectionKey.isReadable()){
                        SocketChannel client = (SocketChannel) selectionKey.channel();
                        ByteBuffer readBuffer =  ByteBuffer.allocate(1024);
                        try {
                            int read = client.read(readBuffer);
                            if(read>0){
                                String receivedMesage = new String(readBuffer.array(),0,read);
                                System.out.println(receivedMesage);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }


                    }

                });
                selectionKeys.clear();
            }


        }catch (Exception ex){

        }

    }
}

 

Tips:

1.每次新建一個channel的時候都要設置其configureBlocking(false)爲非阻塞

2.無論是C或是S通過SocketChannel.open()方法獲得的channel通道都只是爲了鏈接,真正的數據傳輸通道都是通過selectionKey.channel()獲得的(對於server來說需要在進行accept()方法來獲得實際操作channel,注:ServerSocketChannel的作用也是爲了獲得SocketChannel),該方法需要強制類型轉換,轉換成SocketChannel

3.channel.read(buffer)是把buffer讀取出來

 

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