欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java NIO网络编程制作简易聊天室

程序员文章站 2023-12-10 19:27:28
Java NIO网络编程制作简易聊天室一、NIO简介二、编程模型三、BIO网络模型四、NIO网络模型五、具体代码实现六、演示效果图一、NIO简介NIO全称:Non-blocking IO 或 New IO,是非阻塞式的IOJDK版本:JDK1.4+应用场景:高并发网络服务编程二、编程模型模型:对事物共性的抽象编程模型:对编程共性的抽象三、BIO网络模型BIO网络模型介绍从图中可以看出,一个线程到第5步的时候,会阻塞在那等待客户端的下次请求,每新增一个客户端,就会启动一个新的...

一、NIO简介

  • NIO全称:Non-blocking IO 或 New IO,是非阻塞式的IO
  • JDK版本:JDK1.4+
  • 应用场景:高并发网络服务编程

二、编程模型

  • 模型:对事物共性的抽象
  • 编程模型:对编程共性的抽象

三、BIO网络模型

  1. BIO网络模型介绍
    Java NIO网络编程制作简易聊天室
    从图中可以看出,一个线程到第5步的时候,会阻塞在那等待客户端的下次请求,每新增一个客户端,就会启动一个新的线程来处理这个客户端的请求,如果客户端量很大的话,就会造成服务器压力过大而崩溃。

  2. BIO网络模型缺点

  • 阻塞式IO模型
  • 弹性伸缩能力差
  • 多线程非常消耗资源

四、NIO网络模型

  1. NIO网络模型介绍
    Java NIO网络编程制作简易聊天室
    相对于BIO,NIO的网络模型就要复杂的多,服务端提供一个单线程的Selector组件,它是一个事件注册监听器,负责监听管理注册到它上面的连接和事件,但是本身不负责处理客户端的业务逻辑。当它监听到指定的事件,就会启动相应的事件处理器去处理请求,事件处理器可以依次处理多个请求,处理完成之后,由事件处理器去响应客户端,Selector本身继续监听其他事件。

  2. NIO模型的优点

  • 非阻塞式IO模型
  • 弹性伸缩能力强
  • 单线程节约资源
  1. 原生NIO的缺点
  • NIO类库和API比较复杂
  • 可靠性能力补齐,工作量和难度都非常大
  • Selector空轮询,导致CPU占用100%的bug还没有修复

五、具体代码实现

  1. 服务端代码NioServer.java
package com.feonix;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * NIO服务器端
 */
public class NioServer {
    /**
     * 映射客户端channel
     */
    private Map<String, SocketChannel> clientsMap = new HashMap<String, SocketChannel>();

    public static void main(String[] args) {
        NioServer nioServer = new NioServer();
        try {
            // 启动服务端
            nioServer.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 启动
     *
     * @throws IOException
     */
    public void start() throws IOException {
        // 1. 创建Selector
        Selector selector = Selector.open();

        // 2. 通过ServerSocket创建channel通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        // 3. 为channel通道绑定监听端口
        serverSocketChannel.bind(new InetSocketAddress(8000));

        // 4. 将channel设置为非阻塞模式
        serverSocketChannel.configureBlocking(false);

        // 5. 将channel注册到selector上,监听连接事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务器端启动成功!");

        // 6. 循环等待新接入的连接
        for (; ; ) {
            // 获取可用的channel数量
            int readyChannels = selector.select();
            // 屏蔽未就绪的连接,防止selector空轮询
            if (readyChannels == 0) {
                continue;
            }
            // 获取可用的channel集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                // 获取selectionKey实例
                SelectionKey selectionKey = iterator.next();
                // 移除当前的selectionKey
                iterator.remove();
                // 7. 根据就绪状态,调用对应的方法处理业务逻辑
                // 接入事件处理逻辑
                if (selectionKey.isAcceptable()) {
                    acceptHandler(serverSocketChannel, selector);
                }
                // 可读事件处理逻辑
                if (selectionKey.isReadable()) {
                    readHandler(selectionKey, selector);
                }
            }
        }
    }

    /**
     * 接入事件处理器
     *
     * @param serverSocketChannel
     * @param selector
     */
    private void acceptHandler(ServerSocketChannel serverSocketChannel, Selector selector) throws IOException {
        // 创建socketChannel
        SocketChannel socketChannel = serverSocketChannel.accept();
        // 将socketChannel设置为非阻塞模式
        socketChannel.configureBlocking(false);
        // 将channel注册到selector上,监听 可读事件
        socketChannel.register(selector, SelectionKey.OP_READ);
        // 回复客户端提示信息
        socketChannel.write(Charset.forName("UTF-8").encode("系统提示:你与聊天室内的其他人都不是好友关系,请注意隐私安全!"));
    }

    /**
     * 可读事件处理器
     *
     * @param selectionKey
     * @param selector
     * @throws IOException
     */
    private void readHandler(SelectionKey selectionKey, Selector selector) throws IOException {
        // 从selectionKey中获取到已经就绪的channel
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        // 创建buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        // 循环读取客户端的请求信息
        String request = "";
        while (socketChannel.read(byteBuffer) > 0) {
            // 切换buffer为读模式
            byteBuffer.flip();
            // 读取buffer中的内容
            request += Charset.forName("UTF-8").decode(byteBuffer);
        }
        // 将channel再次注册到selector上,监听可读事件
        socketChannel.register(selector, SelectionKey.OP_READ);
        // 将客户端发送的信息广播给其他客户端
        if (request.length() > 0) {
            // 设置昵称的处理逻辑
            if (request.contains("name:::")) {
                String[] res = request.split(":::");
                if (res.length == 2) {
                    // 添加昵称和客户端channel的映射
                    clientsMap.put(res[1], socketChannel);
                    System.out.printf("客户端%s加入聊天室\n", res[1]);
                    // 广播给其他客户端,有新用户加入聊天室
                    broadCast(socketChannel, res[1] + "加入聊天室,和Ta打个招呼吧~");
                }
            } else { // 普通消息的处理逻辑
                // 先判断客户端channel映射map是否空
                if (!clientsMap.isEmpty()) {
                    // 遍历客户端channel映射map
                    for (Entry<String, SocketChannel> clientSet : clientsMap.entrySet()) {
                        // 找到当前客户端channel
                        if (socketChannel.equals(clientSet.getValue())) {
                            // 拿到昵称
                            String name = clientSet.getKey();
                            System.out.printf("客户端%s消息:%s\n", name, request);
                            // 广播给其他客户端
                            broadCast(socketChannel, name + "说:" + request);
                        }
                    }
                }
            }
        }
    }

    /**
     * 广播消息
     */
    private void broadCast(SocketChannel sourceChannel, String request) {
        if (!clientsMap.isEmpty()) {
            // 遍历所有已接入的客户端channel,循环向所有已接入客户端channel广播信息
            clientsMap.entrySet().forEach(clientSet -> {
                // 获取一个已接入的客户端channel
                SocketChannel targetChannel = clientSet.getValue();
                // 剔除发送消息的客户端
                if (!sourceChannel.equals(targetChannel)) {
                    try {
                        // 将消息发送到targetChannel客户端
                        targetChannel.write(Charset.forName("UTF-8").encode(request));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}
  1. 客户端代码NioClient.java
package com.feonix;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Scanner;

/**
 * NIO客户端
 */
public class NioClient {
    public static void main(String[] args) {
        NioClient nioClient = new NioClient();
        try {
            // 启动客户端
            nioClient.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 启动
     */
    public void start() throws IOException {
        // 连接服务器,指定ip和端口号
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8000));
        System.out.println("客户端启动成功!");

        // 接收服务器端的响应
        Selector selector = Selector.open();
        // 设置非阻塞
        socketChannel.configureBlocking(false);
        // 向selector注册可读事件
        socketChannel.register(selector, SelectionKey.OP_READ);
        // 启动一个线程处理消息读取事件
        new Thread(new NioClientHandler(selector)).start();

        // 向服务器端发送消息
        Scanner key = new Scanner(System.in);
        System.out.print("请输入你的昵称:");
        // 等待用户键盘输入昵称
        String name = key.nextLine();
        if (name != null && name.length() > 0) {
            // 发送昵称给服务端
            socketChannel.write(Charset.forName("UTF-8").encode("name:::" + name));
        }
        while (key.hasNextLine()) {
            // 等待用户键盘输入消息内容
            String request = key.nextLine();
            if (request != null && request.length() > 0) {
                // 发送消息给服务端
                socketChannel.write(Charset.forName("UTF-8").encode(request));
            }
        }
    }
}
  1. 客户端消息读取处理器NioClientHandler.java
package com.feonix;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;

/**
 * 客户端消息读取处理器
 */
public class NioClientHandler implements Runnable {

    private Selector selector;

    public NioClientHandler(Selector selector) {
        this.selector = selector;
    }

    @Override
    public void run() {
        try {
            for (; ; ) {
                // 获取可用的channel数量
                int readyChannels = selector.select();
                // 屏蔽未就绪的连接
                if (readyChannels == 0) {
                    continue;
                }
                // 获取可用的channel集合
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    // 获取selectionKey实例
                    SelectionKey selectionKey = iterator.next();
                    // 移除当前的selectionKey
                    iterator.remove();

                    // 可读事件处理逻辑
                    if (selectionKey.isReadable()) {
                        readHandler(selectionKey, selector);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 可读事件处理器
     */
    private void readHandler(SelectionKey selectionKey, Selector selector) throws IOException {
        // 从selectionKey中获取到已经就绪的channel
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        // 创建buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        // 循环读取服务器端的响应信息
        String response = "";
        while (socketChannel.read(byteBuffer) > 0) {
            // 切换buffer为读模式
            byteBuffer.flip();
            // 读取buffer中的内容
            response += Charset.forName("UTF-8").decode(byteBuffer);
        }
        // 将channel再次注册到selector上,监听可读事件
        socketChannel.register(selector, SelectionKey.OP_READ);
        // 将服务器端响应的信息打印到本地
        if (response.length() > 0) {
            System.out.println(response);
        }
    }
}

六、演示效果图

Java NIO网络编程制作简易聊天室
Java NIO网络编程制作简易聊天室
Java NIO网络编程制作简易聊天室
Java NIO网络编程制作简易聊天室


以上就是本次分享的全部内容,欢迎留言讨论,记得点赞哦(๑¯∀¯๑)~
Java NIO网络编程制作简易聊天室

本文地址:https://blog.csdn.net/u012587568/article/details/107074722