NIO实现聊天室

3 篇文章 0 订阅
1 篇文章 0 订阅

NIO概述

NIO(Non-blocking I/O,在Java领域,也称为New I/O),是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。

三大核心部分

  • Channel(通道)
  • Buffer(缓冲区)
  • Selector(多路复用器) 

 

 

NIO聊天室 

# 服务端
package com.milla.study.netbase.expert.io.nio;

import com.google.code.yanf4j.util.ConcurrentHashSet;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * @Package: com.milla.study.netbase.expert.netty.nio.server
 * @Description: <NIO模式下的Server>
 * @Author: milla
 * @CreateDate: 2020/08/07 10:32
 * @UpdateUser: milla
 * @UpdateDate: 2020/08/07 10:32
 * @UpdateRemark: <>
 * @Version: 1.0
 */
@Slf4j
public class NioServer {
    /**
     * 消息缓存区
     */
    private static ByteBuffer readBuf = ByteBuffer.allocate(1024);
    /**
     * 保存的客户端
     */
    static Set<SocketChannel> clients = new ConcurrentHashSet<>();
    /**
     * 选择器
     */
    private static Selector selector;

    public static void main(String[] args) throws Exception {

        server();
    }

    private static void server() throws Exception {
        //获取一个channel
        ServerSocketChannel channel = ServerSocketChannel.open();
        //配置是否阻塞
        channel.configureBlocking(false);
        //获取socket
        ServerSocket server = channel.socket();
        //绑定服务及端口
        server.bind(new InetSocketAddress(InetAddress.getLocalHost(), 10010));
        //获取到选择器
        selector = Selector.open();
        //注册到选择器用以接受连接
        channel.register(selector, SelectionKey.OP_ACCEPT);

        log.info("server is start....");
        //一直能处理
        while (true) {
            //等待需要处理的新事件,阻塞将一直持续到下一个传入事件
            selector.select();
            //获取所有接收事件的selection-key实例
            Set<SelectionKey> readKeys = selector.selectedKeys();

            Iterator<SelectionKey> iterator = readKeys.iterator();

            while (iterator.hasNext()) {
                try {
                    SelectionKey key = iterator.next();
                    //检查时间是否是一个新的已经就绪可以被接受的连接
                    if (key.isAcceptable()) {
                        getConnect(key, selector);
                    }
                    //是否可以读取数据
                    if (key.isReadable()) {
                        getMessage(key);
                    }
                    //检查套接字是否已经准备写数据
//                    if (key.isWritable()) {
                    //写操作
//                    }
                    //处理完成之后要将key删除防止重复处理
                    iterator.remove();
                } catch (Exception e) {
                    log.info("错误:{}", e);
                    e.printStackTrace();
                }
            }

        }
    }

    /**
     * 获取连接
     *
     * @param key
     * @param selector
     * @throws Exception
     */
    private static void getConnect(SelectionKey key, Selector selector) throws Exception {
        //要操作的message
        ByteBuffer msg = ByteBuffer.wrap("connect success ".getBytes());
        ServerSocketChannel serverSocket = (ServerSocketChannel) key.channel();
        //接收一个连接
        SocketChannel client = serverSocket.accept();
        clients.add(client);
        //设置非阻塞
        client.configureBlocking(false);
        //监听读/写事件
        client.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
        //发送连接成功给客户端
        client.write(msg);
        log.info(" a new socket connected...{}", client);
    }

    /**
     * 读取数据
     *
     * @param key
     * @throws IOException
     */
    private static void getMessage(SelectionKey key) throws IOException {
        readBuf.clear();
        SocketChannel client = (SocketChannel) key.channel();
        try {
            int count = client.read(readBuf);
            if (count == -1) {
                client.shutdownOutput();
                client.shutdownInput();
                client.close();
                log.info("断开连接....");
                clients.remove(key);
            }
            byte[] bytes = new byte[count];
            readBuf.flip();
            readBuf.get(bytes);
            String message = new String(bytes, 0, count);
            log.info("接收到信息:{}", message);
            key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
            sendMessages2Clients(message);
        } catch (IOException e) {
            key.cancel();
            client.close();
            log.error("端开连接");
            clients.remove(key);
        }
    }

    /**
     * 接收到数据将数据群发到各个客户端
     *
     * @param message
     */
    private static void sendMessages2Clients(String message) {
        clients.stream().forEach(client -> {
            try {
                client.write(ByteBuffer.wrap(message.getBytes()));
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }
}
# 客户端
package com.milla.study.netbase.expert.io.nio;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetAddress;
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;

/**
 * @Package: com.milla.study.netbase.expert.io.nio
 * @Description: <NIO下的client>
 * @Author: milla
 * @CreateDate: 2020/08/07 11:16
 * @UpdateUser: milla
 * @UpdateDate: 2020/08/07 11:16
 * @UpdateRemark: <>
 * @Version: 1.0
 */
@Slf4j
public class NioClient {

    /**
     * 退出聊天的标识
     */
    private static volatile boolean connected = true;
    /**
     * 输入流
     */
    private static Scanner scanner = new Scanner(System.in);

    public static void main(String[] args) throws Exception {
        init();
        new Thread(() -> write()).start();
        new Thread(() -> read()).start();

    }

    /**
     * 写数据线程
     *
     * @throws IOException
     */
    private static void write() {

        while (connected) {
            try {
                //获取选择器
                selector.select();
                //获取选择器的已选择键集
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectedKeys.iterator();
                while (it.hasNext()) {
                    SelectionKey key = it.next();
                    //处理完当前的key需要删除,防止重复处理
                    it.remove();
                    if (key.isConnectable()) {
                        log.info("try connecting .... ");
                        SocketChannel channel = (SocketChannel) key.channel();
                        channel.configureBlocking(false);
                        channel.finishConnect();
                    }
                    //数据是否可写
                    if (key.isWritable()) {
                        sendMessage(key);
                    }
                    key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

    private static Selector selector;

    /**
     * 初始化连接服务器
     *
     * @throws IOException
     */
    private static void init() throws IOException {
        //打开socket通道
        SocketChannel sc = SocketChannel.open();
        //设置非阻塞
        sc.configureBlocking(false);
        //连接到服务器-指定主机名称和端口
        sc.connect(new InetSocketAddress(InetAddress.getLocalHost(), 10010));
        //打开选择器
        selector = Selector.open();
        //注册到服务器上的socket动作
        sc.register(selector, SelectionKey.OP_CONNECT);
    }

    /**
     * 读数据线程
     *
     * @throws IOException
     */
    private static void read() {

        while (connected) {
            try {
                //获取选择器
                selector.select();

                //获取选择器的已选择键集
                Set<SelectionKey> selectedKeys = selector.selectedKeys();

                Iterator<SelectionKey> it = selectedKeys.iterator();
                while (it.hasNext()) {
                    SelectionKey key = it.next();
                    //处理完当前的key需要删除,防止重复处理
                    it.remove();
                    if (key.isConnectable()) {
                        log.info("try connecting .... ");
                        SocketChannel channel = (SocketChannel) key.channel();
                        channel.configureBlocking(false);
                        //完成连接
                        channel.finishConnect();

                        //下面这种方式也可以
                        //sc.finishConnect();
                        //sc.register(selector, SelectionKey.OP_WRITE);
                    }
                    //数据是否可读
                    if (key.isReadable()) {
                        getMessage(key);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 发送数据
     *
     * @param key
     * @throws IOException
     */
    private static void sendMessage(SelectionKey key) {
        SocketChannel channel = (SocketChannel) key.channel();
        try {
            String requestLine = scanner.nextLine();
            //写数据
            channel.write(ByteBuffer.wrap(requestLine.getBytes()));
            key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
            //设置标识符退出聊天
            if ("quit".equals(requestLine)) {
                connected = false;
                log.info("退出聊天...", Thread.currentThread().getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 获取数据
     *
     * @param key
     * @throws IOException
     */
    private static void getMessage(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //清空缓存数据
        buffer.clear();
        //读取数据
        int count = channel.read(buffer);
        byte[] bytes = new byte[count];
        //弹出数据
        buffer.flip();
        buffer.get(bytes);
        log.info("接收到服务器消息:{}", new String(bytes));
    }
}

 PS : 在连接的时候需要使用finishConnect()函数,多开几个客户端,就可以实现聊天了

 

 

 

 

 

 

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值