H__D  

一、聊天室结构图

   

二、代码

package com.demo;

import org.junit.Test;

import java.io.*;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.*;
import java.util.concurrent.*;

/**
 * NIO 聊天是demo
 * 用户可以登陆聊天室,发送消息,和收到其他人发送的消息
 *
 * 用户第一次登录,需要设置自己的用户名
 */
public class NIOChatDemo {

    Selector selector = null;
    ServerSocketChannel ssChannel = null;

    ExecutorService readPool = Executors.newFixedThreadPool(10);
    Set<SelectionKey> keysSet = new CopyOnWriteArraySet<>();
    Map<String, String> nameMap = new ConcurrentHashMap<>();

    /**
     * 服务器
     */
    @Test
    public void server() {

        try {
            // 创建ServerSocketChannel
            // 1、获取通道
            ssChannel = ServerSocketChannel.open();
            // 2、切换非阻塞模式
            ssChannel.configureBlocking(false);
            // 3、绑定连接
            InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 9999);
            ssChannel.bind(inetSocketAddress);

            // 4、获取选择器
            selector = Selector.open();
            // 5、将通道注册到选择器上,指定监听事件
            // SelectionKey.OP_ACCEPT: 接收
            ssChannel.register(selector, SelectionKey.OP_ACCEPT);

            // 6、轮询式的获取选择器上已经"准备就绪"的事件
            while (true) {
                // 调用NIO选择器,选取方法
                // 阻塞到至少有一个通道在你注册的事件上就绪了
                int readyNum = selector.select();
                if (readyNum == 0) continue;

                // 7、获取当前选择器中所注册的"选择键(已就绪的监听事件)"
                Set<SelectionKey> selected = selector.selectedKeys();
                Iterator<SelectionKey> selectedKeys = selected.iterator();

                boolean newKeyFlag = false;
                // 迭代获取
                while (selectedKeys.hasNext()) {
                    // 8、获取准备就绪的事件的key
                    SelectionKey key = selectedKeys.next();
                    // 将key从集合中移除
                    selectedKeys.remove();
                    if(keysSet.contains(key)) {continue;}
                    newKeyFlag =true;

                    // 9、判断是什么事件准备就绪
                    if (key.isAcceptable()) {// 接收就绪
                        acceptable(ssChannel, selector);
                    } else if (key.isConnectable()) { // 接收就绪
                        // 只有客户端SocketChannel会注册该操作,当客户端调用SocketChannel.connect()时,该操作会就绪。
//                        System.out.println("SocketChannel已注册");
                    } else if (key.isReadable()) {// 读就绪
                        // 将key添加到集合中,表示在处理中
                        keysSet.add(key);
                        readPool.submit(() -> {
                            try {
                                readable(nameMap, key);
                                // 处理完,移除key
                                keysSet.remove(key);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        });
                    }
                }
                if(!newKeyFlag) {
                    // 没有新key,表示其他key都在处理中
                    Thread.sleep(1000);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            if (selector != null) {
                try {
                    selector.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (ssChannel != null) {
                try {
                    ssChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 接收连接
     */
    private void acceptable(ServerSocketChannel ssChannel, Selector selector) {
//        System.out.println("-------------通道可接收-------------");
        // 10、若"接收就绪",就获取客户端的连接
        SocketChannel sChannel = null;
        try {
            sChannel = ssChannel.accept();
            // 11、切换非阻塞模式
            sChannel.configureBlocking(false);
            // 12、将该通道注册到选择器上
            sChannel.register(selector, SelectionKey.OP_READ);
        } catch (IOException e) {
            e.printStackTrace();
            if (sChannel != null && sChannel.isOpen()) {
                try {
                    sChannel.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
    }

    /**
     * 读取客户端数据
     */
    private void readable(Map<String, String> nameMap, SelectionKey key) throws IOException, InterruptedException {
//        System.out.println("-------------通道可读-------------");
        // 13、获取当前选择器上"读就绪"状态的通道
        SocketChannel sChannel = (SocketChannel) key.channel();

        // 14、读取数据
        ByteBuffer lenBuffer = ByteBuffer.allocate(4);
        ByteBuffer contentBuffer = ByteBuffer.allocate(1024);

        String nameKey = sChannel.getRemoteAddress().toString();
        String name = nameMap.get(nameKey);

        while (true) {
            // 获取数据长度
            String content = receiveData(key, lenBuffer, contentBuffer);
            if(content == null) {
                return;
            }

            String result = null;
            // 获取名称
            if(name == null) {
                nameMap.put(nameKey, content + "(" + nameKey +"):");
                result = "欢迎登录-" + content;
                System.out.println(content + " - 登录服务器");
                sendData(result, sChannel);
            }else {
                // 发给其他用户数据
                result = name + content;
                sendToOther(key, result);
            }
        }

    }

    private void sendToOther(SelectionKey key, String result) throws IOException {

        Selector selector = key.selector();
        for (SelectionKey sk : selector.keys()){
            SelectableChannel channel = sk.channel();
            if (channel instanceof SocketChannel && key != sk) {
                SocketChannel dest = (SocketChannel) channel;
                sendData(result, dest);
            }
        }
    }

    /**
     * 客户端
     */
    @Test
    public void client() {

        Set<SelectionKey> keysSet = new CopyOnWriteArraySet<>();

        SocketChannel sChannel = null;
        Selector selector = null;
        try {
            // 创建SocketChannel
            sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
            sChannel.configureBlocking(false);
            selector = Selector.open();
            sChannel.register(selector, SelectionKey.OP_READ);

            // 先数据线程
            clientWrite(sChannel);

            while (true) {
                int readyNum = selector.select();
                if (readyNum == 0) continue;

                Set<SelectionKey> selected = selector.selectedKeys();
                Iterator<SelectionKey> selectedKeys = selected.iterator();

                boolean newKeyFlag = false;
                // 迭代获取
                while (selectedKeys.hasNext()) {
                    // 8、获取准备就绪的事件的key
                    SelectionKey key = selectedKeys.next();
                    // 将key从集合中移除
                    selectedKeys.remove();
                    if(keysSet.contains(key)) {continue;}
                    newKeyFlag =true;

                    if (key.isReadable()) {// 读就绪
                        clientRead(key);
                    }
                }
                if (!newKeyFlag) {
                    // 没有新key,表示其他key都在处理中
                    // 则休眠一会
                    Thread.sleep(1000);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (selector != null) {
                try {
                    selector.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (sChannel != null) {
                try {
                    sChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void clientWrite(SocketChannel sChannel) {
        // 写数据线程
        SocketChannel finalSChannel = sChannel;
        new Thread(() -> {
            try {
                Scanner scanner = new Scanner(System.in);
                System.out.println("请输入用户名:");
                while (scanner.hasNext()) {
                    String content = scanner.next();
                    sendData(content, finalSChannel);
                }
            } catch (IOException  e) {
                e.printStackTrace();
            } finally {
                try {
                    finalSChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }


    private void clientRead(SelectionKey key) throws IOException, InterruptedException {
        // 13、获取当前选择器上"读就绪"状态的通道


        // 14、读取数据
        ByteBuffer lenBuffer = ByteBuffer.allocate(4);
        ByteBuffer contentBuffer = ByteBuffer.allocate(1024);

        while (true) {
            String s = receiveData(key, lenBuffer, contentBuffer);
            if (s == null) return;
            System.out.println(s);
        }
    }

    private String receiveData(SelectionKey key, ByteBuffer lenBuffer, ByteBuffer contentBuffer) throws IOException, InterruptedException {
        SocketChannel sChannel = (SocketChannel) key.channel();

        // 获取数据长度
        int read = sChannel.read(lenBuffer);
        if(read == -1) {
            // 客户端关闭
            sChannel.close();
            key.cancel();
        }
        if (lenBuffer.position() == 0) {
            // 没有数据可以读了
            return null;
        }
        while (lenBuffer.remaining() != 0) {
            // 有出现过未读取到4个字节的情况,继续读取
            sChannel.read(lenBuffer);
            Thread.sleep(10);
            System.out.println(Thread.currentThread().getName() + "长度出现未读完全的情况!");
        }
        // 得到内容字节长度
        lenBuffer.flip();
        int l = lenBuffer.asIntBuffer().get();
        lenBuffer.clear();
        // 内容缓存
        sChannel.read(contentBuffer);
        while (contentBuffer.position() != l) {
            // 有出现过未读完全的情况,继续读取
            sChannel.read(contentBuffer);
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "内容出现未读完全的情况!");
        }
        contentBuffer.flip();
        String content = new String(contentBuffer.array(), 0, contentBuffer.limit());
        contentBuffer.clear();
        return content;
    }

    // 发送数据
    private void sendData(String result, SocketChannel dest) throws IOException {
        int length = result.getBytes().length;
        ByteBuffer buf = ByteBuffer.allocate(length + 4);
        buf.putInt(length);
        buf.put(result.getBytes());
        buf.flip();
        dest.write(buf);
        buf.clear();
    }
}

三、运行结果

  

  

  

  

 

posted on 2021-05-31 19:26  H__D  阅读(303)  评论(0编辑  收藏  举报