NIO服务端与客户端简易实现

NIO服务端与客户端简易实现

NIO简介

Non-blocking I/O 或 New I/O

jdk1.4发布 1.8稳定

高并发网络服务器支持岗

编程模型

模型:对事务共性的抽象

编程模型:对编程共性的抽象

BIO网络模型缺点

阻塞式I/O模型

弹性伸缩能力差

多线程耗资源

大量并发情况下会出现多个线程等待,致使服务器压力增大,甚至使服务器崩掉

NIO网络模型猜想

Selector用于管理连接的客户端

NIO网络模型改进

基于非阻塞I/O实现

弹性伸缩能力强

单线程节省资源

NIO网络编程详解:NIO核心

Channel:通道

Buffer:缓冲区

Selector:选择器或多路复用器

Channel简介

双向性

非阻塞性

操作唯一性

Channel实现

文件类:FileChannel

UDP类:DatagramCHannel

TCP类:ServerSocketChannel/SocketChannel

Buffer简介

作用:读写Channel中数据

本质:一块内存区域

Buffer属性

Capacity:容量–表明数组最大容量字节数,如果超过容量必须将其清空才能继续填充数据

Position:位置–表示当前位置,插入数据后会向后移动到可插入数据单元,初始值为0,最大为下标减1

Limit:上限

Mark:标记

Selector简介

作用:I/O就绪选择

地位:NIO网络编程的基础

一个线程管理多个连接

SelectionKey简介

四种就绪状态常量

有价值的属性

NIO编程实现步骤

第一步:创建Selector

第二步:创建ServerSocketChannel,并绑定监听端口

第三步:将Channel设置为非阻塞模式

第四步:将Channel注册到Selector上,监听连接实际那

第五步:循环掉用Selector的select方法,检测就绪情况

第六步:掉用selectedKeys方法获取就绪channel集合

第七步:判断就绪事件种类,掉用业务处理方法

第八步:根据业务需要决定是否再次这个测监听时间,重复执行第三步操作

NIO网络编程实战

利用NIO编程只是,实现多人聊天室

NIO网络编程缺陷

麻烦:NOI类库和API繁杂

心累:可靠性能力补全,工作了和难度都非常大

有坑:Selector空轮询,导致CPU100% 主要体现在Linux系统

Server端

package com.nio;

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

/**
 * NIO服务器端
 *
 * @author Wangjinghao
 * @version v1.0.0
 * @date 2019/6/11
 */
public class NioServer {
    /* *
     * @Author Wangjinghao
     * @Description //TODO 启动
     * @Date 16:21 2019/6/11
     * @Param []
     * @return void
     **/
    public void start() throws IOException {
        /**
         * 1.创建Selector
         */
        Selector selector = Selector.open();
        /**
         * 2.通过ServerSocketChannel创建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 (; ; ) {//while(true)  c for;;
            /**
             * 获取可用channel数量
             */
            int readyChannels = selector.select();
            if (readyChannels == 0) continue;
            /**
             * 获取可用channel的集合
             */
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                /**
                 * selectionKey实例
                 */
                SelectionKey selectionKey = (SelectionKey) iterator.next();
                /**
                 * 移除Set中的当前selectionKey
                 */
                iterator.remove();
                /**
                 * 7.根据就绪转台,掉用对用方法处理业务逻辑
                 */
                /**
                 * 如果是 接入事件
                 */
                if (selectionKey.isAcceptable()) {
                    acceptHandler(serverSocketChannel, selector);
                }

                /**
                 * 如果是 可读事件
                 */
                if (selectionKey.isReadable()) {
                    readHandler(selectionKey, selector);
                }
            }
        }
    }

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

    /**
     * 可读事件处理器
     */
    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) {
            //广播给其他客户端
            System.out.println("::" + request);
            broadCast(selector,socketChannel,request);
        }
    }

    /**
     * 广播给其他客户端
     */
    private void broadCast(Selector selector,SocketChannel sourceChannel,String request) {
        /**
         * 获取到所有已接入的客户端cahnnel
         */
        Set<SelectionKey> selectionKeySet = selector.keys();
        //
        selectionKeySet.forEach(selectionKey->{
            Channel tagetCahnnel= selectionKey.channel();
            //剔除发消息的客户端
            if (tagetCahnnel instanceof  SocketChannel && tagetCahnnel!=sourceChannel){
                try {
                    //将消息发送到客户端
                    ((SocketChannel) tagetCahnnel).write(Charset.forName("UTF-8").encode(request));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        /**
         * 循环向所有哦channel广播信息
         */
    }

    /* *
     * @Author Wangjinghao
     * @Description //TODO 主方法
     * @Date 16:20 2019/6/11
     * @Param [args]
     * @return void
     **/
    public static void main(String[] args) throws IOException {
        NioServer nioServer = new NioServer();
        nioServer.start();
    }
}

ClientHandler端

package com.nio;

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;

/**
 * 客户端线程类,准吗接收服务器响应信息
 *
 * @author Wangjinghao
 * @version v1.0.0
 * @date 2019/6/11
 */
public class NioClientHandler implements Runnable {

    private Selector selector;

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

    @Override
    public void run() {
        try {
            for (; ; ) {
                int readyChannels = selector.select();
                if (readyChannels == 0) continue;
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = (SelectionKey) iterator.next();
                    iterator.remove();
                    if (selectionKey.isReadable()) {
                        readHandler(selectionKey, selector);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private void readHandler(SelectionKey selectionKey,
                             Selector selector) throws IOException {

        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        String response = "";
        while (socketChannel.read(byteBuffer) > 0) {
            byteBuffer.flip();
            response += Charset.forName("UTF-8").decode(byteBuffer);
        }
        socketChannel.register(selector, SelectionKey.OP_READ);
        //将服务器端信息打印到本地
        if (response.length() > 0) {
            System.out.println("" + response);
        }
    }
}

Client端

package com.nio;

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客户端
 *
 * @author Wangjinghao
 * @version v1.0.0
 * @date 2019/6/11
 */
public class NioClient {
    /**
     * 启动
     */
    public void  start(String nickname) throws IOException {
        /**
         * 连接服务器端
         */
        SocketChannel socketChannel=SocketChannel.open(new InetSocketAddress("127.0.0.1",8000));
        /**
         * 接收服务器端响应
         */
        //新开线程,专门负责来接收服务器端的响应数据
        //selector ,socketCahnnel ,注册
        Selector selector=Selector.open();
        socketChannel.configureBlocking(false);
        socketChannel.register(selector, SelectionKey.OP_READ);
        new Thread(new NioClientHandler(selector)).start();
        /**
         * 向服务器发送数据
         */
        Scanner input=new Scanner(System.in);
        while(input.hasNextLine()){
            String request=input.nextLine();
            if (request!=null && request.length()>0){
                socketChannel.write(Charset.forName("UTF-8").encode(nickname+":"+request));
            }
        }
    }

    public static void main(String[] args) throws IOException {
       // new NioClient().start();
    }
}

你可能感兴趣的:(Java)