Java BIO 基本介绍
Java BIO 同步阻塞模型,属于传统的 java io 编程,其相关的类和接口在 java.io包下。
服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善(实现多个客户连接服务器).
BIO工作机制
对 BIO 编程流程的梳理
- 服务器端启动一个 ServerSocket,注册端口,调用accpet方法监听客户端的Socket连接。
- 客户端启动 Socket对服务器进行通信,默认情况下服务器端需要对每个客户建立一个线程与之通讯。
这里,编写一些简单的案例便于理解。
BIO编程实例
网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信(绑定IP地址和端口),客户端通过连接操作向服务端监听的端口地址发起连接请求,基于TCP协议下进行三次握手连接,连接成功后,双方通过网络套接字(Socket)进行通信。
功能1:客户端发送一个消息,服务端接口一个消息,通信结束!!
创建客户端对象:
(1)创建一个Socket的通信管道,请求与服务端的端口连接。
(2)从Socket管道中得到一个字节输出流。
(3)把字节流改装成自己需要的流进行数据的发送
客户端代码
import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; /** 目标: Socket网络编程。 Java提供了一个包:java.net下的类都是用于网络通信。 Java提供了基于套接字(端口)Socket的网络通信模式,我们基于这种模式就可以直接实现TCP通信。 只要用Socket通信,那么就是基于TCP可靠传输通信。 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 IOException { System.out.println("客户端的启动"); //创建一个socket通信管道,请求与服务端端口连接 Socket socket = new Socket("127.0.0.1", 8888); //从socket通信管道中得到一个字节输出流 OutputStream os = socket.getOutputStream(); //把字节流改装成自己需要的流进行数据发送 PrintStream ps = new PrintStream(os); ps.println("hi,我是客户端端,我给你发消息了!!"); ps.flush(); } }
创建服务端对象:
(1)注册端口
(2)开始等待接收客户端的连接,得到一个端到端的Socket管道
(3)从Socket管道中得到一个字节输入流。
(4)把字节输入流包装成自己需要的流进行数据的读取。
服务端代码
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; /** * @PackageName: com.netty.bio.demo1 * @author: youjp * @create: 2020-12-18 10:59 * @description: 服务端案例 * @Version: 1.0 */ public class ServerDemo { public static void main(String[] args) throws IOException { 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 ; if ((line = br.readLine())!=null){ System.out.println("服务端收到:"+line); } } }
启动测试,注意先启动服务端,再启动客户端,不然客户端会报错。
功能2:在功能1的基础上实现消息的多发和多收
功能1的案例中,只能实现客户端发送消息,服务端接收消息,并不能实现反复的收消息和反复的发消息,我们只需要在客户端案例中,加上反复按照行发送消息的逻辑即可!
客户端代码如下
import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner; /** 目标: Socket网络编程。 功能2: 客户端可以反复发,一个服务可以接收无数个客户端消息!! 小结:服务器如果想要接收多个客户端,那么必须引入线程,一个客户端一个线程处理!! */ public class ClientDemo { public static void main(String[] args) throws IOException { System.out.println("客户端的启动"); //创建一个socket通信管道,请求与服务端端口连接 Socket socket = new Socket("127.0.0.1", 8888); //从socket通信管道中得到一个字节输出流 OutputStream os = socket.getOutputStream(); InputStream in=socket.getInputStream(); //把字节流改装成自己需要的流进行数据发送 PrintStream ps = new PrintStream(os); Scanner scanner=new Scanner(System.in); while (true){ System.out.print("请说:"); String str=scanner.nextLine(); ps.println(str); ps.flush(); } } }
服务端
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; /** * @PackageName: com.netty.bio.demo1 * @author: youjp * @create: 2020-12-18 10:59 * @description: 服务端案例 * @Version: 1.0 */ public class ServerDemo { public static void main(String[] args) throws IOException { 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:使用线程池来接收多个客户端
在上述的案例中,一个服务端只能接收一个客户端的通信请求,那么如果服务端需要处理很多个客户端的消息通信请求应该如何处理呢,此时我们就需要在服务端引入线程了,也就是说客户端每发起一个请求,服务端就创建一个新的线程来处理这个客户端的请求,这样就实现了一个客户端一个线程的模型,图解模式如下:
服务端代码
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @PackageName: com.netty.bio.demo3 * @author: youjp * @create: 2020-12-20 17:23 * @description: * @Version: 1.0 */ public class ServerDemo { public static void main(String[] args) throws IOException { ExecutorService service= Executors.newCachedThreadPool(); //弹性伸缩线程池 System.out.println("服务启动----------"); ServerSocket serverSocket=new ServerSocket(8889); while(true){ final Socket socket= serverSocket.accept(); service.submit(new MyThread(socket));// } } } class MyThread implements Runnable{ private Socket socket; public MyThread(Socket socket){ this.socket=socket; } @Override public void run() { execute(); } public void execute(){ System.out.println("分配到线程---"+Thread.currentThread().getName()+"---------------"); //(3)从Socket管道中得到一个字节输入流。 InputStream is = null; try { is = socket.getInputStream(); //(4)把字节输入流包装成自己需要的流进行数据的读取。 BufferedReader br = new BufferedReader(new InputStreamReader(is)); //(5)读取数据 String line ; while ((line = br.readLine())!=null){ System.out.print("通过执行线程,--名称---"+Thread.currentThread().getName()+"---------------"); System.out.println("服务端收到:"+line); } } catch (IOException e) { System.out.println(Thread.currentThread().getName()+"断开连接"); //e.printStackTrace(); } } }
客户端
import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner; /** 目标: Socket网络编程。 实现:运行多个客户端,向服务端发送消息 public class ClientDemo { public static void main(String[] args) throws IOException { System.out.println("客户端的启动"); //创建一个socket通信管道,请求与服务端端口连接 Socket socket = new Socket("127.0.0.1", 8888); //从socket通信管道中得到一个字节输出流 OutputStream os = socket.getOutputStream(); //把字节流改装成自己需要的流进行数据发送 PrintStream ps = new PrintStream(os); Scanner scanner=new Scanner(System.in); while (true){ System.out.print("请说:"); String str=scanner.nextLine(); ps.println(str); ps.flush(); } } }
本案例你会发现,每单有客户端对服务端发起连接时,服务端就会创建一个线程进行处理。
当客户端的并发访问增加时。服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终导致进程宕机或者僵死,从而不能对外提供服务。
有兴趣的老爷,可以关注我的公众号【一起收破烂】,回复【006】获取2021最新java面试资料以及简历模型120套哦~