使用NIO实现简单的客户端和服务端聊天demo

2,304 阅读3分钟

服务端代码实现

package chat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;

/**
 * 聊天工具服务端实现
 */
public class ChatServer {
    private ServerSocketChannel serverSocketChannel;    //监听通道
    private Selector selector;                          //选择器
    private static final int PORT = 9999;               //服务器端口

    public ChatServer(){
        try {
            //1. 得到监听通道
            serverSocketChannel = ServerSocketChannel.open();
            //2. 得到选择器
            selector = Selector.open();
            //3. 绑定端口
            serverSocketChannel.bind(new InetSocketAddress(PORT));
            //4. 设置非阻塞模式
            serverSocketChannel.configureBlocking(false);
            //5. 将监听通道注册到选择器并监听accept事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            printInfo("Chat Server is ready......");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start(){
        try {
            while (true) {                  //不停监控
                if(selector.select(2000) == 0){
                    System.out.println("Server:没有客户端找我,我就干别的事");
                    continue;
                }
                
                //一旦监听到有客户端请求事件发生,使用选择器获取所有selectedKeys的迭代器对象,挨个判断事件类型
                Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
                while (keyIterator.hasNext()) {
                    SelectionKey selectionKey = keyIterator.next();
                    //连接请求事件
                    if (selectionKey.isAcceptable()) {
                        //获取通道
                        SocketChannel socketChannel = serverSocketChannel.accept();
                        //设置非阻塞模式
                        socketChannel.configureBlocking(false);
                        //将该通道注册到选择器中
                        socketChannel.register(selector,SelectionKey.OP_READ);
                        System.out.println(socketChannel.getRemoteAddress().toString().substring(1) + "上线了...");
                    }

                    //读取数据事件
                    if (selectionKey.isReadable()) {
                        readMsg(selectionKey);
                    }

                    //一定要把当前key删掉,防止重复处理
                    keyIterator.remove();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void printInfo(String msg) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("[" + dateFormat.format(new Date()) + "] ->" + msg);
    }

    //读取客户端发来的消息并广播出去
    private void readMsg(SelectionKey selectionKey) throws Exception{
        //从selectionKey中获取通道
        SocketChannel channel = (SocketChannel)selectionKey.channel();
        //设置缓冲区对象
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        //将通道中的数据读取到缓冲区
        int count = channel.read(byteBuffer);
        if (count > 0) {
            String msg = new String(byteBuffer.array());
            printInfo(msg);
            //发广播给其他客户端
            broadCast(channel,msg);
        }
    }

    //给所有客户端发送广播
    private void broadCast(SocketChannel except, String msg) throws Exception{
        System.out.println("服务器发送了广播...");
        for (SelectionKey key: selector.keys()) {
            //从selectionKey中获取通道
            Channel targetChannel = key.channel();
            //判断该通道是否为SocketChannel类型
            if (targetChannel instanceof SocketChannel && targetChannel != except) {
                SocketChannel destChannel = (SocketChannel)key.channel();
                //将数据放入缓冲区
                ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes());
                //将缓冲区中的数据写入到目标通道中
                destChannel.write(byteBuffer);
            }
        }
    }

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


客户端代码实现

package chat;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/**
 * 聊天程序客户端
 */
public class ChatClient {
    private final String HOST = "127.0.0.1";        //服务器地址
    private int PORT = 9999;                        //服务器端口
    private SocketChannel socketChannel;            //网络通道
    private String userName;                        //聊天用户名

    public ChatClient() throws Exception{
        //1. 得到网络通道
        socketChannel = SocketChannel.open();
        //2. 设置非阻塞方式
        socketChannel.configureBlocking(false);
        //3. 提供服务器端的IP地址和端口号
        InetSocketAddress socketAddress = new InetSocketAddress(HOST, PORT);
        //4. 连接服务器端
        if (!socketChannel.connect(socketAddress)) {    //第一次尝试连接服务器端
            //再次连接服务器端,nio非阻塞式的优势
            while (!socketChannel.finishConnect()) {
                System.out.println("Client:连接服务器的同时,我还可以干别的事情");
            }
        }

        //5. 得到客户端IP地址和端口信息,作为聊天用户名使用
        userName = socketChannel.getLocalAddress().toString().substring(1);
        System.out.println("----------------------Client(" + userName + ") is ready-----------------------");
    }

    /**
     * 客户端发送消息的方法
     * @param msg
     * @throws Exception
     */
    public void sendMsg(String msg) throws Exception{
        //约定客户端发送的消息为"bye"时,聊天终止
        if (msg.equalsIgnoreCase("bye")) {
            //关闭通道
            socketChannel.close();
            return;
        }
        msg = userName + "说: " + msg;
        //将消息写入缓存区
        ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes());
        //将缓存区中的数据写入通道
        socketChannel.write(byteBuffer);
    }

    /**
     * 从服务器端接收数据
     * @throws Exception
     */
    public void receiveMsg() throws Exception{
        //构造一个缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        //从通道中读取数据到缓冲区
        int count = socketChannel.read(byteBuffer);
        if (count > 0) {
            String msg = new String(byteBuffer.array());
            //输出接收到服务器中的数据
            System.out.println(msg.trim());
        }
    }
}

启动客户端

package chat;

import java.util.Scanner;

/**
 * 启动聊天程序客户端
 */
public class TestClient {
    public static void main(String[] args) throws Exception{
        ChatClient client = new ChatClient();

        //不清楚服务器何时发数据给客户端,需重启一个线程循环监测服务器的数据信息
        new Thread() {
            public void run(){
                try {
                    while (true) {
                        client.receiveMsg();
                        Thread.sleep(2000);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();

        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) {
            String msg = scanner.nextLine();
            client.sendMsg(msg);
        }
    }
}