企业型网站建设,芜湖的网站建设,国外建站公司,莱芜金点子招聘网最新招聘✅作者简介#xff1a;大家好#xff0c;我是Leo#xff0c;热爱Java后端开发者#xff0c;一个想要与大家共同进步的男人#x1f609;#x1f609; #x1f34e;个人主页#xff1a;Leo的博客 #x1f49e;当前专栏#xff1a; Netty实战专栏 ✨特色专栏#xff1a…
✅作者简介大家好我是Leo热爱Java后端开发者一个想要与大家共同进步的男人 个人主页Leo的博客 当前专栏 Netty实战专栏 ✨特色专栏 MySQL学习 本文内容 Netty实战专栏 | BIO详解 ️个人小站 个人博客欢迎大家访问 个人知识库 Leo知识库欢迎大家访问 目录 1.前言2.前置知识2.1阻塞IO和非阻塞IO2.2同步IO和非同步IO 3.传统的BIO通信方式简介4.传统的BIO的问题4.BIO通信方式深入分析4.1BIO的工作机制4.2传统的BIO编程实例回顾4.3多发多收案例1.客户端编写2.服务端编写3.小结 4.4BIO模式下接收多个客户端1.服务端编写2.客户端编写 4.4Java BIO模式下的端口转发思想1.客户端代码编写2.客户端线程处理类3.服务端代码编写4.服务端线程处理类测试 学习参考
讲师孙帅老师课程孙哥说netty
1.前言
大家好我是Leo哥上一篇博客我们主要大致了解了一下Java中IO的演变历程大体分为基础IO–BIO–NIO–AIO这几个阶段分部逐层递进。下面几个章节我们就要来聊聊这个变化。本节主要讨论关于BIO的知识。好了话不多说让我们开始吧。
2.前置知识
在学习BIO之前我们先了解一下IO相关的前置知识。
2.1阻塞IO和非阻塞IO
那什么是阻塞式 IO什么是非阻塞 IO 呢
阻塞IO
在这种模型中I/O 操作是阻塞的即执行 I/O 操作时线程会被阻塞直到操作完成。在阻塞 I/O 模型中每个连接都需要一个线程来处理。因此对于大量并发连接的场景阻塞 I/O 模型的性能较差。
在阻塞IO模型中常见的阻塞操作包括从输入流中读取数据和向输出流中写入数据。例如在读取文件内容时程序会一直等待文件数据加载到内存中才能继续执行后续代码。同样地向网络连接发送数据时程序会一直等待数据发送完毕才能进行下一步操作。
阻塞IO的特点是简单直观代码易于理解和编写。然而它也存在一些潜在的问题。首先如果IO操作时间较长如读取大文件或网络延迟高那么程序将会长时间地处于阻塞状态影响整体的性能和响应性能。其次阻塞IO模型通常是单线程的即一次只能处理一个IO请求这可能造成资源的浪费和效率低下。 非阻塞IO
为了解决阻塞IO模型的问题Java引入了非阻塞IONIO机制使得程序能够在等待IO操作完成时继续执行其他任务提高了系统的并发性能和响应速度。非阻塞IO模型使用选择器**(Selector)来管理多个通道(Channel)**并通过轮询的方式检查通道上是否有可以进行IO操作的事件从而实现高效的IO操作。
在这种模型中I/O 操作不会阻塞线程。当数据尚未准备好时I/O 调用会立即返回。线程可以继续执行其他任务然后在适当的时候再次尝试执行 I/O 操作。非阻塞 I/O 模型允许单个线程同时处理多个连接但可能需要在应用程序级别进行复杂的调度和管理。这种模型通常需要一个专门的线程来处理轮询操作。
**Java NIO(New IO)库提供了非阻塞IO支持。在NIO中数据传输通过缓冲区(Buffer)进行而通道(Channel)**则负责管理缓冲区和底层IO操作。其中**选择器(Selector)**是实现非阻塞IO的关键组件之一。选择器可以注册多个通道并监视它们上的IO事件如读取、写入、连接等。通过轮询选择器上的事件程序可以快速地响应IO事件而不会被单个IO操作所阻塞。
与阻塞IO相比非阻塞IO具有更高的并发性能和响应速度。非阻塞IO模型允许程序同时处理多个IO请求并且可以在等待IO操作完成时继续执行其他任务从而充分利用系统资源。然而使用非阻塞IO也面临着更高的复杂性和编程难度因为程序需要显式地处理缓冲区和事件轮询等细节。 2.2同步IO和非同步IO
同步IO的概念是操作系统级别的IO了若Java程序发起IO请求若操作系统IO资源未准备好同步IO不做出任何响应。而非同步IO则会返回一个标记当IO资源准备完成后再通过事件机制通知程序。
如下图所示同步IO会因为IO资源未准备好而进入阻塞状态。
非同步IO则会通过事件机制避免和操作系统级别的阻塞。
3.传统的BIO通信方式简介
以前大多数网络通信方式都是阻塞模式的即:
客户端向服务器端发出请求后客户端会一直等待(不会再做其他事情)直到服务器端返回结果或者网络出现问题。服务器端同样的当在处理某个客户端A发来的请求时另一个客户端B发来的请求会等待直到服务器端的这个处理线程完成上一个处理。 4.传统的BIO的问题
低效率在等待IO操作时线程会一直处于阻塞状态占用系统资源无法响应其他请求。如果有大量的并发请求那么线程将被频繁地切换导致系统性能下降。编程复杂度高由于每个连接都需要一个独立的线程来处理因此需要管理大量的线程。而线程管理是一项非常复杂的任务容易出错。可伸缩性差由于每个连接都需要一个独立的线程来处理线程数量可能会随着连接数的增加而急剧增加导致系统崩溃或者性能急剧下降。不支持异步IOBIO只支持同步阻塞IO模型无法实现异步IO而异步IO在处理高并发和大吞吐量的场景中具有重要意义。
4.BIO通信方式深入分析
4.1BIO的工作机制 对 BIO 编程流程的梳理
服务器端启动一个 ServerSocket注册端口调用accpet方法监听客户端的Socket连接。客户端启动 Socket对服务器进行通信默认情况下服务器端需要对每个客户 建立一个线程与之通讯
4.2传统的BIO编程实例回顾 网络编程的基本模型是Client/Server模型也就是两个进程之 间进行相互通信其中服务端提供位置信绑定IP地址和端口 客户端通过连接操作向服务端监听的端口地址发起连接请 求基于TCP协议下进行三次握手连接连接成功后双方 通过网络套接字Socket进行通信。 传统的同步阻塞模型开发中服务端ServerSocket负责绑定 IP地址启动监听端口客户端Socket负责发起连接操作。连 接成功后双方通过输入和输出流进行同步阻塞式通信。 基于BIO模式下的通信客户端 - 服务端是完全同步完全耦合的。 4.3多发多收案例
1.客户端编写
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
/**目标: Socket网络编程。Java提供了一个包java.net下的类都是用于网络通信。Java提供了基于套接字端口Socket的网络通信模式我们基于这种模式就可以直接实现TCP通信。只要用Socket通信那么就是基于TCP可靠传输通信。功能1客户端发送一个消息服务端接口一个消息通信结束创建客户端对象1创建一个Socket的通信管道请求与服务端的端口连接。2从Socket管道中得到一个字节输出流。3把字节流改装成自己需要的流进行数据的发送创建服务端对象1注册端口2开始等待接收客户端的连接,得到一个端到端的Socket管道3从Socket管道中得到一个字节输入流。4把字节输入流包装成自己需要的流进行数据的读取。Socket的使用构造器public Socket(String host, int port)方法 public OutputStream getOutputStream()获取字节输出流public InputStream getInputStream() :获取字节输入流ServerSocket的使用构造器public ServerSocket(int port)小结通信是很严格的对方怎么发你就怎么收对方发多少你就只能收多少*/
public class ClientDemo {public static void main(String[] args) throws Exception {System.out.println(客户端的启动);// 1创建一个Socket的通信管道请求与服务端的端口连接。Socket socket new Socket(127.0.0.1,8888);// 2从Socket通信管道中得到一个字节输出流。OutputStream os socket.getOutputStream();// 3把字节流改装成自己需要的流进行数据的发送PrintStream ps new PrintStream(os);// 4开始发送消息ps.println(我是客户端我想约吃烤肉);ps.flush();}
}
2.服务端编写
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;/*** 服务端*/
public class ServerDemo {public static void main(String[] args) throws Exception {System.out.println(服务器的启动);// 1注册端口ServerSocket serverSocket new ServerSocket(8888);//2开始在这里暂停等待接收客户端的连接,得到一个端到端的Socket管道Socket socket serverSocket.accept();//3从Socket管道中得到一个字节输入流。InputStream is socket.getInputStream();//4把字节输入流包装成自己需要的流进行数据的读取。BufferedReader br new BufferedReader(new InputStreamReader(is));//5读取数据String line ;while((line br.readLine())!null){System.out.println(服务端收到line);}}
}
3.小结 在以上通信中服务端会一致等待客户端的消息如果客户端没有进行消息的发送服务端将一直进入阻塞状态。同时服务端是按照行获取消息的这意味着客户端也必须按照行进行消息的发送否则服务端将进入等待消息的阻塞状态 4.4BIO模式下接收多个客户端
在上面的案例中一个服务端只能接收到一个客户端传递过来的请求那么如果服务端需要处理很多个客户端的消息通信请求应该如何处理呢此时我们就需要在服务端引入线程了也就是说客户端每发起一个请求服务端就创建一个新的线程来处理这个客户端的请求这样就实现了一个客户端一个线程的模型。 下面我们通过编写代码案例进一步深入。
1.服务端编写
/**服务端*/
public class ServerDemo {public static void main(String[] args) throws Exception {System.out.println(服务器的启动);// 1注册端口ServerSocket serverSocket new ServerSocket(7777);while(true){//2开始在这里暂停等待接收客户端的连接,得到一个端到端的Socket管道Socket socket serverSocket.accept();new ServerReadThread(socket).start();System.out.println(socket.getRemoteSocketAddress()上线了);}}
}
class ServerReadThread extends Thread{private Socket socket;public ServerReadThread(Socket socket){this.socket socket;}Overridepublic void run() {try{//3从Socket管道中得到一个字节输入流。InputStream is socket.getInputStream();//4把字节输入流包装成自己需要的流进行数据的读取。BufferedReader br new BufferedReader(new InputStreamReader(is));//5读取数据String line ;while((line br.readLine())!null){System.out.println(服务端收到socket.getRemoteSocketAddress():line);}}catch (Exception e){System.out.println(socket.getRemoteSocketAddress()下线了);}}
}2.客户端编写
/**目标: Socket网络编程。功能1客户端可以反复发一个服务端可以接收无数个客户端的消息小结服务器如果想要接收多个客户端那么必须引入线程一个客户端一个线程处理*/
public class ClientDemo {public static void main(String[] args) throws Exception {System.out.println(客户端的启动);// 1创建一个Socket的通信管道请求与服务端的端口连接。Socket socket new Socket(127.0.0.1,7777);// 2从Socket通信管道中得到一个字节输出流。OutputStream os socket.getOutputStream();// 3把字节流改装成自己需要的流进行数据的发送PrintStream ps new PrintStream(os);// 4开始发送消息Scanner sc new Scanner(System.in);while(true){System.out.print(请说:);String msg sc.nextLine();ps.println(msg);ps.flush();}}
}注意
每个Socket接收到都会创建一个线程线程的竞争、切换上下文影响性能。每个线程都会占用栈空间和CPU资源。并不是每个socket都进行IO操作无意义的线程处理。客户端的并发访问增加时。服务端将呈现1:1的线程开销访问量越大系统将发生线程栈溢出线程创建失败最终导致进程宕机或者僵死从而不能对外提供服务。
4.4Java BIO模式下的端口转发思想
需要实现一个客户端的消息可以发送给所有的客户 1.客户端代码编写
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class Client {public static void main(String[] args) {try {//1.请求与服务端的Socket对象连接Socket socket new Socket(127.0.0.1, 9999);//收消息Thread clientThread new ClientReaderThread(socket);clientThread.start();while (true) {//发消息OutputStream os socket.getOutputStream();PrintStream ps new PrintStream(os);//3. 使用循环不断的发送消息给服务端接收Scanner sc new Scanner(System.in);//System.out.print(client send message);String msg sc.nextLine();ps.println(msg);ps.flush();}} catch (Exception e) {e.printStackTrace();}}
}2.客户端线程处理类
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
public class ClientReaderThread extends Thread {private Socket socket;public ClientReaderThread(Socket socket) {this.socket socket;}Overridepublic void run() {try {while (true) {InputStream is socket.getInputStream();//4.把字节输入流包装成一个缓存字符输入流BufferedReader br new BufferedReader(new InputStreamReader(is));String msg;if ((msg br.readLine()) ! null) {System.out.println(msg);}}} catch (Exception e) {}}
}3.服务端代码编写
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/*** 目标BIO模式下的端口转发思想-服务端实现* 服务端实现需求* 1.注册端口* 2.接收客户端的socket连接交给一个独立的线程来处理* 3.把当前连接的客户端socket存入到一个所谓的在线socket集合中保存* 4.接收客户端的消息然后推动给当前所有的在线socket接收*/
public class Server {//定义一个静态集合public static ListSocket allSocketOnLine new ArrayList();public static void main(String[] args) {try {ServerSocket ss new ServerSocket(9999);while (true) {Socket socket ss.accept();//把登录的客户端socket存入到一个在线集合中去allSocketOnLine.add(socket);//为当前登录成功的socket分配一个独立的线程来处理与之通信new ServerReaderThread(socket).start();}} catch (Exception e) {e.printStackTrace();}}
}4.服务端线程处理类
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
public class ServerReaderThread extends Thread {private Socket socket;public ServerReaderThread(Socket socket) {this.socket socket;}Overridepublic void run() {try {//1.从socket中去获取当前客户端的输入流BufferedReader br new BufferedReader(newInputStreamReader(socket.getInputStream()));String msg;while ((msg br.readLine()) ! null) {System.out.println(服务器收到消息 msg);//2.服务端接收到了客户端的消息后需要推送给所有的当前在线的socketsendMsgToAllClient(msg, socket);}} catch (Exception e) {e.printStackTrace();System.out.println(当前有人下线了);//从在线socket集合中移除本socketServer.allSocketOnLine.remove(socket);}}/*** 把当前客户端发送来的消息推送给全部在线的socket** param msg*/private void sendMsgToAllClient(String msg, Socket socket) throws Exception {for (Socket sk : Server.allSocketOnLine) {//只发送给除自己以外的其他客户端if (socket ! sk) {PrintStream ps new PrintStream(sk.getOutputStream());ps.println(msg);ps.flush();}}}
}测试
先启动服务端再启动客户端
服务器收到消息大家好我是客户端一
服务器收到消息哈哈哈哈
服务器收到消息大家好我是client2
服务器收到消息嘻嘻嘻嘻
服务器收到消息hello everyone
服务器收到消息i am client3#客户端一
大家好我是客户端一 --发送
哈哈哈哈 --发送
大家好我是client2 --接收
嘻嘻嘻嘻 --接收
hello everyone --接收
i am client3 --接收#客户端二
大家好我是客户端一 --接收
哈哈哈哈 --接收
大家好我是client2 --发送
嘻嘻嘻嘻 --发送
hello everyone --接收
i am client3 --接收#客户端三
大家好我是客户端一 --接收
哈哈哈哈 --接收
大家好我是client2 --接收
嘻嘻嘻嘻 --接收
hello everyone --发送
i am client3 --发送