Java BIO多人聊天室

基於上篇NIO的多人聊天室,這篇將用BIO也實現一遍

首先是服務端的設計:
/**
 * @author Jing
 * @create 2020/5/17
 */
public class ChatServer {

    private int DEFAULT_PORT = 8888;
    private final String Quit = "quit";

    private ServerSocket serverSocket;

    private Map<Integer, Writer> connectedClients; // 這裏用map保存端口,和writer的映射

    public ChatServer(){
        connectedClients = new HashMap<>();
    }

    public synchronized void addClient(Socket socket) throws IOException {
        if(socket != null){
            BufferedWriter writer = new BufferedWriter(
                    new OutputStreamWriter(socket.getOutputStream())
            );
            connectedClients.put(socket.getPort(),writer);
            System.out.println("客戶端:【"+socket.getPort()+"】已經連接到服務器了");
        }
    }

    public synchronized void removeClient(Socket socket) throws IOException {
        if(socket != null){
            if(connectedClients.containsKey(socket.getPort())){
                connectedClients.get(socket.getPort()).close();
                connectedClients.remove(socket.getPort());
            }
        }
        System.out.println("客戶端: "+socket.getPort()+"已經斷開了連接");
    }

    public synchronized void broadCast(Socket socket,String msg) throws IOException { // 服務器輪流發送信息給所有的客戶端
        for(Integer id : connectedClients.keySet()){
            if(id != socket.getPort()){
                Writer writer = connectedClients.get(id);
                writer.write(msg);
                writer.flush();
            }
        }
    }

    public synchronized void close(){
        if(serverSocket != null){
            try {
                serverSocket.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }

    public void start(){

        try {
            serverSocket = new ServerSocket(DEFAULT_PORT);
            System.out.println("啓動服務器,監聽端口 :"+DEFAULT_PORT);
            while(true){
                Socket socket = serverSocket.accept();
                // 創建chatHandler線程
                new Thread(new ChatHandler(this,socket)).start();

            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            close();
        }

    }
    public boolean checkToQuit(String msg){
        return msg.equals(Quit);
    }

    public static void main(String[] args) {
        new ChatServer().start();
    }
}
下面這個線程專門用來讀取各個客戶端發送過來的消息,對消息進行廣播
public class ChatHandler implements Runnable {

    private ChatServer chatServer;
    private Socket socket;

    public ChatHandler(ChatServer chatServer, Socket socket) {
        this.chatServer = chatServer;
        this.socket = socket;
    }


    @Override
    public void run() {
        try {
            chatServer.addClient(socket);
            BufferedReader bufferedReader = new BufferedReader(
                    new InputStreamReader(socket.getInputStream())
            );
            String msg = null;
            while((msg = bufferedReader.readLine()) != null){
                String content = "客戶端【"+socket.getPort()+"】:"+msg+"\n";
                System.out.println(content);
                chatServer.broadCast(socket,content);
                if(chatServer.checkToQuit(msg)){
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                chatServer.removeClient(socket);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}


以上是服務端的代碼,現在編寫客戶端的代碼

public class ChatClient {

    private final int DEFAULT_PORT = 8888;
    private final String Quit = "quit";
    private final String DEFAULT_HOST = "127.0.0.1";

    private Socket socket;
    private BufferedReader bufferedReader;
    private BufferedWriter bufferedWriter;

    public ChatClient(){}

    public void send(String msg) throws IOException {
        if(!socket.isOutputShutdown()){
            bufferedWriter.write(msg+"\n");
            bufferedWriter.flush();
        }
    }

    // 從服務端接收消息
    public String receive() throws IOException {
        String msg = null;
        if(!socket.isInputShutdown()){
            msg = bufferedReader.readLine();
        }
        return msg;
    }

    // 檢查用戶退出
    public boolean isQuit(String msg){
        return Quit.equals(msg);
    }

    public void close(){
        if(bufferedWriter != null){
            try {
                bufferedWriter.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }

    public void start(){  
        try {
            socket = new Socket(DEFAULT_HOST,DEFAULT_PORT);
            bufferedWriter = new BufferedWriter(
                    new OutputStreamWriter(socket.getOutputStream())
            );
            bufferedReader = new BufferedReader(
                    new InputStreamReader(socket.getInputStream())
            );
            // 這裏另開一個線程用於處理用戶的輸入
            new Thread(new UserInputHandler(this,socket)).start();
            // 讀取服務器轉發的消息
            String msg = null;
            while((msg = receive())!= null){
                System.out.println(msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            close();
        }
    }

    public static void main(String[] args) {
        new ChatClient().start();
    }

}

主線程用來接收消息,另外開一個線程專門處理客戶的輸入:

public class UserInputHandler implements Runnable{

    private ChatClient chatClient;

    public UserInputHandler(ChatClient chatClient, Socket socket){
        this.chatClient = chatClient;
    }


    @Override
    public void run() {
        try {
            BufferedReader consoleReader = new BufferedReader(
                    new InputStreamReader(System.in)
            );
            while(true){
                String input = consoleReader.readLine();
                chatClient.send(input);
                if(chatClient.isQuit(input)) break;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


然後,我們可以看一下,執行的效果,我開了兩個客戶端:

這是服務端:
在這裏插入圖片描述
這是客戶端1:
在這裏插入圖片描述
這是客戶端2:
在這裏插入圖片描述


對服務端進一步改進,加入線程池(主要對服務端三個部分進行進一步改進,其他的地方依舊不變):

private ExecutorService executorService;
public ChatServer(){
        connectedClients = new HashMap<>();
        executorService = Executors.newFixedThreadPool(10);
}
public void start(){

        try {
            serverSocket = new ServerSocket(DEFAULT_PORT);
            System.out.println("啓動服務器,監聽端口 :"+DEFAULT_PORT);
            while(true){
                Socket socket = serverSocket.accept();
                // 創建chatHandler線程
                executorService.execute(new ChatHandler(this,socket));

            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            close();
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章