基於 java nio 長連接實現的聊天室

TCP長連接與短連接的區別

基於 java nio 長連接實現的聊天室,如果併發量大的話,可能會有線程問題。

服務端代碼

package com.lp.io.socket;

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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

public class LpNioServerSocket {

    //線程安全
    private static List<SocketChannel> channels = Collections.synchronizedList( new ArrayList<SocketChannel>() );

    public static void main(String[] args) {

        HandlerSelectionKey handler = new HandlerHandlerSelectionKeyImpl();

        try {
            //創建 ServerSocketChannel
            ServerSocketChannel server = ServerSocketChannel.open();
            server.configureBlocking(false);
            server.bind(new InetSocketAddress("localhost", 12345));
            //創建 Selector
            Selector selector = Selector.open();
            server.register(selector, SelectionKey.OP_ACCEPT);
            //死循環,持續接收 客戶端連接
            while(true) {
                //selector.select(); 是阻塞方法
                int keys = selector.select();
                if(keys > 0) {
                    Iterator<SelectionKey> it = selector.selectedKeys().iterator();
                    while(it.hasNext()) {
                        SelectionKey key = it.next();
                        it.remove();
                        //處理 SelectionKey
                        handler.handler(key, selector);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * SelectionKey 處理接口
     *
     */
    public static interface HandlerSelectionKey {

        public void handler(SelectionKey key, Selector selector) throws IOException;

    }

    /**
     * SelectionKey 接口 實現類
     *
     */
    public static class HandlerHandlerSelectionKeyImpl implements HandlerSelectionKey {

        @Override
        public void handler(SelectionKey key, Selector selector) throws IOException {
            int keyState = selectionKeyState(key);
            switch (keyState) {
            case SelectionKey.OP_ACCEPT:
                ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                accept(serverSocketChannel, selector);
                break;
            case SelectionKey.OP_READ:
                SocketChannel readSocketChannel = (SocketChannel) key.channel();
                read(readSocketChannel, selector);
                break;
            }
        }
        /**
         * 獲取 SelectionKey 是什麼事件
         * @param key
         * @return
         */
        private int selectionKeyState(SelectionKey key) {
            if(key.isAcceptable()) {
                return SelectionKey.OP_ACCEPT;
            } else if(key.isReadable()) {
                return SelectionKey.OP_READ;
            }
            return -1;
        }

        /**
         * 接口客戶端請求
         * @param serverSocketChannel
         * @param selector
         * @throws IOException
         */
        private void accept(ServerSocketChannel serverSocketChannel, Selector selector) throws IOException {
            SocketChannel socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(false);
            channels.add(socketChannel);
            //將 channel 註冊到  Selector
            socketChannel.register(selector, SelectionKey.OP_READ);
        }

        /**
         * 讀取客戶端發送過來的信息
         * @param socketChannel
         * @param selector
         * @throws IOException
         */
        private void read(SocketChannel socketChannel, Selector selector) throws IOException {
            ByteBuffer readBuffer = ByteBuffer.allocate(8192);
            int readBytes = socketChannel.read(readBuffer);
            String msg = "";//客戶端發送來的消息
            if(readBytes > 0) {
                 msg = new String(readBuffer.array(), 0, readBytes);
                System.out.println("客戶端發送來的消息");
                System.out.println(msg);
            }
            write(socketChannel, msg);
        }

        /**
         * 響應客戶端請求
         * @param socketChannel
         * @param selector
         * @throws IOException
         */
        private void write(SocketChannel socketChannel, String msg) throws IOException {
            msg = "遊客" + socketChannel.hashCode()+ "\r\n    " + msg;
            //響應消息
            byte[] responseByte = msg.getBytes();
            ByteBuffer writeBuffer = ByteBuffer.allocate(responseByte.length);
            writeBuffer.put(responseByte);
            writeBuffer.flip();
            //響應客戶端
            for(int i=0; i<channels.size(); i++) {
                if(!socketChannel.equals(channels.get(i))) {
                    channels.get(i).write(writeBuffer);
                }
            }
        }

    }


}

客戶端代碼

package com.lp.io.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;

public class LpSocketClient1 {

    /**
     * @param args
     * @throws IOException 
     * @throws UnknownHostException 
     * @throws InterruptedException 
     */
    public static void main(String[] args) throws UnknownHostException, IOException, InterruptedException {

        final Socket socket = new Socket("localhost", 12345);
        Thread inT = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while(true) {
                        InputStream inputStream = socket.getInputStream();
                        byte[] b = new byte[8192];
                        int readSize = inputStream.read(b);
                        System.out.println(new String(b,0,readSize));
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        inT.start();
        while(true) {
            InputStreamReader stdin = new InputStreamReader(System.in);//鍵盤輸入 
            BufferedReader bufin = new BufferedReader(stdin); 
            String str = bufin.readLine(); 
            System.out.println(str);

            OutputStream outStream = socket.getOutputStream();
            outStream.write(str.getBytes());
            outStream.flush();
        }

    }

}

啓動服務端代碼,然後啓動多個客戶端。在某個客戶端代碼控制檯輸入英文(輸入中文亂碼),點擊回車,可以看到其他客戶端控制檯有信息輸出。

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