首页 > 其他分享 >千峰教育--Netty 再学习 1 网络模型概述(BIO、NIO、AIO)、BIO 逻辑实现及其局限性(单线程服务端、多线程服务端、线程池服务端)

千峰教育--Netty 再学习 1 网络模型概述(BIO、NIO、AIO)、BIO 逻辑实现及其局限性(单线程服务端、多线程服务端、线程池服务端)

时间:2024-12-22 18:08:25浏览次数:7  
标签:BIO java 线程 import 多线程 连接 服务端 客户端

课程介绍

1 网络模型概述

2 Channel 详解

3 Buffer 详解

4 Selector 详解

5 NIO综合案例-聊天室

6 AIO概念及实现

 

1 网络编程IO 模型介绍

1.1 BLockingIO

Blocking IO也称BIO,及同步阻塞IO。Java 的 io 包基于流模型实现,提供了FIle,FileInputStream,FileOutputStream等输入输出流的功能。Java io 包下提供的基于流操作,交互方式是同步且阻塞的方式,在输入输出流进行读写操作之前,线程一直阻塞。因此io 包中对流的操作容易造成性能的瓶颈。

同样,在java.net 包鞋提供的部分网络API,如 Socket、ServerSocket、HttpURLConnection 等,进行网络通信时,用到的也是java。io下的流操作,也是同步阻塞IO。

1.2 Non Blocking IO 

Non Blocking IO 也称NIO,即同步非阻塞编程IO。Java 1.4中引入NIO框架,在java.nio 包中提供了Channer、Selector、Buffer 等抽象类,可以快速构建多路复用的IO程序,用于提供更接近操作系统底层高性能数据操作的方式。

1.3 Asynchronous IO

Asynchronous IO,即异步非阻塞IO。Java 7 提供了改进版的NIO,引入异步非阻塞的IO,由操作系统完成后,回调通知服务端启动现场去处理。

 

2 BIO 实现逻辑以及局限性

在BIO同步阻塞模式下,一个服务器可以开启多个线程来处理客户端的连接,但是一个处理线程只能对应一个客户端的连接。

如果大量客户端来连接服务器,服务端将开启大量的线程处理连接,  开启线程非常消耗资源,很容易达到性能瓶颈。

 2.1 案例 单线程服务端

服务端

package com.spqin.nio.newstudy.bio;

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

/**
 * 单线程服务段
 */
public class ServerSocketSingleThread {
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(9090);

            while(true){
                System.out.println("阻塞等待客户端的连接");
                // accept 将一直阻塞直到客户端的连接到来
                Socket socket = serverSocket.accept();
                System.out.println("客户端已经连接了");

                //开始处理客户端的读写请求
                InputStream inputStream = socket.getInputStream();
                //用于接受客户端的数据
                byte[] data = new byte[1024];
                //读取到的数据长度
                int len = inputStream.read(data);
                // 把接收端到的数据转为字符串显示
                System.out.println("收到客户端发来的数据:"+new String(data,0,len));

                OutputStream outputStream = socket.getOutputStream();
                outputStream.write("收到连接,可以聊天了".getBytes("utf-8"));
                outputStream.flush();
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

连个客户端发起请求

客户端1

package com.spqin.nio.newstudy.bio;

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

/**
 * @author ChaoMing
 * @description 客户端
 * @date 2024-12-22 11:01
 */
public class SocketClient {
    public static void main(String[] args) {
        // 连接服务
        System.out.println("连接服务端");
        try {
            Socket socket = new Socket("127.0.0.1", 9090);
            //获得输出流
            OutputStream outputStream = socket.getOutputStream();
            System.out.println("睡眠一段时间,模拟客户端实际发消息的场景");
            TimeUnit.SECONDS.sleep(10);
            outputStream.write("张三 向客户端发起连接!".getBytes("utf-8"));
            outputStream.flush();
            InputStream inputStream = socket.getInputStream();
            byte[] data = new byte[1024];
            int len = inputStream.read(data);
            System.out.println("接收到服务端的相应数据:"+new String (data,0,len));
            //关闭连接
            socket.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

    }
}

客户端2

package com.spqin.nio.newstudy.bio;

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

/**
 * @author ChaoMing
 * @description 客户端
 * @date 2024-12-22 11:01
 */
public class SocketClient2 {
    public static void main(String[] args) {
        // 连接服务
        System.out.println("连接服务端");
        try {
            Socket socket = new Socket("127.0.0.1", 9090);
            //获得输出流
            OutputStream outputStream = socket.getOutputStream();
            TimeUnit.SECONDS.sleep(10);
            outputStream.write("李四 向客户端发起连接!".getBytes("utf-8"));
            outputStream.flush();
            InputStream inputStream = socket.getInputStream();
            byte[] data = new byte[1024];
            int len = inputStream.read(data);
            System.out.println("接收到服务端的相应数据:"+new String (data,0,len));
            //关闭连接
            socket.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

    }
}

运行调试

服务端打印消息:

阻塞等待客户端的连接
客户端已经连接了
收到客户端发来的数据:张三 向客户端发起连接!
阻塞等待客户端的连接
客户端已经连接了
收到客户端发来的数据:李四 向客户端发起连接!
阻塞等待客户端的连接

客户端1打印消息:

连接服务端
睡眠一段时间,模拟客户端实际发消息的场景
接收到服务端的相应数据:收到连接,可以聊天了

客户端2打印消息:

连接服务端
睡眠一段时间,模拟客户端实际发消息的场景
接收到服务端的相应数据:收到连接,可以聊天了

可以看出这个服务端一次只能处理一个客户端的请求,请求处理完之前不能接受第二个客户端的请求。

说明服务端可以开启线程处理客户端的连接,但是一个线程只能处理一个客户端的来连接,不能并行处理两个客户端的连接。

要处理2个客户端连接必须另外开启1个线程。

 2.2 案例 多线程服务端

只需要改进服务端,让其支持多个客户端的连接(每个客户端开启一个子线程处理),客户端不需要改动

package com.spqin.nio.newstudy.bio.multithreadserver;

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

/**
 * 多线程服务段
 */
public class ServerSocketMultiThread {
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(9090);

            while(true){
                System.out.println("阻塞等待客户端的连接");
                // accept 将一直阻塞直到客户端的连接到来
                Socket socket = serverSocket.accept();
                System.out.println("客户端已经连接了,开启子线程处理客户端的连接");

                new Thread(new ConnectHandler(socket),"客户端Socket 端口"+socket.getPort()).start();
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

客户连接处理器

package com.spqin.nio.newstudy.bio.multithreadserver;

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

/**
 * @author ChaoMing
 * @description 处理来自客户端的连接
 * @date 2024-12-22 11:29
 */
public class ConnectHandler implements Runnable{

    private Socket socket;

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

    @Override
    public void run() {
        try {
            //开始处理客户端的读写请求
            InputStream inputStream = socket.getInputStream();
            //用于接受客户端的数据
            byte[] data = new byte[1024];
            //读取到的数据长度
            int len = inputStream.read(data);
            // 把接收端到的数据转为字符串显示
            System.out.println(Thread.currentThread().getName()+"线程收到客户端发来的数据:"+new String(data,0,len));

            OutputStream outputStream = socket.getOutputStream();
            outputStream.write("收到连接,可以聊天了".getBytes("utf-8"));
            outputStream.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

运行结果

服务端打印消息:

阻塞等待客户端的连接
客户端已经连接了,开启子线程处理客户端的连接
阻塞等待客户端的连接
客户端已经连接了,开启子线程处理客户端的连接
阻塞等待客户端的连接
客户端Socket 端口54643线程收到客户端发来的数据:张三 向客户端发起连接!
客户端Socket 端口54646线程收到客户端发来的数据:李四 向客户端发起连接!

客户端信息不变,略。

 2.3 案例 线程池服务端

设计一个线程池,最大线程数为2,最大能持支 4个任务,任务时长设定为10s(让任务不能理解结束),等第5个任务提交时,线程池默认的线程处理策略是拒绝提交。

服务端代码

package com.spqin.nio.newstudy.bio.threadpoolserver;

import com.spqin.nio.newstudy.bio.multithreadserver.ConnectHandler;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 线程池服务端
 */
public class ServerSocketThreadPool {
    public static void main(String[] args) {
        // 创建一个最大线程数为2的线程池,
        // 每个队列设置为2,则 最大能支持2 + 2  = 4个线程
        // 创建 10个客户端,每个客户端,睡眠 10秒,这样理论上再地5个任务提交时,线程池会采取默认的拒绝策略,
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                2, 2,
                1, TimeUnit.MINUTES,
                new LinkedBlockingDeque<>(2),
                new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r,"服务端线程城池-");
                if (t.isDaemon())
                    t.setDaemon(false);
                if (t.getPriority() != Thread.NORM_PRIORITY)
                    t.setPriority(Thread.NORM_PRIORITY);
                return t;
            }
        });

        try {
            ServerSocket serverSocket = new ServerSocket(9090);

            while(true){
                System.out.println("阻塞等待客户端的连接");
                // accept 将一直阻塞直到客户端的连接到来
                Socket socket = serverSocket.accept();
                System.out.println("客户端已经连接了,开启子线程处理客户端的连接");
                try{
                    //线程池拒绝异常
                    threadPoolExecutor.execute(new ConnectHandler(socket));
                }catch (Exception e) {
                    System.out.println("线程池异常:"+e.getMessage());
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

客户端代码

package com.spqin.nio.newstudy.bio.threadpoolserver;

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

/**
 * @author ChaoMing
 * @description 线程池客户端(由于服务端是线程池,所以要增加客户端连接数)
 * @date 2024-12-22 11:01
 */
public class SocketClientThreadPool {
    static int num = 0;
    public static void main(String[] args) {
        //创建 10个客户端
        while (num < 10) {
            num++;
            //循环开启子线程徐连接客户端
            new Thread(()->{
                int no = num;
                // 连接服务
                System.out.println("连接服务端");
                try {
                    Socket socket = new Socket("127.0.0.1", 9090);
                    //获得输出流
                    OutputStream outputStream = socket.getOutputStream();
                    System.out.println("睡眠一段时间,模拟客户端实际发消息的场景");
                    TimeUnit.SECONDS.sleep(10);
                    outputStream.write(("用户" + no + "向客户端发起连接!").getBytes("utf-8"));
                    outputStream.flush();
                    InputStream inputStream = socket.getInputStream();
                    byte[] data = new byte[1024];
                    int len = inputStream.read(data);
                    System.out.println("接收到服务端的相应数据:"+new String (data,0,len));
                    //关闭连接
                    socket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }).start();

            //延迟一下主线程的提交速度,让子线程尽快创建
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

        }
    }
}

运行结果

服务端结果:

阻塞等待客户端的连接
客户端已经连接了,开启子线程处理客户端的连接
阻塞等待客户端的连接
客户端已经连接了,开启子线程处理客户端的连接
阻塞等待客户端的连接
客户端已经连接了,开启子线程处理客户端的连接
阻塞等待客户端的连接
客户端已经连接了,开启子线程处理客户端的连接
阻塞等待客户端的连接
客户端已经连接了,开启子线程处理客户端的连接
线程池异常:Task com.spqin.nio.newstudy.bio.multithreadserver.ConnectHandler@24d46ca6 rejected from java.util.concurrent.ThreadPoolExecutor@4517d9a3[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 0]
阻塞等待客户端的连接
客户端已经连接了,开启子线程处理客户端的连接
线程池异常:Task com.spqin.nio.newstudy.bio.multithreadserver.ConnectHandler@372f7a8d rejected from java.util.concurrent.ThreadPoolExecutor@4517d9a3[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 0]
阻塞等待客户端的连接
客户端已经连接了,开启子线程处理客户端的连接
线程池异常:Task com.spqin.nio.newstudy.bio.multithreadserver.ConnectHandler@2f92e0f4 rejected from java.util.concurrent.ThreadPoolExecutor@4517d9a3[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 0]
阻塞等待客户端的连接
客户端已经连接了,开启子线程处理客户端的连接
线程池异常:Task com.spqin.nio.newstudy.bio.multithreadserver.ConnectHandler@28a418fc rejected from java.util.concurrent.ThreadPoolExecutor@4517d9a3[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 0]
阻塞等待客户端的连接
客户端已经连接了,开启子线程处理客户端的连接
线程池异常:Task com.spqin.nio.newstudy.bio.multithreadserver.ConnectHandler@5305068a rejected from java.util.concurrent.ThreadPoolExecutor@4517d9a3[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 0]
阻塞等待客户端的连接
客户端已经连接了,开启子线程处理客户端的连接
线程池异常:Task com.spqin.nio.newstudy.bio.multithreadserver.ConnectHandler@1f32e575 rejected from java.util.concurrent.ThreadPoolExecutor@4517d9a3[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 0]
阻塞等待客户端的连接
服务端线程城池-线程收到客户端发来的数据:用户1向客户端发起连接!
服务端线程城池-线程收到客户端发来的数据:用户2向客户端发起连接!
服务端线程城池-线程收到客户端发来的数据:用户3向客户端发起连接!
服务端线程城池-线程收到客户端发来的数据:用户4向客户端发起连接!

客户端打印结果

连接服务端
睡眠一段时间,模拟客户端实际发消息的场景
连接服务端
睡眠一段时间,模拟客户端实际发消息的场景
连接服务端
睡眠一段时间,模拟客户端实际发消息的场景
连接服务端
睡眠一段时间,模拟客户端实际发消息的场景
连接服务端
睡眠一段时间,模拟客户端实际发消息的场景
连接服务端
睡眠一段时间,模拟客户端实际发消息的场景
连接服务端
睡眠一段时间,模拟客户端实际发消息的场景
连接服务端
睡眠一段时间,模拟客户端实际发消息的场景
连接服务端
睡眠一段时间,模拟客户端实际发消息的场景
连接服务端
睡眠一段时间,模拟客户端实际发消息的场景
接收到服务端的相应数据:收到连接,可以聊天了
接收到服务端的相应数据:收到连接,可以聊天了
接收到服务端的相应数据:收到连接,可以聊天了
接收到服务端的相应数据:收到连接,可以聊天了

可以看到在第五个客户端连接时,线程池抛出异常,服务端没有处理对应的一场,服务端挂掉了,不再向线程池提交任务,已经提交到线程池的任务继续执行。

2.4 BIO 局限总结

在上面的例子中可以看出,IO代码里Read操作是阻塞操作,如果连接读数据时数据没有准备好,会导致线程阻塞,浪费线程资源。

如果采用多线程处理客户端请求,如果客户端请求过多,将会导致服务端不断创建出新的线程,对服务器造成过大的压力。

采用线程池实现服务端,是对多线程资源浪费的一种优化,解决不了线程阻塞的问题,如果请求过多会出现提交任务被拒绝(即任务队列满了,不支持新任务提交了)。

BIO的使用和维护相对 比较简单,适用于较小且稳定的框架

标签:BIO,java,线程,import,多线程,连接,服务端,客户端
From: https://www.cnblogs.com/spqin/p/18621874

相关文章

  • 【并发编程】第十一章 测试和调试多线程应用程序
    第十一章测试和调试多线程应用程序11.1与并发相关的bug类型有些类型的错误直接与并发的使用相关11.1.1不必要的阻塞线程因为等待某些条件(如互斥锁、条件变量、期值对象或I/O操作)而无法继续执行:死锁:两个或多个线程无限期地等待对方释放资源,导致程序挂起活锁:线程不断尝......
  • Kafka服务端认证日志导致磁盘空间占满案例
    背景    某IP为44.2的服务器挂载的硬盘服务器磁盘空间占满突然故障排查过程查看docker容器磁盘SIZE查看docker容器使用磁盘大小此目前单独挂载路劲/data/docker使用du-sh*查看,发现kafka容器消耗91G空间查看具体容器,判断是kafkakafka容器状态是客户端认证识机制,Kafka日......
  • C++ 中的多线程编程:从基础到实战
        随着多核处理器的普及,多线程编程成为现代C++开发中的关键技能。C++11引入了强大的线程库,使得多线程编程更安全、更高效。本文将带你深入了解C++中的多线程编程,从基础概念到实际案例,逐步掌握如何用现代C++编写高效的多线程程序。一、多线程的基础概念  ......
  • linux-多线程
    目录​编辑一、线程  1.线程定义2.线程的优点:3.线程的缺点4.线程异常:5.线程用途:6.Linux进程VS线程     linux线程控制        创建线程:线程终止:线程等待 分离线程Linux线程互斥互斥量mutex互斥量的接口初始化互斥量销毁互斥量阻塞......
  • Bios中断向量表
    ah用于传递中断例程中的子程序编号中断号备注0123456789向键盘缓冲区写如一个键盘输入ABCDEF10H包含多个和屏幕输出相关的子程序16H-0号子程序从键盘缓冲区中读取一个键盘输入,结果:(ah)=扫描码,(a......
  • 【Java学习笔记】多线程基础
    并行:同一时刻,多任务同时进行多任务分别进行一、线程相关概念1.程序是为完成特定任务、用某种语言编写的一组指令的集合。简单的说:就是我们写的代码2.进程(1)进程指的就是运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅......
  • 使用 1panel图形化部署LobeChat服务端数据库版
    LobeChat它是什么?LobeChat是一个现代化设计的开源ChatGPT/LLMs聊天应用与开发框架,支持语音合成、多模态、可扩展的(functioncall)插件系统,一键免费拥有你自己的ChatGPT/Gemini/Claude/Ollama应用。它的特性?多模型服务商支持;支持本地大语言模型(LLM);模型视觉识别......
  • sse 浏览器端技术使用,Server-Sent Events ,EventSource,来建立长连接保持与服务端的通讯
    Server-SentEvents(SSE)是一种允许服务器向客户端推送更新的技术。它使用HTTP协议,通过一个持久的HTTP连接,服务器可以在任何时间发送新的事件到客户端。SSE通常用于实时更新,如股票价格、新闻更新等。以下是一个简单的SSE使用示例:服务器端(Node.js+Express)constex......
  • Java多线程
    多线程总结Java中的多线程是Java编程语言中的一个重要特性,它允许多个线程同时执行,从而提高程序的性能和响应能力。多线程在处理并发任务、优化资源利用率以及构建高性能应用程序方面发挥着关键作用。本文将详细介绍Java中的多线程,包括其基本概念、线程的创建与管理、线程同步、并......
  • Java多线程第一篇-认识多线程
    文章目录进程和线程概念继承Thread重写run方法实现Runnable重写run方法(解耦合)通过匿名内部类lambda表达式线程的常见的属性(方法)Id(getId)名称(getName)是否后台线程(isDaemon)是否存活(isAlive)进程和线程概念进程(process):进程是操作系统资源分配的基本单位,操作系统......