多人聊天室BIO

为了熟练运用BIO, NIO, AIO。先使用BIO实现一个控制台上的多人聊天室。主要的实现方法就是客户端发送消息到服务端,再由服务端进行消息的转发(转发到服务端所监听到的客户端)

目录结构

  • client
    • ChatClient
    • UserInputHandler
  • server
    • ChatHndler
    • ChatServer

client部分

ChatClient

这部分主要是Socket交互功能的实现

public class ChatClient {
    private final String DEFAULT_SERVER_HOST = "127.0.0.1";
    private final int DEFAULT_SERVER_PORT = 8888;
    private final String QUIT = "quit";

    private Socket socket;
    private BufferedReader reader;
    private BufferedWriter writer;

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

    public String receive() throws IOException {
        String str = null;
        if (!socket.isInputShutdown()){
            str = reader.readLine();
        }
        return str;
    }

    public boolean readyToQuit(String str){
        return str.equals(QUIT);
    }

    public void close(){
        try {
            if (writer != null){
                System.out.println("server closed");
                writer.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start(){
        try {
            socket = new Socket(DEFAULT_SERVER_HOST, DEFAULT_SERVER_PORT);
            reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

            new Thread(new UserInputHandler(this)).start();
            String str = null;
            while ((str = receive()) != null){
                System.out.println(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            close();
        }
    }

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

这部分主要是监听键盘输入的线程

public class UserInputHandler implements Runnable{
    private ChatClient client;

    public UserInputHandler(ChatClient client) {
        this.client = client;
    }

    @Override
    public void run() {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
            while (true){
                String input = reader.readLine();
                client.sendMessage(input);
                if (client.readyToQuit(input)){
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Server部分

ChatServer

这部分主要是实现了Socket交互,使用Map数组存储客户端以及其输入流进行转发消息,在可能存在线程中数据读写时发生错误的方法只是简单的添加了synchronized关键字。另外使用线程池监听客户端,线程池满时阻塞。

public class ChatServer {
    private int DEFAULT_PORT = 8888;
    private final String QUIT = "quit";

    private ExecutorService executorService;
    private ServerSocket serverSocket;
    private Map<Integer, Writer> connectedClients;

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

    /**
     * 添加客户端
     * @param socket
     * @throws IOException
     */
    public synchronized void addClient(Socket socket) throws IOException {
        if (socket != null){
            int port = socket.getPort();
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            connectedClients.put(port, writer);
            System.out.println("client[" + port +"]connected server");
        }
    }

    /**
     * 客户端关闭
     * @param socket
     * @throws IOException
     */
    public synchronized void removeClient(Socket socket) throws IOException {
        if (socket != null){
            int port = socket.getPort();
            if (connectedClients.containsKey(port)){
                connectedClients.get(port).close();
            }
            connectedClients.remove(port);
            System.out.println("client[" + port +"]connect finished");
        }
    }

    /**
     * 转发消息
     * @param socket
     * @param fwdMsg
     * @throws IOException
     */
    public synchronized void forwardMessage(Socket socket, String fwdMsg) throws IOException {
        for (Integer id: connectedClients.keySet()){
            if (!id.equals(socket.getPort())){
                Writer writer = connectedClients.get(id);
                writer.write(fwdMsg);
            }
        }
    }

    public boolean readyToQuit(String msg){
        return QUIT.equals(msg);
    }

    private synchronized void close(){
        try {
            if (serverSocket != null){
                serverSocket.close();
                System.out.println("serverSocket closed");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start(){
        try {
            // 登台监听端口
            serverSocket = new ServerSocket(DEFAULT_PORT);
            System.out.println("server start, listen: " + DEFAULT_PORT + "...");

            while (true){
                // 等待客户端连接
                Socket socket = serverSocket.accept();
                // 不用没进来一个用户都要单独创建一个线程,浪费资源
                // new Thread(new ChatHandler(this, socket)).start();
                executorService.execute(new ChatHandler(this, socket));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            close();
        }
    }

    public static void main(String[] args) {
        ChatServer server = new ChatServer();
        server.start();
    }
}
ChatHandler

这部分线程主要实现数据的接收以及转发功能。


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 reader = new BufferedReader(
                    new InputStreamReader(socket.getInputStream())
            );
            String msg = null;
            while ((msg = reader.readLine()) != null){
                if (chatServer.readyToQuit(msg)){
                    break;
                }
                String fwdMsg = "client[" + socket.getPort() + "]: " + msg + "\n";
                System.out.println(fwdMsg);
                chatServer.forwardMessage(socket, fwdMsg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                System.out.println("client[" + socket.getPort() +"] closed");
                chatServer.removeClient(socket);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值