Netty,认识和深入(二),JAVA原生BIO认识

BIO

JAVA BIOblocking I/O):同步并阻塞(传统阻塞型),服务器实现模型为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制完善(实现多个客户连接服务器)

应用场景


BIO 方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序简单易理解。

BIO工作原理大致流程图如下:
Netty,认识和深入(二),JAVA原生BIO认识_第1张图片

  1. 服务器启动一个ServreSocket
  2. 客户端启动Socket对服务器进行通信,默认情况下服务器端需要对每个客户建立一个线程与之通讯;
  3. 客户端发出请求后,先咨询服务器是否有线程响应,如果没有则会等待,或者被拒绝;
  4. 如果有响应,客户端线程会等待请求结束后,在继续执行;

JAVA代码示例:

package com.kelecc.bio;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;


/**
 * 功能描述: BIO Demo
 *
 * @Author keLe
 * @Date 2022/1/24
 */
public class BIOServer {

    public static void main(String[] args) {
        //创建一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();

        try {
            //创建ServerSocket ,监听8888端口
            ServerSocket serverSocket = new ServerSocket(8888);
            System.out.println("服务启动");
            while(true){
                //监听,等待客户端链接,该方法阻塞,直到建立连接。
                System.out.println("等待连接");
                //main方法的主线程
                System.out.println("线程信息 id = " + Thread.currentThread().getId());
                System.out.println("线程名字 name="+Thread.currentThread().getName());
                final Socket socket = serverSocket.accept();
                System.out.println("连接到客户端");
                //创建一个线程,与之通讯
                 executorService.execute(()->{
                     handler(socket);
                 });
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static void handler(Socket socket){
       byte[] bytes  = new byte[1024];
        try {
            //得到输入流
            InputStream inputStream = socket.getInputStream();
            //循环读取客户端发送的数据
            while(true){
                System.out.println("线程信息 id = " + Thread.currentThread().getId());
                System.out.println("线程名字 name="+Thread.currentThread().getName());
                System.out.println("read.......");
                int read = inputStream.read(bytes);
                if(read != -1){
                    //控制台打印输出
                    System.out.println("输出客户端发送的数据"+new String(bytes,0,read));
                }else{
                    //读取完毕
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            System.out.println("关闭客户端连接");
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

  • 代码运行:

Netty,认识和深入(二),JAVA原生BIO认识_第2张图片

只要没有客户端连接,线程都会阻塞,socket.accept 调用之前:

final Socket socket = serverSocket.accept();
  • 使用CMD 创建一个客户端连接:使用命令测试8888端口
telnet 127.0.0.1 8888

Netty,认识和深入(二),JAVA原生BIO认识_第3张图片

  • 连接成功:

Netty,认识和深入(二),JAVA原生BIO认识_第4张图片

  • 控制台打印:

Netty,认识和深入(二),JAVA原生BIO认识_第5张图片

  • 观察打印输出,我们发现:

    主线程main , 在有客户端连接过后,又会阻塞在 客户端连接进来之前,所以这里会打印 两个等待连接;我们也可以看上面的流程图 一目了然;

serverSocket.accept();

而我们客户端连接的子线程 通过我们设计代码连接成功后,当我们没有发送数据,子线程会阻塞在 read/write这个操作时;可以看上面的流程图;

int read = inputStream.read(bytes); 
  • 发送数据,在当前界面,按住 ctrl+] 键;

Netty,认识和深入(二),JAVA原生BIO认识_第6张图片
Netty,认识和深入(二),JAVA原生BIO认识_第7张图片

  • 通过 send 发送数据;

Netty,认识和深入(二),JAVA原生BIO认识_第8张图片

  • 观测输出日志;

Netty,认识和深入(二),JAVA原生BIO认识_第9张图片

发送数据成功之后,子线程依旧阻塞在 read 操作之前

接下来,我们新建第二个客户端,启动第二个cmd,观察输出日志;

Netty,认识和深入(二),JAVA原生BIO认识_第10张图片

总结:

  • 每个请求都需要创建独立的线程,与对应的客户端进行数据Read,业务处理,数据Write
  • 当并发数较大时,需要创建大量线程来处理连接,系统资源占用较大;
  • 连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞在Read操作上,造成线程资源浪费;

你可能感兴趣的:(网络编程,服务器,java,运维,netty,bio)