一、BIO简介

  BIO是java1.4之前唯一的IO逻辑,在客户端通过socket向服务端传输数据,服务端监听端口。由于传统IO读数据的时候如果数据没有传达,IO会一直等待输入传入,所以当有请求过来的时候,新起一条线程对数据进行等待、处理,导致每一个链接都对应着服务器的一个线程。

  BIO是同步阻塞的,如图所示:

          

二、原理简述

  BIO对应linux io模型的阻塞IO,服务端提供IP和监听端口,客户端通过连接操作想服务端监听的地址发起连接请求,通过三次握手连接,如果连接成功建立,双方就可以通过套接字进行通信。

  重要组件
    ServerSocket:负责绑定IP地址,启动监听端口
    Socket:负责发起连接操作。连接成功后,双方通过输入和输出流进行同步阻塞式通信。

三、手写服务端

  首先我们来用ServerSocket和Socket写个服务端,并进行测试,代码以及注释如下:

public class BioServerSingle {  //blocking
public static void main(String[] args) {
// 服务端开启一个端口进行监听
int port = 8080;
ServerSocket serverSocket = null; //服务端
Socket socket; //客户端
InputStream in = null;
OutputStream out = null;
try { serverSocket = new ServerSocket(port); //通过构造函数创建ServerSocket,指定监听端口,如果端口合法且空闲,服务器就会监听成功
// 通过无限循环监听客户端连接,如果没有客户端接入,则会阻塞在accept操作
while (true) {
System.out.println("Waiting for a new Socket to establish" + " ," + new Date().toString());
socket = serverSocket.accept();//阻塞 三次握手
in = socket.getInputStream();
byte[] buffer = new byte[1024];
int length = 0;
while ((length = in.read(buffer)) > 0) {//阻塞
System.out.println("input is:" + new String(buffer, 0, length) + " ," + new Date().toString());
out = socket.getOutputStream();
out.write("success".getBytes());
System.out.println("Server end" + " ," + new Date().toString());
} }
} catch (Exception e) {
e.printStackTrace();
} finally {
// 必要的清理活动
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

  执行代码main方法启动服务端,并查看堆栈信息,可以看到服务端阻塞在ServerSocket的accept方法,如截图:

        

  此处即是ServerSocket的第一个阻塞点,接下来在控制台执行命令:

telnet localhost 

  打印此时堆栈信息可以看到ServerSocket阻塞在read方法,如截图所示:

        

  如果此时在控制台输入一些内容,那么服务端将接收到控制台输入,并会成功将服务端想要输出的的内容返回给控制台,如图所示:

        

  如图所示,这样就完成了一次socket的连接与数据传输,但是这样的Socket并未关闭,你仍旧可以继续在控制台输入内容,然后服务端接收输入,因为这一次的Socket并未关闭,从哪里可以看出来呢?

      

  如图所示:执行的代码并未重新等待一次新的Socket客户端请求,而且原先的Socket客户端(即控制台)仍旧可以继续同服务端进行输入输出交互,同时也可以打印堆栈信息进行确认,此时依旧阻塞在read方法而不是accept方法,此处不再截图。如果此时关闭客户端Socket(即控制台),那么此次Socket通信将结束,然后重新阻塞在Accept继续监听端口,等待新的客户端连接的到来。

  因此可以确认ServerSocket两个阻塞点,分别是accept、read,accept阻塞等待新的socket通信建立请求,read阻塞等待客户端输入。

四、手写客户端

public class BioClient {

    public static void main(String[] args) {
Socket clientSocket = null;
OutputStream outputStream = null;
InputStream inputStream = null;
try{
// 新建一个Socket请求
clientSocket = new Socket("localhost",8080);
System.out.println("Build the connection successfully!"+" ,"+new Date().toString());
outputStream = clientSocket.getOutputStream();
inputStream = clientSocket.getInputStream();
byte[] buffer = new byte[1024];
Scanner scanner = new Scanner(System.in);
while(true){
System.out.println("Please input a String !"+" ,"+new Date().toString());
String string = scanner.nextLine();
if("end".equals(string)){
System.out.println("Client end"+" ,"+new Date().toString());
break;
}
outputStream.write(("This is a new request: "+string).getBytes());
int length = 0;
if ((length = inputStream.read(buffer)) > 0) {//阻塞
}
System.out.println("The response is:" + new String(buffer, 0, length)+" ,"+new Date().toString());
} }catch (Exception e){ } finally {
if (clientSocket != null) {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
} }

  启动main方法,如下截图(客户端和服务端):

          

          

  客户端可以一直接受请求,直到输入end结束此次客户端输入,那么服务端能一直接受请求,然后给出答复,当客户端断开请求后,服务端重新恢复监听,等待新的socket请求的到来。

  因此,在BIO通信模型中,Socket通信的发起以及结束都是客户端进行的。

五、多Socket客户端请求处理

  上面进行的是1对1的Socket通信建立,如果已经有一个Socket请求在处理的时候,此时再来一个socket请求,那么会怎样呢,我们通过两个控制台简历请求进行测试。

        

  如截图所示,客户端1和客户端建立Socket请求的通信并未报错,客户端①可以正常通信,但是客户端②的请求并未得到响应。

  我们因此可以知道BIO是一对一应答模型,一个Socekt只能支持一个通信,如果一个客户端没有断开,当前线程会阻塞在读操作。要想实现同时处理多个通信,那么就必须建立多个Socket,因此此时服务端可以通过多线程,在socket请求到来时,单独为通信建立一个Socket进行通信,代码如下:

public class BioServerThread {
public static void main(String[] args) {
int port=8080;
ServerSocket serverSocket=null;
try{ serverSocket=new ServerSocket(port);
Socket socket=null;
while (true){
socket=serverSocket.accept();//拿到socket
//连接量大的时候 会拖垮cpu
new Thread(new SocketHandler(socket)).start();
}
}catch (Exception e){
e.printStackTrace();
}finally {
if (serverSocket!=null){
try {
serverSocket.close();
serverSocket=null;
}catch (IOException e){
e.printStackTrace();
}
}
}
}
}
public class SocketHandler implements Runnable {
private Socket socket; public SocketHandler(Socket socket) {
this.socket=socket;
} public void run() {
InputStream in = null;
OutputStream out = null;
try {
in = socket.getInputStream();
byte[] buffer = new byte[1024];
int length = 0;
while ((length = in.read(buffer)) > 0) {//阻塞
System.out.println("input is:" + new String(buffer, 0, length));
out = socket.getOutputStream();
out.write("success".getBytes());
System.out.println("end");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
}catch (Exception e){
e.printStackTrace();
}
}
if (socket!=null){
try {
socket.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}

  此时用两个控制台建立两个socket请求进行测试:

          

  如图所示,客户端①和客户端②同时得到了服务端的返回,服务端成功完成了多请求的处理。

六、线程池管理Socket

  虽然为了解决该问题可以引入多线程,实现伪异步IO,但是因为处理线程和客户端是1:1的关系,随着客户端请求增大,线程数随着上升,会极大的消耗cpu资源,引起服务器异常。为了保障服务器资源可以实现线程池,如果发生读取数据较慢时,大量并发的情况下,其他接入的客户都只能一直等待。

  代码如下:

public class ServerHandlerExcutePool {
private ExecutorService executor; public ServerHandlerExcutePool(int maxPoolSize, int queueSize){
executor=new ThreadPoolExecutor(2, maxPoolSize, 120L, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(queueSize));
} public void execute(Runnable task){
executor.execute(task);
}
}
public class BioServerThreadPool {
public static void main(String[] args) {
int port=8080;
ServerSocket serverSocket=null;
try{
//
serverSocket=new ServerSocket(port);
Socket socket=null;
//100000 100000
ServerHandlerExcutePool excutePool=new ServerHandlerExcutePool(2,100);
while (true){
socket=serverSocket.accept();
excutePool.execute(new SocketHandler(socket));
}
}catch (Exception e){
e.printStackTrace();
}finally {
if (serverSocket!=null){
try {
serverSocket.close();
serverSocket=null;
}catch (IOException e){
e.printStackTrace();
}
}
}
}
}

  如截图所示,我设置线程池最多处理两个请求,那么此时第三个客户端的连接依旧无法处理,这样避免了创建大量的线程创建新的Socket,多余线程池大容量的请求只能阻塞。

        

  综上所述,BIO的特点是缺乏弹性伸缩能力,在高并发这种大量请求来临时,并不具备强大的处理能力。

IO模型之BIO代码详解及其优化演进的更多相关文章

  1. RAID5 IO处理之replace代码详解

    1 作用 从字面意思理解,replacement即是替换.我们知道硬盘都有一定的使用寿命,可以在硬盘失效之前通过该功能将就盘的数据迁移至新盘.因为replacement的流程是从旧盘中读出数据直接写入 ...

  2. RAID5 IO处理之重构代码详解

    1 作用 当阵列降级时,可以添加一块新盘进行重构,以恢复阵列的冗余. 2 发起重构 可以通过以下命令md并发起重构: mdadm -C /dev/md0 --force --run -l 5 -n 3 ...

  3. IO模型(epoll)--详解-01

    写在前面 从事服务端开发,少不了要接触网络编程.epoll作为linux下高性能网络服务器的必备技术至关重要,nginx.redis.skynet和大部分游戏服务器都使用到这一多路复用技术. 本文会从 ...

  4. IO模型(epoll)--详解-02

    写在前面 从事服务端开发,少不了要接触网络编程.epoll作为linux下高性能网络服务器的必备技术至关重要,大部分游戏服务器都使用到这一多路复用技术.文章核心思想是:要让读者清晰明白EPOLL为什么 ...

  5. IO模型(epoll)--详解-03

    写在前面 epoll是开发linux高性能服务器的必备技术至,epoll本质,是服务端程序员的必须掌握的知识. 七.epoll的原理和流程 本节会以示例和图表来讲解epoll的原理和流程. 创建epo ...

  6. Kaggle网站流量预测任务第一名解决方案:从模型到代码详解时序预测

    Kaggle网站流量预测任务第一名解决方案:从模型到代码详解时序预测 2017年12月13日 17:39:11 机器之心V 阅读数:5931   近日,Artur Suilin 等人发布了 Kaggl ...

  7. Java IO模型:BIO、NIO、AIO

    Java IO模型:BIO.NIO.AIO 本来是打算直接学习网络框架Netty的,但是先补充了一下自己对Java 几种IO模型的学习和理解.分别是 BIO.NIO.AIO三种IO模型. IO模型的基 ...

  8. ASP.NET MVC 5 学习教程:生成的代码详解

    原文 ASP.NET MVC 5 学习教程:生成的代码详解 起飞网 ASP.NET MVC 5 学习教程目录: 添加控制器 添加视图 修改视图和布局页 控制器传递数据给视图 添加模型 创建连接字符串 ...

  9. Github-karpathy/char-rnn代码详解

    Github-karpathy/char-rnn代码详解 zoerywzhou@gmail.com http://www.cnblogs.com/swje/ 作者:Zhouwan  2016-1-10 ...

随机推荐

  1. linux下nacos的1.1.3版本集群部署

    windows单机版参见0.9.0.RELEASE版本的spring cloud alibaba nacos实例 ,linux集群版我们还是用上次下载的nacos压缩包,通过rz -y上传到wlf用户 ...

  2. Qt widget中使用QML自定义电池

    1.效果 2.QML 在资源里新建Mybattery.qml: import QtQuick 2.0 import QtQuick 2.12 Item { id: root property colo ...

  3. **209. Minimum Size Subarray Sum 长度最小的子数组

    1. 题目描述 给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组.如果不存在符合条件的连续子数组,返回 0. 示例: 输入: s = 7, nu ...

  4. 编译Flink 1.9.0

    闲来无事,编个Flink 1.9 玩玩 1.下载flink.flink-shaded 源码.解压 flink flink-shaded 7.0 [venn@venn release]$ ll tota ...

  5. redis八大应用场景

    1.缓存 缓存现在几乎是所有中大型网站都在用的必杀技,合理的利用缓存不仅能够提升网站访问速度,还能大大降低数据库的压力.Redis提供了键过期功能,也提供了灵活的键淘汰策略,所以,现在Redis用在缓 ...

  6. 【linux学习笔记一】目录处理命令

    一 建立目录:mkdir make directories //创建一个name的目录 mkdir name //-p 递归创建 //在没有目录a也没有目录b的情况下 直接创建 mkdir -p a/ ...

  7. Input.GetMouseButtonDown 在fixedupdate中会出现丢失问题,在update中则完全没这个问题

    Input.GetMouseButtonDown 在fixedupdate中会出现丢失问题,在update中则完全没这个问题

  8. SPSS 2019年10月24日 今日学习总结

    2019年10月24日今日课上内容1.SPSS掌握基于键值的一对多合并2.掌握重构数据3.掌握汇总功能 内容: 1.基于键值的一对多合并 合并文件 添加变量 合并方法:基于键值的一对多合并 变量 2. ...

  9. Raspberry Pi 3B I2C 问题

    刚刚接触树莓派,的确很适合用来学习,资料多而且很规范,开发者多,平台统一,很多问题别人都已经解决.

  10. 反馈神经网络Hopfield网络

    一.前言 经过一段时间的积累,对于神经网络,已经基本掌握了感知器.BP算法及其改进.AdaLine等最为简单和基础的前馈型神经网络知识,下面开启的是基于反馈型的神经网络Hopfiled神经网络.前馈型 ...