IO通讯模型(一)同步阻塞模式BIO(Blocking IO)

几个概念

阻塞IO非阻塞IO 这两个概念是程序级别的。主要描述的是程序请求操做系统IO操做后,若是IO资源没有准备好,那么程序该如何处理的问题:前者等待;后者继续执行(可是使用线程一直轮询,直到有IO资源准备好了)。html

同步IO异步IO,这两个概念是操做系统级别的。主要描述的是操做系统在收到程序请求IO操做后,若是IO资源没有准备好,该如何响应程序的问题:前者不响应,直到IO资源准备好之后;后者返回一个标记(好让程序和本身知道之后的数据往哪里通知),当IO资源准备好之后,再用事件机制返回给程序。java

同步阻塞模式(Blocking IO)

同步阻塞IO模型是最简单的IO模型,用户线程在内核进行IO操做时若是数据没有准备号会被阻塞。git

BIO
图片来源:www.masterraghu.com

伪代码表示以下:github

{
    // 阻塞,直到有数据
	read(socket, buffer);
	process(buffer);
}
复制代码

BIO通讯方式的特色面试

  1. 一个线程负责链接,多线程则为每个接入开启一个线程。
  2. 一个请求一个应答。
  3. 请求以后应答以前客户端会一直等待(阻塞)。

BIO通讯方式在单线程服务器下一次只能处理一个请求,在处理完毕以前一直阻塞。所以不适用于高并发的状况。不过可使用多线程稍微改进。服务器

BIO通讯模型-来源于慕课网

Java同步阻塞模式

Java中的阻塞模式BIO,就是在java.net包中的Socket套接字的实现,Socket套接字是TCP/UDP等传输层协议的实现。网络

Java同步阻塞模式编码

多线程客户端

为了测试服务端程序,能够先编写一个多线程客户端用于请求测试。多线程

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.concurrent.CountDownLatch;

/** * <p> * BIO测试 * 模拟20个客户端并发请求,服务端则使用单线程。 * * @Author niujinpeng * @Date 2018/10/15 10:50 */
public class SocketClient {
    public static void main(String[] args) throws InterruptedException {
        Integer clientNumber = 20;
        CountDownLatch countDownLatch = new CountDownLatch(clientNumber);

        // 分别启动20个客户端
        for (int index = 0; index < clientNumber; index++, countDownLatch.countDown()) {
            SocketClientRequestThread client = new SocketClientRequestThread(countDownLatch, index);
            new Thread(client).start();
        }

        synchronized (SocketClient.class) {
            SocketClient.class.wait();
        }
    }
}

/** * <p> * 客户端,用于模拟请求 * * @Author niujinpeng * @Date 2018/10/15 10:53 */
class SocketClientRequestThread implements Runnable {

    private CountDownLatch countDownLatch;

    /** * 线程的编号 */
    private Integer clientIndex;


    public SocketClientRequestThread(CountDownLatch countDownLatch, Integer clientIndex) {
        this.countDownLatch = countDownLatch;
        this.clientIndex = clientIndex;
    }

    @Override
    public void run() {
        Socket socket = null;
        OutputStream clientRequest = null;
        InputStream clientResponse = null;
        try {
            socket = new Socket("localhost", 83);
            clientRequest = socket.getOutputStream();
            clientResponse = socket.getInputStream();

            //等待,直到SocketClientDaemon完成全部线程的启动,而后全部线程一块儿发送请求
            this.countDownLatch.await();

            // 发送请求信息
            clientRequest.write(("这是第" + this.clientIndex + "个客户端的请求").getBytes());
            clientRequest.flush();

            // 等待服务器返回消息
            System.out.println("第" + this.clientIndex + "个客户端请求发送完成,等待服务器响应");
            int maxLen = 1024;
            byte[] contentBytes = new byte[maxLen];
            int realLen;
            String message = "";

            // 等待服务端返回,in和out不能cloese
            while ((realLen = clientResponse.read(contentBytes, 0, maxLen)) != -1) {
                message += new String(contentBytes, 0, realLen);
            }
            System.out.println("第" + this.clientIndex + "个客户端接受到来自服务器的消息:" + message);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            try {
                if (clientRequest != null) {
                    clientRequest.close();
                }
                if (clientRequest != null) {
                    clientResponse.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
复制代码

单线程服务端

由于Java中的Socket就是BIO的模式,所以咱们能够很简单的编写一个BIO单线程服务端。架构

SocketServer.java并发

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

/** * BIO服务端 * <p> * 单线程阻塞的服务器端 * * @Author niujinpeng * @Date 2018/10/15 11:17 */
public class SocketServer {

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(83);
        try {
            while (true) {
                // 阻塞,直到有数据准备完毕
                Socket socket = serverSocket.accept();

                // 开始收取信息
                InputStream input = socket.getInputStream();
                OutputStream output = socket.getOutputStream();
                Integer sourcePort = socket.getPort();
                int maxLen = 1024 * 2;
                byte[] contextBytes = new byte[maxLen];

                // 阻塞,直到有数据准备完毕
                int realLen = input.read(contextBytes, 0, maxLen);
                // 读取信息
                String message = new String(contextBytes, 0, realLen);

                // 输出接收信息
                System.out.println("服务器收到来自端口【" + sourcePort + "】的信息:" + message);
                // 响应信息
                output.write("Done!".getBytes());

                // 关闭
                output.close();
                input.close();
                socket.close();

            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (serverSocket != null) {
                serverSocket.close();
            }
        }
    }
}

复制代码

多线程服务端

单线程服务器,在处理请求时只能同时处理一条,也就是说若是在请求到来时发现有请求还没有处理完毕,只能等待处理,所以使用多线程改进服务端。

SocketServerThread.java

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/** * BIO服务端 * <p> * 多线程的阻塞的服务端 * <p> * 固然,接收到客户端的socket后,业务的处理过程能够交给一个线程来作。 * 但仍是改变不了socket被一个一个的作accept()的状况。 * * @Author niujinpeng * @Date 2018/10/15 11:17 */
public class SocketServerThread implements Runnable {

    /** * 日志 */
    private static final Logger logger = LoggerFactory.getLogger(SocketServerThread.class);

    private Socket socket;

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

    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(83);
        try {
            while (true) {
                Socket socket = serverSocket.accept();
                //固然业务处理过程能够交给一个线程(这里可使用线程池),而且线程的建立是很耗资源的。
                //最终改变不了.accept()只能一个一个接受socket的状况,而且被阻塞的状况
                SocketServerThread socketServerThread = new SocketServerThread(socket);
                new Thread(socketServerThread).start();
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            if (serverSocket != null) {
                serverSocket.close();
            }
        }
    }


    @Override
    public void run() {
        InputStream in = null;
        OutputStream out = null;
        try {
            //下面咱们收取信息
            in = socket.getInputStream();
            out = socket.getOutputStream();
            Integer sourcePort = socket.getPort();
            int maxLen = 1024;
            byte[] contextBytes = new byte[maxLen];
            //使用线程,一样没法解决read方法的阻塞问题,
            //也就是说read方法处一样会被阻塞,直到操做系统有数据准备好
            int realLen = in.read(contextBytes, 0, maxLen);
            //读取信息
            String message = new String(contextBytes, 0, realLen);

            //下面打印信息
            logger.info("服务器收到来自于端口:" + sourcePort + "的信息:" + message);

            //下面开始发送信息
            out.write("回发响应信息!".getBytes());
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        } finally {
            //试图关闭
            try {
                if (in != null) {
                    in.close();
                }
                if (out != null) {
                    out.close();
                }
                if (this.socket != null) {
                    this.socket.close();
                }
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
            }
        }
    }
}
复制代码

看起来多线程增长了服务能力,可是很明显多线程改进以后仍有如下局限性

  • 接收和通知处理结果的过程依旧是单线程的。
  • 系统能够建立的线程数量有限。cat /proc/sys/kernel/threads-max能够查看能够建立的线程数量。
  • 若是线程较多,CPU须要更多的时间切换,处理真正业务的时间就会变少。
  • 建立线程会消耗较多资源,JVM建立一个线程都会默认分配128KB空间。
  • 多线程也没法解决由于调用底层系统同步IO而决定的同步IO机制。

同步阻塞模式总结

BIO模式由于进程的阻塞挂起,不会消耗过多的CPU资源,并且开发难度低,比较适合并发量小的网络应用开发。同时很容易发现由于请求IO会阻塞进程,因此不时候并发量大的应用。若是为每个请求分配一个线程,系统开销就会过大。

同时在Java中,使用了多线程来处理阻塞模式,也没法解决程序在accept()read()时候的阻塞问题。由于accept()read()的IO模式支持是基于操做系统的,若是操做系统发现没有套接字从指定的端口传送过来,那么操做系统就会等待。这样accept()read()方法就会一直等待。


GitHub 源码:github.com/niumoo/java…

此文参考文章:5种IO模型、阻塞IO和非阻塞IO、同步IO和异步IO

此文参考文章:架构设计:系统间通讯(3)——IO通讯模型和JAVA实践 上篇


<完>
我的网站:www.codingme.net
若是你喜欢这篇文章,能够关注公众号,一块儿成长。 关注公众号回复资源能够没有套路的获取全网最火的的 Java 核心知识整理&面试核心资料。

公众号
相关文章
相关标签/搜索