十二、IO进化过程之BIO

18 篇文章 1 订阅

概述

前面几篇文章我们对Java的IO体系做了一个大致的介绍,从本文开始我们将对BIO、NIO、SELECTOR、EPOLL、Netty等携带例子做更深入的讲解。
如需持续了解请关注后随时查看。

解读

阻塞IO模型:最传统的一种IO模型,即在读写数据过程中会发生阻塞现象。当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才接触block状态。典型的阻塞IO模型的例子为data=socket.read();如果数据没有就绪,就会一直阻塞在read方法。

全图(下面有分解图)

在这里插入图片描述

图解

在这里插入图片描述

释义

在这里插入图片描述
1.不管是java程序,还是c语言程序,如果想作为服务端提供socket连接,那么他必须经过以下几个步骤:
①调用内核的socket接口,得到文件描述符–fd5 ;
②调用内核的bind接口,绑定8090端口;
③调用内核的listen接口,监听fd5的状态;
④调用内核的accept接口,等待客户端调用fd5,Accept进入阻塞状态,直到有客户端进入才可以中断阻塞状态。
⑤当有客户端进入后,系统为客户端生成一个文件描述符fd6,然后接入到socket服务端,accept结束阻塞状态。fd5=fd6开始进行通信。

2.以上的描述都是在只有一个客户端的情况下进行的,但是在实际应用过程中,会有很多个客户端进入,如果只有一个线程进行处理,那么其他的客户端只有全部阻塞。因此需要在accept获取到客户端后新建一个线程(clone系统调用)去处理客户端发送的数据。read(读取)或者recvfrom(接收)等系统调用都需要对线程进行阻塞。
3.以上过程中为了防止主线程因为
read(读取)或者recvfrom(接收)数据时阻塞,导致其他客户端无法连接,因此在主线程使用accept接收到一个客户端后便新建一个线程去处理客户端的数据。新建的线程也可能因为read(读取)或者recvfrom(接收)数据也进入阻塞状态
。这种处理过程中每个线程对应一个客户端client。
4.以上BIO模型的问题->
①.如果有一万个客户端连接服务端,那么服务端就需要创建一万个线程去处理请求。创建线程需要系统调用是很消耗资源的。消耗内存资源,而且cpu调度过程中线程的上下文切换也需要时间。以上问题的根本原因就是当客户端连接进入服务端后,服务端是阻塞状态的,干不了其他事情。
②.如果要解决以上问题则需要解决socket的阻塞问题,这样就形成了IO进化过程之NIO。

下一篇文章我们将对NIO做详细的介绍

以下我们将通过两个示例对JavaBIO体系做一个简单介绍。

示例代码一为单线程的服务端;

实例代码二为多线程处理数据的服务端;

示例代码一-单线程服务端:

import org.apache.commons.lang.StringUtils;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
 * Created by Bruce on 2020/9/16
 * 网络IO之BIO-服务端
 * BIO-写法
 **/
public class SocketServerBIO {
    public static void main(String[] args) throws IOException {
        /**
         * 创建serverSocket 并绑定 8080端口
         * 在系统内核中要经过以下几步来完成Java语言的
         “new ServerSocket(8080)”。
         * 1. socket()  = fd6 获取文件描述符;
         * 2. bind(fd6,8080)    文件描述符与端口绑定;
         * 3. listen(fd6)  监听文件描述符
         */
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("step1 : new ServerSocket(8080) ");
        /**
         * 接受socket客户端请求连接
         * accept命令是一个阻塞命令,
         只有当有客户端连接进入时候,
         才会结束阻塞状态。
         * 当有客户端连接进入时候 accept(fd6) ==>fd7 
         与客户端创建的随机端口号和随机文件描述符连接
         */
        Socket socket = serverSocket.accept();//阻塞状态1
        System.out.println("acceptSocketClient:" + socket.getPort());
        /**
         * 获取阻塞连接输出 输入流
         */
        InputStream inputStream = socket.getInputStream();
        /**
         * 创建出入缓冲区
         */
        BufferedReader bufferedReader = 
          new BufferedReader(new InputStreamReader(inputStream));
        String text = null;
        /**
         * read命令也是一个阻塞命令,等待客户端数据传入。
         * 客户端输入传入后结束阻塞状态。
         *          * read(fd7)   读取客户端写入到文件描述符7中的文件。
         */
        while (StringUtils.isNotEmpty((text = bufferedReader.readLine() ))){//阻塞状态2
            System.out.println("acceptSocketClient:" + socket.getPort() + "---" + text);
        }
        while (true){
            //暂时死循环
        }
    }
}

示例代码一客户端(仅示例,具体测试时使用nc命令链接)

import org.apache.commons.lang.StringUtils;
import java.io.*;
import java.net.Socket;
/**
 * Created by Bruce on 2020/9/16
 *  * 网络IO之BIO-客户端
 *  * BIO-写法
 **/
public class SocketClientBIO {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",8080);
        OutputStream outputStream = socket.getOutputStream();        PrintWriter printWriter = new PrintWriter(outputStream);
        printWriter.write("server端口你好,我是client");
        printWriter.flush();        //关闭资源
        printWriter.close();
        outputStream.close();
//        InputStream inputStream = socket.getInputStream();
//        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
//        String text = null;
//        while (StringUtils.isNotEmpty(text = bufferedReader.readLine())){
//            System.out.println(text);
//        }
        socket.close();
    }
}

示例代码一-单线程打印测试

在linux环境或者windows环境下使用nc命令链接服务端,查看服务端打印过程。

具体linux系统或者windows系统如何安装nc命令,请从网络搜索或查看目录文档 ‘网络IO涉及到的-linux指令.docx’。
五、Centos-Linux安装nc
六、windows环境下netcat的安装及使用

1. nc客户端打印

Windows-nc命令打印

C:\Users\Administrator>nc 127.0.0.1 8080
123456789aaa
sss
ddd
fff

2. 服务端打印

step1 : new ServerSocket(8080)
acceptSocketClient:45021
acceptSocketClient:45021---123
acceptSocketClient:45021---456
acceptSocketClient:45021---789
acceptSocketClient:45021---aaa
acceptSocketClient:45021---sss
acceptSocketClient:45021---ddd
acceptSocketClient:45021---fff

示例代码二-多线程服务端

import org.apache.commons.lang.StringUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
 * Created by Bruce on 2020/9/16
 * 网络IO之BIO-服务端
 *  * BIO-写法
 *
 *  多线程
 **/
public class SocketServerThreadBIO {
    public static void main(String[] args) throws IOException {
        {            /**
             * 创建serverSocket 并绑定 8080端口
             * 在系统内核中要经过以下几步来完成Java语言的
             “new ServerSocket(8080)”。
             * 1. socket()  = fd6 获取文件描述符;
             * 2. bind(fd6,8080)    文件描述符与端口绑定;
             * 3. listen(fd6)  监听文件描述符
             */
            ServerSocket serverSocket = new ServerSocket(8080);
            System.out.println("step1 : new ServerSocket(8080) ");
            while (true){
                /**
                 * 接受socket客户端请求连接
                 *
                 * accept命令是一个阻塞命令,只有当有客户端连接进入时候,
                 才会结束阻塞状态。
                 * 当有客户端连接进入时候 accept(fd6) ==>fd7  
                 与客户端创建的随机端口号和随机文件描述符连接
                 */
                Socket socket = serverSocket.accept();//阻塞状态1
                System.out.println("acceptSocketClient:" + socket.getPort());
                /**
                 * 每次进入一个客户端则创建一个线程去处理数据
                 * 因为线程的创建是非常消耗资源的
                 * 而且线程创建数量也是有限的,在C10K问题下,处理更多的TCP连接请求。
                 */
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        /**
                         * 获取阻塞连接输出 输入流
                         */
                        InputStream inputStream = null;
                        try {
                            inputStream = socket.getInputStream();
                            /**
                             * 创建出入缓冲区
                             */
                            BufferedReader bufferedReader = 
                              new BufferedReader(new InputStreamReader(inputStream));
                            String text = null;
                            /**
                             * read命令也是一个阻塞命令,等待客户端数据传入。
                             * 客户端输入传入后结束阻塞状态。
                             *          * read(fd7)   读取客户端写入到文件描述符7中的文件。
                             */
                            while (StringUtils.isNotEmpty((text = bufferedReader.readLine() ))){//阻塞状态2
                                System.out.println("acceptSocketClient:" + socket.getPort() + "---" + text);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        }
    }
}

示例代码二-多线程服务端打印测试:

在linux环境或者windows环境下使用nc命令链接服务端,查看服务端打印过程。

具体linux系统或者windows系统如何安装nc命令,请从网络搜索或查看目录文档 ‘网络IO涉及到的-linux指令.docx’。
五、Centos-Linux安装nc
六、windows环境下netcat的安装及使用

1. nc客户端打印1(Windows-nc命令打印)

C:\Users\Administrator>nc 127.0.0.1 8080
456
123456789

2. nc客户端打印2(Windows-nc命令打印)

C:\Users\Administrator>nc 127.0.0.1 8080
123
789
77777777

3. 服务端打印

step1 : new ServerSocket(8080)
acceptSocketClient:45614
acceptSocketClient:45619
acceptSocketClient:45619---123
acceptSocketClient:45614---456
acceptSocketClient:45619---789
acceptSocketClient:45614---123456789
acceptSocketClient:45619---77777777

往期JavaIO文章:

一、C10K问题经典问答
二、java.nio.ByteBuffer用法小结
三、Channel 通道
四、Selector选择器
五、Centos-Linux安装nc
六、windows环境下netcat的安装及使用
七、IDEA的maven项目的netty包的导入(其他jar同)
八、JAVA IO/NIO
九、网络IO原理-创建ServerSocket的过程
十、网络IO原理-彻底弄懂IO
十一、JAVA中ServerSocket调用Linux系统内核

整体JavaIO体系文章概览

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值