基于BIO模型实现多人聊天室

第一版

服务端实现:

public class ChatServer {
    private int DEFAULT_PORT = 8888;
    private final String QUIT = "quit";
    private ServerSocket serverSocket;
    private Map<Integer, Writer> connectedClients;

    public ChatServer() {
        connectedClients = new HashMap<>();
    }
    public synchronized void addClient(Socket socket) throws IOException {
        if(socket==null) return;
        int port = socket.getPort();
        BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(socket.getOutputStream())
        );
        connectedClients.put(port,writer);
        System.out.println("客户端["+port+"]已连接到服务器");
    }
    public synchronized void removeClient(Socket socket) throws IOException {
        if(socket==null) return;
        int port = socket.getPort();
        if(connectedClients.containsKey(port)){
            connectedClients.get(port).close();
        }
        connectedClients.remove(port);
        System.out.println("客户端["+port+"]已断开连接");
    }

    public synchronized void forwardMessage(Socket socket,String fwdMsg){
        connectedClients.forEach((id,writer)->{
            if(!id.equals(socket.getPort())){
                try {
                    writer.write(fwdMsg);
                    writer.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public synchronized void close(){
        if(serverSocket!=null){
            try {
                serverSocket.close();
                System.out.println("关闭ServerSocket");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

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

    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 static void main(String[] args) {
        ChatServer server = new ChatServer();
        server.start();
    }
}

服务端分配线程服务用户:

public class ChatHandler implements Runnable{
    private ChatServer server;
    private Socket socket;
    public ChatHandler(ChatServer server,Socket socket){
        this.server = server;
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 存储新上线用户
            server.addClient(socket);

            // 读取用户发送的消息
            BufferedReader reader = new BufferedReader(
                    new InputStreamReader(socket.getInputStream())
            );
            String msg = null;
            while ((msg = reader.readLine())!=null){
                String fwdMsg = "客户端["+socket.getPort()+"]:"+msg;
                System.out.println(fwdMsg);
                if(server.readyToQuit(msg)) break;
                // 将消息转发给聊天室里在线的其他用户
                server.forwardMessage(socket,fwdMsg+"\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                server.removeClient(socket);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

用户端:

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 send(String msg) throws IOException {
        if(!socket.isOutputShutdown()){
            writer.write(msg+"\n");
            writer.flush();
        }
    }

    // 从服务器接收消息
    public String recieve() throws IOException {
        String msg = null;
        if(!socket.isInputShutdown()){
            msg = reader.readLine();
        }
        return msg;
    }

    // 检查用户是否准备退出
    public boolean readyToQuit(String msg){
        return QUIT.equals(msg);
    }

    public void close(){
        if(writer!=null){
            try {
                System.out.println("关闭socket");
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public void start(){
        try {
            // 创建socket
            socket = new Socket(DEFAULT_SERVER_HOST, DEFAULT_SERVER_PORT);
            // 创建IO流
            reader = new BufferedReader(
                    new InputStreamReader(socket.getInputStream())
            );
            writer = new BufferedWriter(
                    new OutputStreamWriter(socket.getOutputStream())
            );
            // 处理用户的输入
            new Thread(new UserInputHandler(this)).start();
            // 读取服务器转发的消息
            String msg = null;
            while((msg = recieve())!=null){
                System.out.println(msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            close();
        }
    }

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

用户线程处理IO输入:

public class UserInputHandler implements Runnable{
    private ChatClient chatClient;

    public UserInputHandler(ChatClient chatClient){
        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.readyToQuit(input)){
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

第二版:使用线程池改进

服务端,创建线程使用线程池:

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<>();
    }
    public synchronized void addClient(Socket socket) throws IOException {
        if(socket==null) return;
        int port = socket.getPort();
        BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(socket.getOutputStream())
        );
        connectedClients.put(port,writer);
        System.out.println("客户端["+port+"]已连接到服务器");
    }
    public synchronized void removeClient(Socket socket) throws IOException {
        if(socket==null) return;
        int port = socket.getPort();
        if(connectedClients.containsKey(port)){
            connectedClients.get(port).close();
        }
        connectedClients.remove(port);
        System.out.println("客户端["+port+"]已断开连接");
    }

    public synchronized void forwardMessage(Socket socket,String fwdMsg){
        connectedClients.forEach((id,writer)->{
            if(!id.equals(socket.getPort())){
                try {
                    writer.write(fwdMsg);
                    writer.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public synchronized void close(){
        if(serverSocket!=null){
            try {
                serverSocket.close();
                System.out.println("关闭ServerSocket");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

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

    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();
        }
    }

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

原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/%e5%9f%ba%e4%ba%8ebio%e6%a8%a1%e5%9e%8b%e5%ae%9e%e7%8e%b0%e5%a4%9a%e4%ba%ba%e8%81%8a%e5%a4%a9%e5%ae%a4/

(0)
彭晨涛彭晨涛管理者
上一篇 2020年2月6日
下一篇 2020年2月6日

相关推荐

  • 谈谈Java中的Iterator

    摘要 Iterator的作用? Iterator和Enumeration的区别? Iterator和ListIterator的区别? Iterator和foreach的关联? It…

    Java 2019年12月6日
    0180
  • Java线程池详解

    线程池就是享元模式和生产者消费者模式的应用 动手实现线程池 步骤1:自定义拒绝策略接口 @FunctionalInterface // 拒绝策略 interface RejectP…

    2020年2月3日
    0310
  • AQS及其应用ReentrantLock源码分析

    AQS原理 概述 全称是 AbstractQueuedSynchronizer(抽象同步队列),是阻塞式锁和相关的同步器工具的框架。 特点:+ 用 state 属性来表示资源的状态…

    2020年2月4日
    0120
  • JavaIO-缓冲流与转换流

    缓冲流 概述 缓冲流,也叫高效流,是对4个基本的FileXxx 流的增强,所以也是4个流,按照数据类型分类: 字节缓冲流:BufferedInputStream,BufferedO…

    Java 2020年2月4日
    0120
  • HashMap源码分析

    HashMap是java中非常常见的一个数据结构,在这篇文章里,我依然以Map中的操作为导向来循序渐进研究HashMap中的源码,阅读这篇文章需要的前置知识有: 弱平衡的二叉查找树…

    Java 2020年2月12日
    0220
  • LinkedHashMap源码分析

    总结 总结放前面防止太长不看: LinkedHashMap继承自HashMap,为Entry额外维护了两个属性:before和after,可以按照节点的插入顺序或者访问顺序为Ent…

    Java 2020年2月19日
    0550
  • 详解java中的unicode编码(码点)

    致谢: 本文参考网页:Unicode字符集以及UTF-8,UTF-16编码的总结 - vcj1009784814的博客 - CSDN博客 Unicode unicode的码点从U+…

    2019年11月25日
    0760
  • Java中的四种内部类

    我发现最近真是越来越没有东西写了。。。不可能天天学习新知识啊,最近在复习阶段了,复习的东西大多数是博客里写过的/(ㄒoㄒ)/ 复习Java基础的时候认真看了一下Java的内部类,这…

    Java 2020年5月23日
    0100
  • Java中的File类以及IO流

    File 类 概述 java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作。 构造方法 public File(String path…

    Java 2020年2月4日
    0150
  • Java之UDP编程

    DatagramSocket概述 上次在Java网络套接字Socket编程那篇博客里只写了Socket和ServerSocket,即TCP通信,这次来补充一下UDP通信。 和Soc…

    Java 2020年3月11日
    0810

发表回复

登录后才能评论