文档章节

I/O之BIO详解

Clarence_D
 Clarence_D
发布于 2017/05/17 14:56
字数 2178
阅读 46
收藏 0
点赞 0
评论 0

BIO编程

1.1、传统的BIO编程

    网络编程的基本模型是C/S模型,即两个进程间的通信。

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

    传统的同步阻塞模型开发中,ServerSocket负责绑定IP地址,启动监听端口;Socket负责发起连接操作。连接成功后,双方通过输入和输出流进行同步阻塞式通信。 

    简单的描述一下BIO的服务端通信模型:采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理没处理完成后,通过输出流返回应答给客户端,线程销毁。即典型的一请求一应答通宵模型。

    传统BIO通信模型图:

    01

    该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1:1的正比关系,Java中的线程也是比较宝贵的系统资源,线程数量快速膨胀后,系统的性能将急剧下降,随着访问量的继续增大,系统最终就死-掉-了

    同步阻塞式I/O创建的Server源码:

package com.example.BIO;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
 * BIO服务端源码
 * @author yangtao__anxpp.com
 * @version 1.0
 */
public final class ServerNormal {
    //默认的端口号
    private static int DEFAULT_PORT = 12345;
    //单例的ServerSocket
    private static ServerSocket server;
    //根据传入参数设置监听端口,如果没有参数调用以下方法并使用默认值
    public static void start() throws IOException{
        //使用默认值
        start(DEFAULT_PORT);
    }
    //这个方法不会被大量并发访问,不太需要考虑效率,直接进行方法同步就行了
    public synchronized static void start(int port) throws IOException{
        if(server != null) return;
        try{
            //通过构造函数创建ServerSocket
            //如果端口合法且空闲,服务端就监听成功
            server = new ServerSocket(port);
            System.out.println("服务器已启动,端口号:" + port);
            Socket socket;
            //通过无线循环监听客户端连接
            //如果没有客户端接入,将阻塞在accept操作上。
            while(true){
                socket = server.accept();
                //当有新的客户端接入时,会执行下面的代码
                //然后创建一个新的线程处理这条Socket链路
                new Thread(new ServerHandler(socket)).start();
            }
        }finally{
            //一些必要的清理工作
            if(server != null){
                System.out.println("服务器已关闭。");
                server.close();
                server = null;
            }
        }
    }
}

 客户端消息处理线程ServerHandler源码:

package com.example.BIO;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

/**
 * 客户端线程
 * @author yangtao__anxpp.com
 * 用于处理一个客户端的Socket链路
 */
public class ServerHandler implements Runnable{
    private Socket socket;
    public ServerHandler(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        BufferedReader in = null;
        PrintWriter out = null;
        try{
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(),true);
            String expression;
            String result;
            while(true){
                //通过BufferedReader读取一行
                //如果已经读到输入流尾部,返回null,退出循环
                //如果得到非空值,就尝试计算结果并返回
                if((expression = in.readLine())==null) break;
                System.out.println("服务器收到消息:" + expression);
                try{
                    result = Calculator.cal(expression).toString();
                }catch(Exception e){
                    result = "计算错误:" + e.getMessage();
                }
                out.println(result);
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            //一些必要的清理工作
            if(in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                in = null;
            }
            if(out != null){
                out.close();
                out = null;
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                socket = null;
            }
        }
    }
}

同步阻塞式I/O创建的Client源码:

package com.example.BIO;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/**
 * 阻塞式I/O创建的客户端
 * @author yangtao__anxpp.com
 * @version 1.0
 */
public class Client {
    //默认的端口号
    private static int DEFAULT_SERVER_PORT = 12345;
    private static String DEFAULT_SERVER_IP = "127.0.0.1";
    public static void send(String expression){
        send(DEFAULT_SERVER_PORT,expression);
    }
    public static void send(int port,String expression){
        System.out.println("算术表达式为:" + expression);
        Socket socket = null;
        BufferedReader in = null;
        PrintWriter out = null;
        try{
            socket = new Socket(DEFAULT_SERVER_IP,port);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(),true);
            out.println(expression);
            System.out.println("___结果为:" + in.readLine());
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            //一下必要的清理工作
            if(in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                in = null;
            }
            if(out != null){
                out.close();
                out = null;
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                socket = null;
            }
        }
    }
}

测试代码,为了方便在控制台看输出结果,放到同一个程序(jvm)中运行:

package com.example.BIO;
import java.io.IOException;
import java.util.Random;
/**
 * 测试方法
 * @author yangtao__anxpp.com
 * @version 1.0
 */
public class Test {
    //测试主方法
    public static void main(String[] args) throws InterruptedException {
        //运行服务器
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    ServerBetter.start();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        //避免客户端先于服务器启动前执行代码
        Thread.sleep(100);
        //运行客户端
        final char operators[] = {'+','-','*','/'};
        final Random random = new Random(System.currentTimeMillis());
        new Thread(new Runnable() {
            @SuppressWarnings("static-access")
            @Override
            public void run() {
                while(true){
                    //随机产生算术表达式
                    String expression = random.nextInt(10)+""+operators[random.nextInt(4)]+(random.nextInt(10)+1);
                    Client.send(expression);
                    try {
                        Thread.currentThread().sleep(random.nextInt(1000));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

控制台输出其中一次运行结果如下

服务器已启动,端口号:12345
算术表达式为:1/7
服务器收到消息:1/7
___结果为:0.14285714285714285
算术表达式为:8*10
服务器收到消息:8*10
___结果为:80
算术表达式为:4/10
服务器收到消息:4/10
___结果为:0.4

 从以上代码,很容易看出,BIO主要的问题在于每当有一个新的客户端请求接入时,服务端必须创建一个新的线程来处理这条链路,在需要满足高性能、高并发的场景是没法应用的(大量创建新的线程会严重影响服务器性能,甚至罢工)。

1.2、伪异步I/O编程

    为了改进这种一连接一线程的模型,我们可以使用线程池来管理这些线程(需要了解更多请参考前面提供的文章),实现1个或多个线程处理N个客户端的模型(但是底层还是使用的同步阻塞I/O),通常被称为“伪异步I/O模型“。

    伪异步I/O模型图:

    02

    实现很简单,我们只需要将新建线程的地方,交给线程池管理即可,只需要改动刚刚的Server代码即可:

package com.example.BIO;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * BIO服务端源码__伪异步I/O
 * @author yangtao__anxpp.com
 * @version 1.0
 */
public final class ServerBetter {
    //默认的端口号
    private static int DEFAULT_PORT = 12345;
    //单例的ServerSocket
    private static ServerSocket server;
    //线程池 懒汉式的单例
    private static ExecutorService executorService = Executors.newFixedThreadPool(60);
    //根据传入参数设置监听端口,如果没有参数调用以下方法并使用默认值
    public static void start() throws IOException{
        //使用默认值
        start(DEFAULT_PORT);
    }
    //这个方法不会被大量并发访问,不太需要考虑效率,直接进行方法同步就行了
    public synchronized static void start(int port) throws IOException{
        if(server != null) return;
        try{
            //通过构造函数创建ServerSocket
            //如果端口合法且空闲,服务端就监听成功
            server = new ServerSocket(port);
            System.out.println("服务器已启动,端口号:" + port);
            Socket socket;
            //通过无线循环监听客户端连接
            //如果没有客户端接入,将阻塞在accept操作上。
            while(true){
                socket = server.accept();
                //当有新的客户端接入时,会执行下面的代码
                //然后创建一个新的线程处理这条Socket链路
                executorService.execute(new ServerHandler(socket));
            }
        }finally{
            //一些必要的清理工作
            if(server != null){
                System.out.println("服务器已关闭。");
                server.close();
                server = null;
            }
        }
    }
}

测试运行结果是一样的。

    我们知道,如果使用CachedThreadPool线程池(不限制线程数量,如果不清楚请参考文首提供的文章),其实除了能自动帮我们管理线程(复用),看起来也就像是1:1的客户端:线程数模型,而使用FixedThreadPool我们就有效的控制了线程的最大数量,保证了系统有限的资源的控制,实现了N:M的伪异步I/O模型。

    但是,正因为限制了线程数量,如果发生大量并发请求,超过最大数量的线程就只能等待,直到线程池中的有空闲的线程可以被复用。而对Socket的输入流就行读取时,会一直阻塞,直到发生:

  • 有数据可读
  • 可用数据以及读取完毕
  • 发生空指针或I/O异常

    所以在读取数据较慢时(比如数据量大、网络传输慢等),大量并发的情况下,其他接入的消息,只能一直等待,这就是最大的弊端。

    而后面即将介绍的NIO,就能解决这个难题。

附录

    上文中服务端使用到的用于计算的工具类:

package com.example.BIO;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public final class Calculator {
    private final static ScriptEngine jse = new ScriptEngineManager().getEngineByName("JavaScript");
    public static Object cal(String expression) throws ScriptException {
        return jse.eval(expression);
    }
}

 

本文转载自:http://blog.csdn.net/anxpp/article/details/51512200

共有 人打赏支持
Clarence_D
粉丝 8
博文 112
码字总数 98515
作品 0
天津
程序员
Linux块设备开发详解及代码(版本3.10.0)

  不同于字设备,高效的块驱动对于性能至关重要,它是核心内存和二级存储之间的管道,所以块层的设计必定围绕性能。   找源码的,请直接往下翻(在3.10.0版本可编译使用)。 1. 注册编号 ...

binarydady ⋅ 05/03 ⋅ 0

java I/O 模型简述

同步与异步&阻塞与非阻塞 五大I/O模型详解 java I/O模型简述 概述 从同步与异步&阻塞与非阻塞的概念,到具体的I/O模型,再到具体的Java语言实现,都是层层递进,本篇就从Java语言来看I/O模型...

haoran_10 ⋅ 2016/07/14 ⋅ 5

仲肥:何为Redis 4.0 ?

摘要:在2018数据库直播大讲堂峰会-Redis专场上,阿里云的仲肥对Redis4.0介绍。仲肥从何为Redis4.0着手,通过与以前Redis版本进行对比,全面详解了4.0无论是在计算速度还是进行删除操作时的计...

阿里云_云栖社区 ⋅ 01/29 ⋅ 0

详解:tomcat的连接数与线程池

转自:http://www.cnblogs.com/kismetv/p/7806063.html前言在使用tomcat时,经常会遇到连接数、线程数之类的配置问题,要真正理解这些概念,必须先了解Tomcat的连接器(Connector)。 在前面...

goldfishe ⋅ 2017/12/22 ⋅ 0

详解 Tomcat 的连接数与线程池

前言 在使用tomcat时,经常会遇到连接数、线程数之类的配置问题,要真正理解这些概念,必须先了解Tomcat的连接器(Connector)。 在前面的文章 详解Tomcat配置文件server.xml 中写到过:Con...

t4i2b10X4c22nF6A ⋅ 2017/11/15 ⋅ 0

mpage机制中的BH_Boundary详解以及bio思想

这个特性的存在要归结于文件系统在磁盘上的布局,文件需要元数据以便更高效的被系统 所管理,元数据分为两类,一类为抽象给用户的元数据,比如所有者,访问控制列表,修改日期等等,另一类是...

晨曦之光 ⋅ 2012/04/10 ⋅ 0

gff文件解析

filtergff3bytranscriptid.pl 获取 gff 文件第一列数据:,更多请参考。

_lhtk_ ⋅ 2016/02/26 ⋅ 0

浅谈“阻塞同步”,“BIO、NIO、AIO”

一、阻塞?同步? 可能大家平常会经常听到这两个名词,但是没花太多心思详细了解,今天就来揭开这层面纱。 一次IO操作,以read方法举例,会经历两个阶段: (1)等待数据准备(Waitingfor the...

叫我宫城大人 ⋅ 2017/09/04 ⋅ 0

开启Tomcat APR运行模式,优化并发性能

Tomcat支持三种接收请求的处理方式:BIO、NIO、APR 1>、BIO模式:阻塞式I/O操作,表示Tomcat使用的是传统Java I/O操作(即java.io包及其子包)。Tomcat7以下版本默认情况下是以bio模式运行的,...

woshiluo ⋅ 2017/10/11 ⋅ 0

Linux I/O子系统框架图

I/O write I/O read: read系统调用的处理分为用户空间和内核空间处理两部分。其中,用户空间处理只是通过0x80中断陷入内核,接着调用其中断服务例程,即sysread以进入内核处理流程。 对于rea...

满小茂 ⋅ 2015/12/31 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Sqoop

1.Sqoop: 《=》 SQL to Hadoop 背景 1)场景:数据在RDBMS中,我们如何使用Hive或者Hadoop来进行数据分析呢? 1) RDBMS ==> Hadoop(广义) 2) Hadoop ==> RDBMS 2)原来可以通过MapReduce I...

GordonNemo ⋅ 11分钟前 ⋅ 0

全量构建和增量构建的区别

1.全量构建每次更新时都需要更新整个数据集,增量构建只对需要更新的时间范围进行更新,所以计算量会较小。 2.全量构建查询时不需要合并不同Segment,增量构建查询时需要合并不同Segment的结...

无精疯 ⋅ 21分钟前 ⋅ 0

如何将S/4HANA系统存储的图片文件用Java程序保存到本地

我在S/4HANA的事务码MM02里为Material维护图片文件作为附件: 通过如下简单的ABAP代码即可将图片文件的二进制内容读取出来: REPORT zgos_api.DATA ls_appl_object TYPE gos_s_obj.DA...

JerryWang_SAP ⋅ 39分钟前 ⋅ 0

云计算的选择悖论如何对待?

导读 人们都希望在工作和生活中有所选择。但心理学家的调查研究表明,在多种选项中进行选择并不一定会使人们更快乐,甚至不会产生更好的决策。心理学家Barry Schwartz称之为“选择悖论”。云...

问题终结者 ⋅ 47分钟前 ⋅ 0

637. Average of Levels in Binary Tree - LeetCode

Question 637. Average of Levels in Binary Tree Solution 思路:定义一个map,层数作为key,value保存每层的元素个数和所有元素的和,遍历这个树,把map里面填值,遍历结束后,再遍历这个map,把每...

yysue ⋅ 今天 ⋅ 0

IDEA配置和使用

版本控制 svn IDEA版本控制工具不能使用 VCS-->Enable Version Control Integration File-->Settings-->Plugins 搜索Subversion,勾选SVN和Git插件 删除.idea文件夹重新生成项目 安装SVN客户......

bithup ⋅ 今天 ⋅ 0

PE格式第三讲扩展,VA,RVA,FA的概念

作者:IBinary 出处:http://www.cnblogs.com/iBinary/ 版权所有,欢迎保留原文链接进行转载:) 一丶VA概念 VA (virtual Address) 虚拟地址的意思 ,比如随便打开一个PE,找下它的虚拟地址 这边...

simpower ⋅ 今天 ⋅ 0

180623-SpringBoot之logback配置文件

SpringBoot配置logback 项目的日志配置属于比较常见的case了,之前接触和使用的都是Spring结合xml的方式,引入几个依赖,然后写个 logback.xml 配置文件即可,那么在SpringBoot中可以怎么做?...

小灰灰Blog ⋅ 今天 ⋅ 0

冒泡排序

原理:比较两个相邻的元素,将值大的元素交换至右端。 思路:依次比较相邻的两个数,将小数放在前面,大数放在后面。即在第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第...

人觉非常君 ⋅ 今天 ⋅ 0

Vagrant setup

安装软件 brew cask install virtualboxbrew cask install vagrant 创建project mkdir -p mst/vmcd mst/vmvagrant init hashicorp/precise64vagrant up hashicorp/precise64是一个box......

遥借东风 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部