NIO群聊系统的实现

一、前言

通过NIO编写简单版聊天室,客户端通过控制台输入发送消息到其他客户端。注意:并未处理粘包半包问题。

二、逻辑简述

服务器:

1)创建服务器NIO通道,绑定端口并启动服务器
2)开启非阻塞模式
3)创建选择器、并把通道注册到选择器上,关心的事件为新连接
4)循环监听选择器的事件,
5)监听到新连接事件:
       5.1) 建立连接、创建客户端通道
       5.2)客户端通道设置非阻塞
       5.3)客户端注册到选择器上,关心的事件为读
6)监听到读 事件
       6.1)获取到发送数据的客户端通道
       6.2)把通道数据写入到一个缓冲区中
       6.3)打印数据
       6.4)发送给其他注册在选择器上的客户端,排除自己

客户器:

1)创建客户端通道,连接服务器 ip和端口
2)创建选择器,注册客户端通道到选择器上,关心的事件为读
3)开启一个线程 循环监听选择器事件
4)监听到读事件后
       4.1)从通道中把数据读到缓冲区中
       4.2)打印数据
5)主线程循环用scanner 来监听控制台输入
       5.1)有输入后 发送给服务器

三、代码

服务器:

 
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
 
/**
 
 */
public class GroupChatServer {
 
    private int port = 8888;
 
    private ServerSocketChannel serverSocketChannel;
 
    private Selector selector;
 
 
    public GroupChatServer() throws IOException {
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(port));
 
        //创建选择器
        selector = Selector.open();
        //通道注册到选择器上,关心的事件为 OP_ACCEPT:新连接
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("server is ok");
    }
 
    public void listener() throws IOException {
        for (; ; ) {
            if (selector.select() == 0) {
                continue;
            }
            //监听到时间
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                if (selectionKey.isAcceptable()) {//新连接事件
                    newConnection();
                }
                if (selectionKey.isReadable()) {//客户端消息事件
                    clientMsg(selectionKey);
                }
                iterator.remove();
            }
        }
    }
 
    /**
     * 客户端消息处理
    
     */
    private void clientMsg(SelectionKey selectionKey) throws IOException {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
        try {
            //通道数据读取到 byteBuffer缓冲区
            socketChannel.read(byteBuffer);
            //创建一个数组用于接受 缓冲区的本次写入的数据。
            byte[] bytes = new byte[byteBuffer.limit()];
            //转换模式 写->读
            byteBuffer.flip();
            //获取数据到 bytes 中 从位置0开始到limit结束
            byteBuffer.get(bytes, 0, byteBuffer.limit());
            String msg = socketChannel.getRemoteAddress() + "说:" + new String(bytes, "utf-8");
            //倒带这个缓冲区。位置设置为零,标记为-1.这样下次写入数据会从0开始写。但是如果下次的数据比这次少。那么使用 byteBuffer.array方法返回的byte数组数据会包含上一次的部分数据
            //例如 上次写入了 11111 倒带后 下次写入了 22 读取出来 却是 22111
            byteBuffer.rewind();
            System.out.println(msg);
            //发送给其他客户端
            sendOuterClient(msg, socketChannel);
        } catch (Exception e) {
            System.out.println(socketChannel.getRemoteAddress() + ":下线了");
            socketChannel.close();
        }
    }
 
    /**
     * 发送给其他客户端
     *
     * @param msg           要发送的消息
     * @param socketChannel 要排除的客户端
     * @throws IOException
     */
    private void sendOuterClient(String msg, SocketChannel socketChannel) throws IOException {
        //获取selector上注册的全部通道集合
        Set<SelectionKey> keys = selector.keys();
        for (SelectionKey key : keys) {
            SelectableChannel channel = key.channel();
            //判断通道是客户端通道(因为服务器的通道也注册在该选择器上),并且排除发送人的通道
            if (channel instanceof SocketChannel && !channel.equals(socketChannel)) {
                try {
                    ((SocketChannel) channel).write(ByteBuffer.wrap(msg.getBytes()));
                } catch (Exception e) {
                    channel.close();
                    System.out.println(((SocketChannel) channel).getRemoteAddress() + ":已下线");
                }
            }
        }
    }
 
    /**
     * 新连接处理方法
     * @throws IOException
     */
    private void newConnection() throws IOException {
        //连接获取SocketChannel
        SocketChannel socketChannel = serverSocketChannel.accept();
        //设置非阻塞
        socketChannel.configureBlocking(false);
        //注册到选择器上,关心的事件是读,并附带一个ByteBuffer对象
        socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
        System.out.println(socketChannel.getRemoteAddress() + " 上线了");
    }
 
    public static void main(String[] args) throws IOException {
        GroupChatServer groupChatServer = new GroupChatServer();
        //启动监听
        groupChatServer.listener();
    }
}

客户端:

 
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
 
/**    
 */
public class GroupChatClient {
 
    private Selector selector;
 
    private SocketChannel socketChannel;
 
    public GroupChatClient(String host, int port) throws IOException {
        socketChannel = SocketChannel.open(new InetSocketAddress(host, port));
        socketChannel.configureBlocking(false);
        selector = Selector.open();
        //注册事件,关心读事件
        socketChannel.register(selector, SelectionKey.OP_READ);
        System.out.println("我是:" + socketChannel.getLocalAddress());
    }
    /**
     * 读消息
     */
    private void read() {
        try {
            if(selector.select() == 0){
                //没有事件,return
                return;
            }
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                SelectionKey selectionKey = iterator.next();
                if(selectionKey.isReadable()){//判断是 读 事件
                    SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    //读取数据到  byteBuffer 缓冲区
                    socketChannel.read(byteBuffer);
                    //打印数据
                    System.out.println(new String(byteBuffer.array()));
                }
                iterator.remove();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 发送数据
     * @param msg 消息
     * @throws IOException
     */
    private void send(String msg) throws IOException {
        socketChannel.write(ByteBuffer.wrap(new String(msg.getBytes(),"utf-8").getBytes()));
    }
 
 
    public static void main(String[] args) throws IOException {
        //创建客户端 指定 ip端口
        GroupChatClient groupChatClient = new GroupChatClient("127.0.0.1",8888);
        //启动一个线程来读取数据
        new Thread(()->{
            while (true){
                groupChatClient.read();
            }
        }).start();
        //Scanner 发送数据
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()){
            String s = scanner.nextLine();
            //发送数据
            groupChatClient.send(s);
        }
 
    }
}

你可能感兴趣的:(java,AIO,BIO,NIO,nio,java)