【JDK】【网络编程】NIO实现服务端客户端、NIO实现Tomcat

1、Java NIO 基本介绍

  1. Java NIO 全称 java non-blocking IO,是指 JDK 提供的新 API。从 JDK1.4 开始,Java 提供了一系列改进的
    输入/输出的新特性,被统称为 NIO(即 New IO),是同步非阻塞的
  2. NIO 相关类都被放在 java.nio 包及子包下,并且对原 java.io 包中的很多类进行改写。
  3. NIO 有三大核心部分:Channel( 通道) ,Buffer( 缓冲区), Selector(选择器)。
  4. NIO 是 是 区 面向缓冲区 ,向 或者面向 块 块 编程的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络。
  5. Java NIO 的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
  6. HTTP2.0 使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比 HTTP1.1 大了好几个数量级。

初学者最困难的地方是 Channel通道 和 Selector选择器 的关系,以及确定当前正在使用的通道是 Selector下管理的哪条通道

//得到当前通道 ip:端口号 socketChannel.getRemoteAddress()

//得到当前进程的 ip:端口号 socketChannel.getLocalAddress()

Selector 、 Channel 和 Buffer 的关系图(简单版)
在这里插入图片描述

2、NIO基本实现服务端与客户端消息接收发送

图示
在这里插入图片描述

服务端 NioServer

package study.tangxz.nio;

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

/**
 * @Info:
 * @Author: 唐小尊
 * @Date: 2020/10/29 04:27
 */
public class NioServer {
    private ServerSocketChannel ssc;
    private SocketChannel sc;
    private Selector selector;
    private String host = "127.0.0.1";
    private int port = 9999;

    NioServer() {
        try {
            ssc = ServerSocketChannel.open();
            //设置为非阻塞
            ssc.configureBlocking(false);
            //给当前服务器主线程创建一个SocketChannel绑定ip地址和端口号
            ssc.socket().bind(new InetSocketAddress(host, port));
            //得到一个选择器
            selector = Selector.open();
            //把服务SocketChannel给注册进selector
            ssc.register(selector, SelectionKey.OP_ACCEPT);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
	//启动服务端方法
    public void start() {
        System.out.println("监听线程: " + Thread.currentThread().getName());
        while (true) {
            try {
                //监听所有的连接请求,如果连接成功,则通过下面这条代码。
                //如果没有任何的请求连接,则在这里等待。
                int select = selector.select();
                //得到当前key的迭代器
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                if (select > 0) {
                    while (iterator.hasNext()) {
                        SelectionKey key = iterator.next();
                    	//每次迭代之后,移除当前迭代内容,防止重复迭代
                        iterator.remove();
                        //如果该key为连接监听事件,则进入下面的方法
                        if (key.isAcceptable()) {
                            //接受一个连接,返回代表这个连接的通道对象
                            //用于监听服务器的读事件,客户端的写事件
                            SocketChannel socketChannel = ssc.accept();
                            //设置为非阻塞
                            socketChannel.configureBlocking(false);
                            //放入selector选择器中,并让选择器监听该通道读事件
                            socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024 * 16));
                            System.out.println(socketChannel.getRemoteAddress() + "  服务器连接成功!注册读事件!");
                        }
                        //如果该key为监听到的读事件
                        if (key.isReadable()) {
                            //服务端就去读,读完后,发送给所有非发送者的其他客户端
                            read(key);
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //SelectionKey相当于是包装了的Channel,
    //其可以得到该SelectionKey中对应的Channel和其通道中的数据
    public void read(SelectionKey key) {
        SocketChannel socketChannel = null;
        try {
            //得到当前发送消息者SelectionKey对应的Channel
            socketChannel = (SocketChannel) key.channel();
            //创建一个字节Buffer缓冲区,用来获取通道中的数据
            ByteBuffer buffer = ByteBuffer.allocate(1024 * 16);
            //把数据写入buffer,并返回数据的长度
            int read = socketChannel.read(buffer);
            if (read > 0) {//如果长度大于0,说明有数据
                System.out.println("【接收到客户端的消息】" + new String(buffer.array(), "UTF-8"));
                //当服务端获取到正确的数据时,就调用写方法,把数据返回给除了服务端channel之外的所有channel,细节看方法内部代码
            	write(socketChannel,new String(buffer.array()));
            }
        } catch (IOException e) {
            try {
                //如果连接断开,就会触发异常
                System.out.println(socketChannel.getRemoteAddress() + " 离线了..");
                //取消注册
                key.cancel();
                //关闭通道
                socketChannel.close();
            } catch (IOException e2) {
                e2.printStackTrace();
            }
            e.printStackTrace();
        }

    }
	
    //服务端发送消息方法
    public void write(SocketChannel socketChannel, String msg) {
        System.out.println("服务器转发数据给客户端线程: " + Thread.currentThread().getName());
        //该 selector 绑定了服务端ServerSocketChannel
        //连接该 服务端地址 的所有channel通道,都在这个selector中
        //遍历 selector选择器中的所有 SelectionKey
        for (SelectionKey key : selector.keys()) {
            //得到其中的Channel
            Channel channel = key.channel();
            //转发不转发给服务端
            //channel instanceof SocketChannel 过滤掉 ServerSocketChannel
            //channel != socketChannel 过滤掉发送者本身
            if (channel instanceof SocketChannel && channel != socketChannel) {
                SocketChannel nowChannel = (SocketChannel) channel;
                try {
					//当前通道 socketChannel.getRemoteAddress()
                    System.out.println("now:"+socketChannel.getLocalAddress());
                    nowChannel.write(ByteBuffer.wrap(msg.getBytes()));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } else if (channel instanceof SocketChannel) {
                String content = "【服务端回复】你好客户端,我已收到你发的消息";
                try {
                    SocketChannel nowChannel = (SocketChannel) channel;
                    nowChannel.write(ByteBuffer.wrap(content.getBytes()));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

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

}

客户端 NioClient

package study.tangxz.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Scanner;

/**
 * @Info:
 * @Author: 唐小尊
 * @Date: 2020/10/29 04:27
 */
public class NioClient {
    private final String host = "127.0.0.1";
    private final int port = 9999;
    private SocketChannel socketChannel;
    private Selector selector;
    private String username;

    NioClient(){
        try {
            //打开一个通道,通道的另一头是这个服务端
            //服务端的Selector中配置了服务端的ServerSocketChannel,与该ServerSocketChannel有关联的所有SocketChannel通道发送数据,都会默认把自己(当前通道)给放入Selector中。
            socketChannel = SocketChannel.open(new InetSocketAddress(host,port));
            //设置通道为非阻塞通道
            socketChannel.configureBlocking(false);
            //打开一个选择器
            selector = Selector.open();
            //这个selector选择器监听该通道的读方法,这个通道为接收服务端消息的通道
            socketChannel.register(selector, SelectionKey.OP_READ);
            //得到username,ip地址:端口号
            username = socketChannel.getLocalAddress().toString().substring(1);
            System.out.println(username + " is ok...");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public void read(){
        try {
            //等待监听的事件发生,register(selector, SelectionKey.OP_READ);
            selector.select();
            //得到监听到的事件通道
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                iterator.remove();
                //这个通道为服务端发送数据的通道
                SocketChannel channel = (SocketChannel) key.channel();
                System.out.println("本地地址:"+channel.getLocalAddress());
                ByteBuffer buffer = ByteBuffer.allocate(1024*16);
                int read = channel.read(buffer);
                if (read>0){
                    System.out.println(new String(buffer.array()));
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    public void write(String msg){
        msg = username + " 说:" + msg;
        try {
            //这个socketChannel就是当前通道,write直接发送给服务端的ServerSocketChannel,ServerSocketChannel收到通道后,会默认直接连接在服务端的selector中。
            socketChannel.write(ByteBuffer.wrap(msg.getBytes()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        //启动我们客户端
        NioClient nioClient = new NioClient();

        new Thread(()->{
            while (true){
                nioClient.read();
                try{
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        Scanner sc = new Scanner(System.in);
        while (sc.hasNextLine()){
            String msg = sc.nextLine();
            nioClient.write(msg);
        }
    }
}

3、NIO实现Tomcat

MyServlet

package study.tangxz.nio2;

/**
 * @Info:
 * @Author: 唐小尊
 * @Date: 2020/10/29 15:47
 */
public abstract class MyServlet {

    public abstract void doGet(MyRequest myRequest,MyResponse myResponse);
    public abstract void doPost(MyRequest myRequest,MyResponse myResponse);

    public void service(MyRequest myRequest,MyResponse myResponse){
        if (myRequest.getMethod().equalsIgnoreCase("POST")){
            doPost(myRequest,myResponse);
        }else if (myRequest.getMethod().equalsIgnoreCase("GET")){
            doGet(myRequest,myResponse);
        }

    }
}

HelloWorldServlet

package study.tangxz.nio2;

import java.io.IOException;

/**
 * @Info:
 * @Author: 唐小尊
 * @Date: 2020/10/29 15:47
 */
public class HelloWorldServlet extends MyServlet{
    @Override
    public void doGet(MyRequest myRequest,MyResponse myResponse) {
        try {
            myResponse.write("get hello world");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void doPost(MyRequest myRequest, MyResponse myResponse) {
        try {
            myResponse.write("post hello world");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

MyRequest

package study.tangxz.nio2;

import com.sun.xml.internal.stream.events.StartElementEvent;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.HashMap;

/**
 * @Info:
 * @Author: 唐小尊
 * @Date: 2020/10/29 15:47
 */
public class MyRequest {
    private String url;
    private String method;
    private HashMap<String,String> param = new HashMap<>();

    public MyRequest(SelectionKey selectionKey) throws IOException{
        //从契约获取通道
        SocketChannel channel = (SocketChannel) selectionKey.channel();
        String httpRequest = "";
        ByteBuffer bb = ByteBuffer.allocate(16*1024);
        int length = 0;
        length = channel.read(bb);
        if (length<0){
            selectionKey.channel();
        }else {
            httpRequest = new String(bb.array()).trim();
            String httpHead = httpRequest.split("\n")[0];
            url = httpHead.split("\\s")[1].split("\\?")[0];
            String path = httpHead.split("\\s")[1];
            method = httpHead.split("\\s")[0];

            //以下是拆分get请求的参数数据
            String[] params = path.indexOf("?")>0?path.split("\\?")[1].split("\\&"):null;
            if (params!=null){
                try {
                    for (String tmp:params){
                        param.put(tmp.split("\\=")[0],tmp.split("\\=")[1]);
                    }
                }catch (NullPointerException e){
                    e.printStackTrace();
                }
            }
            System.out.println(this);
        }
        bb.flip();
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    @Override
    public String toString() {
        return "MyRequest{" +
                "url='" + url + '\'' +
                ", method='" + method + '\'' +
                ", param=" + param +
                '}';
    }
}

MyResponse

package study.tangxz.nio2;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;

/**
 * @Info:
 * @Author: 唐小尊
 * @Date: 2020/10/29 15:47
 */
public class MyResponse {
    private SelectionKey selectionKey;
    public MyResponse(SelectionKey selectionKey){
        this.selectionKey = selectionKey;
    }

    public void write(String content) throws IOException{
        //拼接相应数据包
        StringBuffer httpResponse = new StringBuffer();
        httpResponse.append("HTTP/1.1 200 OK\n")
                .append("\r\n")
                .append("<html><body>")
                .append(content)
                .append("</body></html>");

        //转换为ByteBuffer
        ByteBuffer bb = ByteBuffer.wrap(httpResponse.toString().getBytes());
        SocketChannel channel = (SocketChannel) selectionKey.channel();
        long len = channel.write(bb);
        if (len==-1){
            selectionKey.cancel();
        }
        channel.close();
        selectionKey.cancel();
    }
}

ServletMapping

package study.tangxz.nio2;

/**
 * @Info:
 * @Author: 唐小尊
 * @Date: 2020/10/29 15:47
 */
public class ServletMapping {
    private String servletName;
    private String url;
    private String clazz;

    public ServletMapping(String servletName, String url, String clazz) {
        this.servletName = servletName;
        this.url = url;
        this.clazz = clazz;
    }

    public String getServletName() {
        return servletName;
    }

    public void setServletName(String servletName) {
        this.servletName = servletName;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getClazz() {
        return clazz;
    }

    public void setClazz(String clazz) {
        this.clazz = clazz;
    }
}

ServletMappingConfig

package study.tangxz.nio2;

import java.util.ArrayList;
import java.util.List;

/**
 * @Info:
 * @Author: 唐小尊
 * @Date: 2020/10/29 15:47
 */
public class ServletMappingConfig {
    public static List<ServletMapping> servletMappingList = new ArrayList<>();
    static {
        servletMappingList.add(new ServletMapping("Hello World","/world","study.tangxz.nio2.HelloWorldServlet"));

    }
}

MyTomcat

package study.tangxz.nio2;

import study.tangxz.nio.NioTomcat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Info:
 * @Author: 唐小尊
 * @Date: 2020/10/29 15:47
 */
public class MyTomcat {
    private int port = 9999;
    private Map<String, String> urlServletMap = new HashMap<>();

    private Selector selector;
    private ExecutorService es = Executors.newCachedThreadPool();

    public MyTomcat() {

    }

    public MyTomcat(int port) {
        this.port = port;
    }

    public void start() throws IOException {
        initServletMapping();

        //SelectorProvider 此类中的所有方法均可安全地被多个并发线程使用
        selector = SelectorProvider.provider().openSelector();
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        InetSocketAddress isa = new InetSocketAddress(9999);
        ssc.socket().bind(isa);

        ssc.register(selector, SelectionKey.OP_ACCEPT);

        ConcurrentLinkedQueue<MyRequest> requestList = new ConcurrentLinkedQueue<>();
        ConcurrentLinkedQueue<MyResponse> responseList = new ConcurrentLinkedQueue<>();

        while (true) {
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey sk = iterator.next();
                iterator.remove();

                if (sk.isAcceptable()) {
                    doAccept(sk);
                    //isValid() 告知此密钥是否有效。
                } else if (sk.isValid() && sk.isReadable()) {
                    requestList.add(getRequest(sk));
                    sk.interestOps(SelectionKey.OP_WRITE);
                } else if (sk.isValid() && sk.isWritable()) {
                    responseList.add(getResponse(sk));
                    sk.interestOps(SelectionKey.OP_READ);
                }

                if (!requestList.isEmpty() && !responseList.isEmpty()) {
                    dispatch(requestList.poll(), responseList.poll());
                }
            }
        }
    }
    private void doAccept(SelectionKey sk) throws IOException {
        ServerSocketChannel server = (ServerSocketChannel) sk.channel();
        SocketChannel clientChannel;
        try {
            //接受与ServerSocketChannel建立的连接。创建一条通道,提供给该客户端读写数据。
            clientChannel=server.accept();
            clientChannel.configureBlocking(false);
            clientChannel.register(selector,SelectionKey.OP_READ);
            System.out.println("【" + clientChannel.getRemoteAddress() + "】 上线了");
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    private MyRequest getRequest(SelectionKey sk) throws IOException {
        return new MyRequest(sk);
    }

    private MyResponse getResponse(SelectionKey sk) {
        return new MyResponse(sk);
    }

    private void dispatch(MyRequest myRequest, MyResponse myResponse) {
        if (myRequest == null||myResponse==null){
            return;
        }
        String clazz = urlServletMap.get(myRequest.getUrl());
        try {
            if (clazz==null){
                myResponse.write("404");
                return;
            }
            es.execute(()->{
                try {
                    Class<MyServlet> myServletClass = (Class<MyServlet>) Class.forName(clazz);
                    MyServlet myServlet = myServletClass.newInstance();
                    myServlet.service(myRequest,myResponse);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void initServletMapping() {
        for (ServletMapping servletMapping : ServletMappingConfig.servletMappingList) {
            urlServletMap.put(servletMapping.getUrl(), servletMapping.getClazz());
        }
    }

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

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值