Netty系列第二章 NIO 1.1 伪异步I/O

NIO入门

伪异步I/O

1.伪异步I/O的诞生和定义
首先伪异步I/O是为了解决BIO中的一个客户端就要服务端一个线程处理的问题而诞生的。
伪异步I/O是通过线程池和任务队列来实现的,无论有多少客户端,线程池都可以灵活的调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽,当有新的客户端接入时,将客户端的socket封装成一个Task(该任务实现Runnable接口)投递到后端的线程池中处理。

  1. 伪异步I/O创建的TimeServer源码
package com.carfi.netty.fakeio;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 1. @author: ll
 2. @time: 2020/6/14 23:15
 */
public class TimeServer {
    public static void main(String[] args) throws IOException {
        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
            }
        }
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(port);
            System.out.println("Thr rime server is start in port : " + port);
            Socket socket = null;
            //线程池
            TimeServerHandlerExecutePool timeServerHandlerExecutePool = new TimeServerHandlerExecutePool(50, 10000);
            while (true) {
                socket = serverSocket.accept();
                //线程池执行处理客户端线程任务
                timeServerHandlerExecutePool.execute(new TimtServerHandler(socket));
            }
        } finally {
            if (serverSocket != null) {
                System.out.println("The time server close");
                serverSocket.close();
                serverSocket = null;
            }
        }
    }
}

其中客户端处理代码由线程池完成,将请求socket封装为task,调用线程池execute方法执行,避免每个请求都创建一个新的线程
3. 伪异步I/O创建的TimeServerHandlerExecutePool源码

package com.carfi.netty.fakeio;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 服务端处理客户端连接控制线程池
 * @author: ll
 * @time: 2020/6/14 23:20
 */
public class TimeServerHandlerExecutePool {
    private ExecutorService executorService;

    /**
     *
     * @param maxPollSize 最大线程数量
     * @param queueSize 最大阻塞队列数量
     */
    public TimeServerHandlerExecutePool(int maxPollSize, int queueSize) {
        /**
         * Runtime.getRuntime().availableProcessors() 用来设置该线程池核心线程数量
         * maxPollSize 用来设置该线程池最大线程数量
         * 120L 线程池中超过corePoolSize数目的空闲线程最大存活时间
         * TimeUnit.SECONDS 时间单位
         * new ArrayBlockingQueue<Runnable>(queueSize) 阻塞任务队列,当线程池达到corePoolSize时,新提交任务将被放入阻塞任务队列中,等待线程池中任务调度执行
         * 当阻塞任务队列已满,且最大线程池大小>核心线程数量时(此时workQueue已满),新提交任务会创建新线程执行任务
         */
        executorService = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),
                maxPollSize, 120L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueSize));
    }

    public void execute(Runnable task) {
        executorService.execute(task);
    }

    public static void main(String[] args) {
        System.out.println(Runtime.getRuntime().availableProcessors());
    }

}
  1. 伪异步I/O创建的TimtServerHandler源码
package com.carfi.netty.fakeio;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Date;

/**
 * @author: ll
 * @time: 2020/6/11 22:56
 */
public class TimtServerHandler implements Runnable {

    private Socket socket;

    public TimtServerHandler() {
    }

    public TimtServerHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            out = new PrintWriter(this.socket.getOutputStream(), true);
            String currentTime = null;
            String body = null;
            while (true) {
                body = in.readLine();
                if (body == null) {
                    break;
                }
                System.out.println("The time server receive order : " + body);
                currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
                out.println(currentTime);
            }
        } catch (Exception e) {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
                in = null;
            }
            if (out != null) {
                out.close();
                out = null;
            }
            if (this.socket != null) {
                try {
                    this.socket.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
                this.socket = null;
            }
        }
    }
}

  1. 伪异步I/O创建的TimeClient源码
package com.carfi.netty.fakeio;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

/**
 * @author: ll
 * @time: 2020/6/11 23:26
 */
public class TimeClient {
    public static void main(String[] args) {
        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
                //采用默认值
            }
        }
        Socket socket = null;
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            socket = new Socket("127.0.0.1", port);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(), true);
            out.println("QUERY TIME ORDER");
            System.out.println("Send order 2 server succedd.");
            String resp = in.readLine();
            System.out.println("Now is : " + resp);
        } catch (IOException e) {
           //不需处理
        } finally {
            if (out != null) {
                out.close();
                out = null;
            }
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                in = null;
            }
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                socket = null;
            }
        }
    }
}

伪异步I/O的优点:

  • 由于线程池和消息队列是有界的,无论客户端并发连接数有多大,都不会导致线程个数膨胀或内存溢出

伪异步I/O的缺点:

  • 伪异步底层依然是同步阻塞模型
  • 对Socket输入流读取时,会一直阻塞,直到 有数据可读,可用数据已经读取完毕,发生空指针异常或者I/O异常
  • 当对方发送请求或者应答消息比较缓慢或者网络延迟,读取输入流乙方的通信线程也将会被长时间堵塞,如果堆放要60s才能够将数据发送完成,那么读取一方的线程也将会被同步阻塞60s,其它接入消息只能在阻塞队列中排队
  • 当调用OutputStream的write方法写输出流的时候,也将被堵塞,直到所有要发送的字节全部写入完毕,或者发生异常,如果消息接收方处理缓慢,同步阻塞I/O将会导致write方法被无限期堵塞

伪异步I/O由于对方应答时间过长可能引起的级联故障:

  • 服务端也将延长应答时间
  • 采用伪异步I/O的线程正在读取故障服务节点的响应,由于读取输入流是阻塞的,也将被同步阻塞
  • 假如所有的可用线程都被故障服务器堵塞,那后续的所有I/O消息都将在队列中排队
  • 由于线程池采取阻塞队列实现,当队列积满之后,后续入队列的操作将被阻塞
  • 新的客户端请求将被拒绝,发生超时连接
  • 如果所有的连接全部超时,可认为系统已崩溃,无法接受新的请求消息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值