Java学习篇(五)—— 第一个Java程序:Hello, 多人聊天室!

在源代码的基础上添加了服务器NIO,但是客户端的Jackson部分没有实现,只能运行测试代码,还增加了一个历史记录功能,进入服务器后能看到服务器的历史记录,过程中有一些问题放在了最后。

Java项目结构

原作者的项目写的很规范,项目结构是三层结构。用户的请求和客户端的响应都进行了封装,有了以下几个类:ResponseTypeResponseStatusRequestResponse。基本功能是聊天和传送文件,所以有两个用户发送内容的封装:MessageFileInfo。用户的属性包括:用户名、ID、密码、头像、性别。项目UI使用的是Java swing,所以UI和实体是直接绑定的。

服务器端实现对已经注册的用户和在线用户的新增,删除,登录,加载等,RequestProcessor实现了Runnable接口,对应一个客户端socket连接,Server的主进程开了一个单独的线程监听客户端请求。客户端的请求封装成一个RequestController接收Request,处理完后包装成Response返回。

客户端在UI里直接发送了Request,然后开了一个单独的线程处理服务器的回应。客户端可以选择用户进行私聊,还可以传输文件。

性能测试

源代码的服务器端,每来一个socket链接就开启一个新线程,简单易用,但在高并发下效率低。使用代码测试服务器端的性能:

展开代码 ```java public class ClientTest { private static final int TOTAL_CLIENTS = 5000; private static final String HOST = "localhost"; private static final int PORT = 6666; private static final AtomicInteger completed = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
    // 创建固定线程池(防止线程数过多导致系统资源耗尽)
    ExecutorService executor = Executors.newFixedThreadPool(5000);
    // 全局时间
    long globalStart = System.currentTimeMillis();

    for (int i = 0; i < TOTAL_CLIENTS; i++) {
        int clientId = i;
        executor.submit(() -> {
            try {
                // 客户端的开始时间
                long connectStart = System.nanoTime();

                SocketChannel socketChannel = SocketChannel.open();
                socketChannel.configureBlocking(true); // 也可以设置非阻塞
                socketChannel.connect(new InetSocketAddress(HOST, PORT));
                // 结束时间
                long connectEnd = System.nanoTime();
                long connectDurationMs = (connectEnd - connectStart) / 1_000_000;

                System.out.println("Client " + clientId + " connected in " + connectDurationMs + " ms");

                // 模拟长时间连接
                Thread.sleep(60_000);  // 保持 60 秒
                socketChannel.close();

                int done = completed.incrementAndGet();
                if (done == TOTAL_CLIENTS) {
                    long globalEnd = System.currentTimeMillis();
                    System.out.println("🚀 全部连接任务提交完成,用时:" + (globalEnd - globalStart) + " ms");
                }
            } catch (Exception e) {
                System.out.println("Client " + clientId + " failed: " + e.getMessage());
            }
        });
    }

    executor.shutdown();
    executor.awaitTermination(2, TimeUnit.MINUTES);
    System.out.println("测试完成");

}

}

</details>

结果显示:

![image](https://img2024.cnblogs.com/blog/3180563/202506/3180563-20250629145002386-1088448746.png)

这里可能的连接失败和连接方式没关心,Java的socket底层调用的是操作系统的socket,将最大连接数`backlog`传给操作系统的`listen(fd, backlog)`调用,不同的操作系统有不同的限制,并且最终使用的参数是`somaxconn`和`backlog`的最小值,所以即使设置的很大也不会是真实的,测试设置的是5000(现代linux的`somaxconn`是4096),测试结果看这个最大连接数大概在1000。

使用Java的性能分析工具,监督服务器的资源消耗如图所示:

![image](https://img2024.cnblogs.com/blog/3180563/202506/3180563-20250630210639968-1895396760.png)

可以看到,500个连接就是五百个线程,每一个线程都有用户栈,随着用户操作的增加,可分配的栈空间会越来越少。

# NIO + 线程池 + Jackson 实现服务器端

## 工具类

### common.util.SocketUtil

功能:将Jackson包装了一下,可以将Object写到buffer,也可以从buffer中读取object。

<details>
  <summary>代码展开</summary>
```java
public class SocketUtil {
    /** 关闭Socket */
    public static void close(Socket socket) {
        if (socket != null && !socket.isClosed()) {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /** 关闭ServerSocket */
    public static void close(ServerSocket ss) {
        if (ss != null && !ss.isClosed()) {
            try {
                ss.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

Server

ClientContext

功能:客户端通道的上下文,包括读写缓冲区和通道。

代码展开
public class ClientContext {
    public SocketChannel channel;              // 关联的客户端通道
    public ByteBuffer readBuffer;               // 读缓冲区,和数组相比的优势是有状态指针position, limit, capacity
    public Queue<ByteBuffer> writeQueue;        // 待发送数据队列
    public boolean writeInProgress;             // 是否正在写数据

    // 构造函数,初始化缓冲区和队列
    public ClientContext(SocketChannel channel) {
        this.channel = channel;
        this.readBuffer = ByteBuffer.allocate(8192);  // 8KB读缓冲
        this.writeQueue = new LinkedList<>();
        this.writeInProgress = false;
    }

    // 添加要写的数据到队列
    public void enqueueWrite(byte[] data) {
        if (data != null && data.length > 0) {
            writeQueue.add(ByteBuffer.wrap(data));
        }
    }

    // 判断是否还有数据待写
    public boolean hasPendingWrites() {
        return !writeQueue.isEmpty();
    }

    // 从队列获取当前写缓冲
    public ByteBuffer getCurrentWriteBuffer() {
        return writeQueue.peek();
    }

    // 写完当前缓冲后调用,弹出已写完的缓冲
    public void popCurrentWriteBuffer() {
        writeQueue.poll();
    }
}

server.controller.RequestProcessorJson

功能:对客户端信息的处理,包括注册,登录,抖动,传输文件。

代码展开
public class RequestProcessorJson implements Runnable{
    private final ClientContext context;
    private final SelectionKey key;

    public RequestProcessorJson(ClientContext context, SelectionKey key) {
        this.context = context;
        this.key = key;
    }

    public void run() {
        try {
            if(key.isReadable()){
                processRead();
            }else if(key.isWritable()){
                processWrite();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    // 通道读
    public void processRead( ) throws IOException {
        SocketChannel channel = context.channel;
        ByteBuffer buffer = context.readBuffer;
        buffer.clear();

        int len = channel.read(buffer);
        if (len == -1) {
            channel.close();
            System.out.println("客户端断开");
            return;
        }

        Request request = JacksonUtil.readBuffer2Obj(buffer, Request.class);

        try{
            //从请求输入流中读取到客户端提交的请求对象
            String actionName = request.getAction();   //获取请求中的动作
            switch (actionName) {
                case "userRegiste" ->       //用户注册
                        registe(request);
                case "userLogin" ->   //用户登录
                        login(request);
                case "exit" ->        //请求断开连接
                        logout(request);
                case "chat" ->        //聊天
                        chat(request);
                case "shake" ->       //振动
                        shake(request);
                case "toSendFile" ->  //准备发送文件
                        toSendFile(request);
                case "agreeReceiveFile" ->  //同意接收文件
                        agreeReceiveFile(request);
                case "refuseReceiveFile" ->  //拒绝接收文件
                        refuseReceiveFile(request);
            }
        }catch(Exception e){
            e.printStackTrace();
        }

        // 切换成写状态
        context.writeInProgress = true;
    }

    // 通道写
    public void processWrite() throws IOException {
        SocketChannel channel = context.channel;

        while (true) {
            ByteBuffer buffer = context.getCurrentWriteBuffer();
            if (buffer == null) {
                // 没数据可写了,切回读
                context.writeInProgress = false;
                key.interestOps(SelectionKey.OP_READ);
                return;
            }

            channel.write(buffer);  // 写数据(可能只写一部分)

            if (buffer.hasRemaining()) {
                // hasRemaining判断buffer有没有读取完
                // 说明本轮写没写完,等下一轮 selector 再触发
                break;
            } else {
                // 本 buffer 写完了,出队
                context.popCurrentWriteBuffer();
            }
        }

        // 写队列还有数据,继续监听写事件
        if (!context.hasPendingWrites()) {
            context.writeInProgress = false;
            key.interestOps(SelectionKey.OP_READ);
        }
    }


    /** 注册 */
    public void registe(Request request) throws IOException {
        User user = (User)request.getAttribute("user");
        UserService userService = new UserService();
        userService.addUser(user);

        Response response = new Response();  //创建一个响应对象
        response.setStatus(ResponseStatus.OK);
        response.setData("user", user);

        JacksonUtil.writeObj2Buffer(response, context.writeQueue);
        key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);

        //把新注册用户添加到RegistedUserTableModel中
        DataBuffer.registedUserTableModel.add(new String[]{
                String.valueOf(user.getId()),
                user.getPassword(),
                user.getNickname(),
                String.valueOf(user.getSex())
        });
    }


    /** 拒绝接收文件 */
    private void refuseReceiveFile(Request request) throws IOException {
        FileInfo sendFile = (FileInfo)request.getAttribute("sendFile");
        Response response = new Response();  //创建一个响应对象
        response.setType(ResponseType.REFUSERECEIVEFILE);
        response.setData("sendFile", sendFile);
        response.setStatus(ResponseStatus.OK);
        //向请求方的输出流输出响应
        ClientContext receiveContext = DataBuffer.onlineUserContextMap.get(sendFile.getToUser().getId());
        JacksonUtil.writeObj2Buffer(response, receiveContext.writeQueue);
        DataBuffer.onlineUserKeyMap.get(sendFile.getToUser().getId()).interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
    }

    /** 同意接收文件 */
    private void agreeReceiveFile(Request request) throws IOException {
        FileInfo sendFile = (FileInfo)request.getAttribute("sendFile");
        //向请求方(发送方)的输出流输出响应
        Response response = new Response();  //创建一个响应对象
        response.setType(ResponseType.AGREERECEIVEFILE);
        response.setData("sendFile", sendFile);
        response.setStatus(ResponseStatus.OK);
        ClientContext sendContext = DataBuffer.onlineUserContextMap.get(sendFile.getFromUser().getId());
        JacksonUtil.writeObj2Buffer(response, sendContext.writeQueue);
        DataBuffer.onlineUserKeyMap.get(sendFile.getFromUser().getId()).interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);

        //向接收方发出接收文件的响应
        Response response2 = new Response();  //创建一个响应对象
        response2.setType(ResponseType.RECEIVEFILE);
        response2.setData("sendFile", sendFile);
        response2.setStatus(ResponseStatus.OK);
        ClientContext receiveContext = DataBuffer.onlineUserContextMap.get(sendFile.getToUser().getId());
        JacksonUtil.writeObj2Buffer(response2, receiveContext.writeQueue);
        DataBuffer.onlineUserKeyMap.get(sendFile.getToUser().getId()).interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
    }

    /** 客户端退出 */
    public void logout( Request request) throws IOException{

        User user = (User)request.getAttribute("user");
        //把当前上线客户端的IO从Map中删除
        DataBuffer.onlineUserIOCacheMap.remove(user.getId());
        //从在线用户缓存Map中删除当前用户
        DataBuffer.onlineUsersMap.remove(user.getId());

        Response response = new Response();  //创建一个响应对象
        response.setType(ResponseType.LOGOUT);
        response.setData("logoutUser", user);
        JacksonUtil.writeObj2Buffer(response, context.writeQueue);
        // 关闭channel
        context.channel.close();
        DataBuffer.onlineUserTableModel.remove(user.getId()); //把当前下线用户从在线用户表Model中删除
        iteratorResponse(response);//通知所有其它在线客户端
    }

    /** 登录 */
    public void login(Request request) throws IOException {
        String idStr = (String)request.getAttribute("id");
        String password = (String) request.getAttribute("password");
        UserService userService = new UserService();
        User user = userService.login(Long.parseLong(idStr), password);

        Response response = new Response();  //创建一个响应对象
        if(null != user){
            if(DataBuffer.onlineUsersMap.containsKey(user.getId())){ //用户已经登录了
                response.setStatus(ResponseStatus.OK);
                response.setData("msg", "该 用户已经在别处上线了!");
                JacksonUtil.writeObj2Buffer(response, context.writeQueue);
            }else { //正确登录
                DataBuffer.onlineUsersMap.put(user.getId(), user); //添加到在线用户
                //设置在线用户
                response.setData("onlineUsers",
                        new CopyOnWriteArrayList<User>(DataBuffer.onlineUsersMap.values()));

                response.setStatus(ResponseStatus.OK);
                response.setData("user", user);
                // response里设置历史聊天记录
                response.setData("history", MainServer.chatRecords);
                JacksonUtil.writeObj2Buffer(response, context.writeQueue);

                //通知其它用户有人上线了
                Response response2 = new Response();
                response2.setType(ResponseType.LOGIN);
                response2.setData("loginUser", user);
                iteratorResponse(response2);

                //把当前上线的用户IO添加到缓存Map中
                DataBuffer.onlineUserContextMap.put(user.getId(), context);
                DataBuffer.onlineUserKeyMap.put(user.getId(), key);
                //把当前上线用户添加到OnlineUserTableModel中
                DataBuffer.onlineUserTableModel.add(
                        new String[]{String.valueOf(user.getId()),
                                user.getNickname(),
                                String.valueOf(user.getSex())});
            }
        }else{ //登录失败
            response.setStatus(ResponseStatus.OK);
            response.setData("msg", "账号或密码不正确!");
            JacksonUtil.writeObj2Buffer(response, context.writeQueue);
        }
        key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
    }

    /** 聊天 */
    public void chat(Request request) throws IOException {
        Message msg = (Message)request.getAttribute("msg");
        Response response = new Response();
        response.setStatus(ResponseStatus.OK);
        response.setType(ResponseType.CHAT);
        response.setData("txtMsg", msg);

        if(msg.getToUser() != null){ //私聊:只给私聊的对象返回响应
            ClientContext toContext = DataBuffer.onlineUserContextMap.get(msg.getToUser().getId());
            JacksonUtil.writeObj2Buffer(response, toContext.writeQueue);
            DataBuffer.onlineUserKeyMap.get(msg.getToUser().getId()).interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
        }else{  //群聊:给除了发消息的所有客户端都返回响应
            // 记录所有的公共聊天记录
            MainServer.chatRecords.add(msg);
            for(Long id : DataBuffer.onlineUserIOCacheMap.keySet()){
                if(msg.getFromUser().getId() == id ){	continue; }
                JacksonUtil.writeObj2Buffer(response, DataBuffer.onlineUserContextMap.get(id).writeQueue);
                DataBuffer.onlineUserKeyMap.get(id).interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
            }
        }
    }

    /*广播*/
    public static void board(String str) throws IOException {
        User user = new User(1,"admin");
        Message msg = new Message();
        msg.setFromUser(user);
        msg.setSendTime(new Date());

        DateFormat df = new SimpleDateFormat("HH:mm:ss");
        StringBuffer sb = new StringBuffer();
        sb.append(" ").append(df.format(msg.getSendTime())).append(" ");
        sb.append("系统通知\n  "+str+"\n");
        msg.setMessage(sb.toString());

        Response response = new Response();
        response.setStatus(ResponseStatus.OK);
        response.setType(ResponseType.BOARD);
        response.setData("txtMsg", msg);

        for (Long id : DataBuffer.onlineUserIOCacheMap.keySet()) {
            JacksonUtil.writeObj2Buffer(response, DataBuffer.onlineUserContextMap.get(id).writeQueue);
            SelectionKey tokey = DataBuffer.onlineUserKeyMap.get(id);
            tokey.interestOps(tokey.interestOps() | SelectionKey.OP_WRITE);
        }
    }

    /*踢除用户*/
    public static void remove(User user_) throws IOException{
        User user = new User(1,"admin");
        Message msg = new Message();
        msg.setFromUser(user);
        msg.setSendTime(new Date());
        msg.setToUser(user_);

        StringBuffer sb = new StringBuffer();
        DateFormat df = new SimpleDateFormat("HH:mm:ss");
        sb.append(" ").append(df.format(msg.getSendTime())).append(" ");
        sb.append("系统通知您\n  "+"您被强制下线"+"\n");
        msg.setMessage(sb.toString());

        Response response = new Response();
        response.setStatus(ResponseStatus.OK);
        response.setType(ResponseType.REMOVE);
        response.setData("txtMsg", msg);

        // 发送消息,并开启写监听
        ClientContext toContext = DataBuffer.onlineUserContextMap.get(msg.getToUser().getId());
        JacksonUtil.writeObj2Buffer(response, toContext.writeQueue);
        SelectionKey tokey = DataBuffer.onlineUserKeyMap.get(msg.getToUser().getId());
        tokey.interestOps(tokey.interestOps() | SelectionKey.OP_WRITE);
    }

    /*私信*/
    public static void chat_sys(String str,User user_) throws IOException{
        User user = new User(1,"admin");
        Message msg = new Message();
        msg.setFromUser(user);
        msg.setSendTime(new Date());
        msg.setToUser(user_);

        DateFormat df = new SimpleDateFormat("HH:mm:ss");
        StringBuffer sb = new StringBuffer();
        sb.append(" ").append(df.format(msg.getSendTime())).append(" ");
        sb.append("系统通知您\n  "+str+"\n");
        msg.setMessage(sb.toString());

        Response response = new Response();
        response.setStatus(ResponseStatus.OK);
        response.setType(ResponseType.CHAT);
        response.setData("txtMsg", msg);

        ClientContext toContext = DataBuffer.onlineUserContextMap.get(msg.getToUser().getId());
        JacksonUtil.writeObj2Buffer(response, toContext.writeQueue);
    }

    /** 发送振动 */
    public void shake(Request request)throws IOException {
        Message msg = (Message) request.getAttribute("msg");
        DateFormat df = new SimpleDateFormat("HH:mm:ss");
        StringBuffer sb = new StringBuffer();
        sb.append(" ").append(msg.getFromUser().getNickname())
                .append("(").append(msg.getFromUser().getId()).append(") ")
                .append(df.format(msg.getSendTime())).append("\n  给您发送了一个窗口抖动\n");
        msg.setMessage(sb.toString());
        Response response = new Response();
        response.setStatus(ResponseStatus.OK);
        response.setType(ResponseType.SHAKE);
        response.setData("ShakeMsg", msg);
        iteratorResponse(response);
    }

    /** 准备发送文件 */
    public void toSendFile(Request request)throws IOException{
        Response response = new Response();
        response.setStatus(ResponseStatus.OK);
        response.setType(ResponseType.TOSENDFILE);
        FileInfo sendFile = (FileInfo)request.getAttribute("file");
        response.setData("sendFile", sendFile);
        //给文件接收方转发文件发送方的请求
        ClientContext toContext = DataBuffer.onlineUserContextMap.get(sendFile.getToUser().getId());
        SelectionKey tokey = DataBuffer.onlineUserKeyMap.get(sendFile.getToUser().getId());
        JacksonUtil.writeObj2Buffer(response, toContext.writeQueue);
        tokey.interestOps(tokey.interestOps() | SelectionKey.OP_WRITE);
    }

    /** 给所有在线客户都发送响应 */
    private void iteratorResponse(Response response) throws IOException {
        for (Long id : DataBuffer.onlineUserIOCacheMap.keySet()) {
            ClientContext toContext = DataBuffer.onlineUserContextMap.get(id);
            SelectionKey tokey = DataBuffer.onlineUserKeyMap.get(id);
            JacksonUtil.writeObj2Buffer(response, toContext.writeQueue);
            tokey.interestOps(tokey.interestOps() | SelectionKey.OP_WRITE);
        }
    }
}

MainServerNIO

功能:使用NIO和线程池,获取客户端连接。

代码展开
public class MainServerNIO {
    public static List<Message> chatRecords;

    public static void main(String[] args) {
        // 历史记录
        chatRecords = new ArrayList<Message>();
        int port = Integer.parseInt(DataBuffer.configProp.getProperty("port"));
        // 线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(100);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try (ServerSocketChannel serverChannel = ServerSocketChannel.open()) {
                    serverChannel.bind(new InetSocketAddress(port), 5000);
                    serverChannel.configureBlocking(false);
                    // 实例化一个selector
                    Selector selector = Selector.open();
                    // selector和severChannel关联
                    serverChannel.register(selector, SelectionKey.OP_ACCEPT);
                    while(true){
                        // selector.select()将请求注册到SelectionKey集合,并返回selectionKey数量
                        int readyChannels = selector.select();
                        if (readyChannels == 0) continue;
                        // 数量不为0说明有请求
                        Set<SelectionKey> selectionKeys = selector.selectedKeys();
                        // set的迭代器
                        Iterator<SelectionKey> iterator = selectionKeys.iterator();
                        // 遍历每一个selectionKey
                        while (iterator.hasNext()) {
                            SelectionKey key = iterator.next();
                            // 判断是什么类型的请求
                            if(key.isAcceptable()){
                                // 连接请求构建新的客户端通道
                                ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                                SocketChannel socketChannel = serverSocketChannel.accept();
                                socketChannel.configureBlocking(false);
                                // 注册通道到selector
                                socketChannel.register(selector, SelectionKey.OP_READ, new ClientContext(socketChannel));
                            }else if(key.isReadable() || key.isWritable()){
                                // 读数据请求
                                ClientContext context = (ClientContext) key.attachment();
                                threadPool.submit(new RequestProcessorJson(context, key));
                            }
                        }
                    }
                }catch (Exception e) {
                    e.printStackTrace();
                    System.out.println(e.getMessage());
                }
            }
        }).start();


        //设置外观感觉
        JFrame.setDefaultLookAndFeelDecorated(true);
        JDialog.setDefaultLookAndFeelDecorated(true);
        try {
            UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
        } catch (Exception e) {
            e.printStackTrace();
        }
        //启动服务器监控窗体
        new ServerInfoFrame();
    }
}

NIO性能监督

image

同样还是只能连接1000个线程,多的线程连接会refuse掉,线程池只有20个线程在处理任务,Threads上可以看出来。由于每一个Channel都需要分配缓冲区,所以可以看到堆的空间占用相比于BIO要更高,GC在Minor GC或Full GC被触发,GC会回收堆中所有“不可达对象”,垃圾清除完后堆使用量迅速下降。

一些问题

代码小白,这里记录一些自己问GPT的问题。

文件持久化和数据库相比会带来哪些问题、原因是什么、怎么验证它们确实存在?

  1. 性能瓶颈,每一次要把所有的用户信息都加载,但是数据库只需要一个SELECT语句。 2. 并发写入冲突,两个线程一起写入文件,会带来冲突,数据库可以实现并发。3. 结构变更兼容性差,修改User类,加一个新字段,再去读取旧文件,会读入失败。4. 不灵活,无法筛选。 5. 过程中一旦中断,就会发生报错。

代码将所有用户信息加载到了内存里,企业会怎么做?

放在分布式内存系统中,加载信息包括:用户ID,用户当前连接的设备,socket 连接信息,最近活动时间,会话状态(在线、离开、隐身等),客户端类型、IP、位置信息等。中大型系统的缓存使用Redis,可共享,跨服务访问,适合分布式。

如何判断一个类应不应该是内部类,成员变量需不需要getset方法?

仅仅在外部类中使用,不会在类的外部访问,需要访问外部类成员的可以写成内部类;变量需要对外公开访问/修改,提供gettersetter,类是JavaBean(如实体类、DTO),也需要;只读变量,变量不能被随便改动,只提供getter

怎么判断我是应该使用线程池还是NIO+线程池呢?

NIO的特点是一个线程可以处理多个连接,所以对于连接数少、任务处理耗时长的用 BIO + 线程池,而连接数多、任务处理快或IO密集型,使用 NIO + 线程池。

  1. 并发连接数:少于几百:BIO 就够了;几千~上万:使用 NIO 更合适。

  2. 连接的“活跃性”:很多连接是“长连接但空闲”(比如聊天软件):推荐 NIO;连接一来就立刻处理任务然后断开(如 HTTP 请求):BIO 或 NIO 都可。

  3. 处理任务的复杂程度:如果是图像处理、数据库读写等“阻塞型任务”,BIO + 线程池更简单;如果是简单读写转发、转发消息、推送通知,NIO 更适合,CPU 利用率更高。

  4. 是否追求高性能、低延迟:如果你追求极限性能(如聊天室上万人同时在线),那么选择 NIO + Selector + 线程池 是趋势;如果是中小项目、原型开发,BIO + 线程池更简单直接。

如何确定线程池的大小?

资料二,目前有点看不懂。

为什么使用JSON传输数据,而不是用Java原生的字节流?

安全性和跨平台兼容性是JSON最大优势。现代分布式系统几乎都弃用Java原生序列化,转向JSON、Protobuf、Avro等格式。

到底一个变量该放在类中,还是放在方法(函数)中?

你应该将变量声明在它被使用的最小作用域内,如果只在某个方法中使用就放在方法里,如果跨多个方法或对象需要用就放在类里(作为成员变量)。

参考资料

posted @ 2025-06-30 21:45  ZCry  阅读(18)  评论(0)    收藏  举报