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

news/2025/3/12 1:19:47/文章来源:https://www.cnblogs.com/spqin/p/18621874

课程介绍

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;}@Overridepublic 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() {@Overridepublic 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的使用和维护相对 比较简单,适用于较小且稳定的框架

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/856873.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ZBlog升级之后出现后台登录错误

升级 1.7.3.3260 之后出现后台登录错误( 提示非法访问,验证码不显示,验证码报错 ) 这是因为 1.7.3.3260 增加了对后台登录的 2 个保护功能,因为主题插件的兼容性原因或其它原因造成功能不正常,可以在 option.php 里关掉 CSRF 保护功能或是验证码功能,或是 2 个都关闭 使…

织梦源码后台更新系统后,网页错乱的解决方法

覆盖模板文件下载之前使用的模板文件。 覆盖 templets/default 目录下的文件。 如果使用的是其他模板目录,确保更新不会影响到这些目录。扫码添加技术【解决问题】专注中小企业网站建设、网站安全12年。熟悉各种CMS,精通PHP+MYSQL、HTML5、CSS3、Javascript等。承接:企业仿站…

解决Dede织梦上传图片失败 ERROR:Copy Uploadfile Error! 提示

错误显示:拷贝(复制)上传文件出错! 原因:上传的文件损坏。 上传目录无写权限。解决方法:尝试上传其它图片。 给uploads写入权限:Linux服务器:通过FTP设置 uploads 目录为777权限,子文件夹选择继承。 Windows 2003服务器:右键文件夹属性 -> 安全 -> 添加账户 -&…

织梦CMS:Error:check Snooping out of bounds 的解决办法

Error:check Snooping out of bounds @ D:wwwrootgdhgcwwwrootkjempletsdefaultarticle_article.htm原因:路径中包含了大写字母,使用 strpos 操作时区分大小写。解决方法:打开 include/dedetag.class.php 文件。 找到第325行,修改为:if (stripos($path, $this->clean(D…

dedecms修改网站内容,如何在DedeCMS中编辑文章和页面

在DedeCMS中编辑文章和页面的步骤如下:登录管理后台:使用管理员账号登录DedeCMS管理后台。 进入内容管理:导航至“内容管理”或“文章管理”模块。 选择内容:找到需要修改的文章或页面。 编辑内容:点击“编辑”按钮,进行必要的修改。 保存更改:确认无误后,保存修改。扫…

如何通过修改hosts限制访问网站,利用hosts文件限制网站访问

打开hosts文件:使用文本编辑器打开C:\Windows\System32\drivers\etc\hosts(Windows)或/etc/hosts(Linux/Mac)。 添加条目:在文件末尾添加一行,格式为127.0.0.1 网站域名。 保存文件:保存并关闭文件。 刷新DNS缓存:在命令行中运行ipconfig /flushdns(Windows)或sudo …

JAVA-通过大疆TSDK的API直接获取红外图片温度信息

一、前言 看过很多关于大疆红外图片用TSDK取温的方式,但是网上能搜到的大部分教程都是通过官方下载文件smple编译出来的程序来取温,如果这样做,虽然确实也能够实现目的,但不得不说,不但会降低运行速度,而且代码调用起来也麻烦。所以不如研究一下怎么直接调用他们的C++ AP…

4-14文件上传刷题日记

buuctf[是兄弟就来传马 先传一个php木马页面报错,随便改一个后缀,看看是黑名单还是白名单过滤,这里php代码前加GIF89a绕过图片大小检查报错,接着试一下是不是MIME过滤 Content-Type: image/png 上传成功 /var/www/html/upload/743be915f2c6ab5de3600d87068cafd2/hack.jpg s…

24浮动案例练习-布局方案总结

一、浮动案例三 float的兼容性是大于flex布局的,然后flex大于grid布局 一般的公司都不实用浮动了,大公司一般会使用浮动 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" co…

《计算机组成及汇编语言原理》阅读笔记:p28-p47

《计算机组成及汇编语言原理》学习第 3 天,p28-p47 总结,总计 20 页。 一、技术总结 1.Virtual Machine 2.stack 3.The fetch-execute Cycle 在控制单元(Control Unit, CU)里面有一个指令寄存器(Instruction Register, IR)和一个程序计数器(Program Counter, PC)。PC保存下次…

20241313 刘鸣宇 《计算机基础与程序设计》第13周学习总结

作业信息这个作业属于哪个课程 <班级的链接>(如2024-2025-1-计算机基础与程序设计)这个作业要求在哪里 <作业要求的链接>(如2024-2025-1计算机基础与程序设计第一周作业)这个作业的目标 <写上具体方面>作业正文 ... 本博客链接教材学习内容总结 《C语言程序…

中考阅读理解深入逻辑分析-002 Battle of the Classroom: Bits vs. Books 课堂之战:比特对抗书本

文章正文 The debate on “textbooks vs computers” has been going on for years. How much technology is in the classroom? Should textbooks be placed by notebook computers? The is no doubt that computers are powerful. Computer-based lesson plans are updated…