Java BIO 基本介绍
- Java BIO 就是传统的 java io 编程,其相关的类和接口在 java.io
- BIO(blocking I/O) : 同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善(实现多个客户连接服务器).
Java BIO 工作机制
对 BIO 编程流程的梳理
- 服务器端启动一个 ServerSocket,注册端口,调用accpet方法监听客户端的Socket连接。
- 客户端启动 Socket 对服务器进行通信,默认情况下服务器端需要对每个客户 建立一个线程与之通讯
传统的BIO编程实例回顾
1.网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信(绑定IP地址和端口),客户端通过连接操作向服务端监听的端口地址发起连接请求,基于TCP协议下进行三次握手连接,连接成功后,双方通过网络套接字(Socket)进行通信。
2.传统的同步阻塞模型开发中,服务端ServerSocket负责绑定IP地址,启动监听端口;客户端Socket负责发起连接操作。连接成功后,双方通过输入和输出流进行同步阻塞式通信。
基于BIO模式下的通信,客户端 - 服务端是完全同步,完全耦合的。
过程:
创建客户端对象:
(1)创建一个Socket的通信管道,请求与服务端的端口连接。
(2)从Socket管道中得到一个字节输出流。
(3)把字节流改装成自己需要的流进行数据的发送
创建服务端对象:
(1)注册端口
(2)开始等待接收客户端的连接,得到一个端到端的Socket管道
(3)从Socket管道中得到一个字节输入流。
(4)把字节输入流包装成自己需要的流进行数据的读取。
客户端案例如下
package one;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
public class client {
public static void main(String[] args) {
try {
Socket socket=new Socket("127.0.0.1",9999);
OutputStream outputStream=socket.getOutputStream();
PrintStream printStream=new PrintStream(outputStream);
printStream.println("hello_world");
printStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端案例
package one;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class server {
public static void main(String[] args) {
ServerSocket ss;
{
try {
ss = new ServerSocket(9999);
Socket socket=ss.accept();
InputStream inputStream=socket.getInputStream();
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(inputStream));
String msg;
while((msg = bufferedReader.readLine())!=null){
System.out.println("服务端收到"+msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
结果:
小结
- 在以上通信中,服务端会一致等待客户端的消息,如果客户端没有进行消息的发送,服务端将一直进入阻塞状态。
- 同时服务端是按照行获取消息的,这意味着客户端也必须按照行进行消息的发送,否则服务端将进入等待消息的阻塞状态!
BIO模式下多发和多收消息
在上述案例中,只能实现客户端发送消息,服务端接收消息,并不能实现反复的收消息和反复的发消息,我们只需要在客户端案例中,加上反复按照行发送消息的逻辑即可!案例代码如下:
服务端案例
package two;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class server {
public static void main(String[] args) {
ServerSocket ss;
{
try {
ss = new ServerSocket(9999);
Socket socket=ss.accept();
InputStream inputStream=socket.getInputStream();
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(inputStream));
String msg;
while((msg = bufferedReader.readLine())!=null){
System.out.println("服务端收到"+msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端案例
package two;
import java.io.IOException;
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 {
Socket socket=new Socket("127.0.0.1",9999);
OutputStream outputStream=socket.getOutputStream();
PrintStream printStream=new PrintStream(outputStream);
while(true){
Scanner scanner=new Scanner(System.in);
System.out.println("请输入消息:");
String s = scanner.nextLine();
printStream.println(s);
printStream.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
小结
- 本案例中确实可以实现客户端多发多收
- 但是服务端只能处理一个客户端的请求,因为服务端是单线程的。一次只能与一个客户端进行消息通信。
BIO模式下接收多个客户端
客户端
package three;
import java.io.IOException;
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 {
Socket socket=new Socket("127.0.0.1",9999);
OutputStream outputStream=socket.getOutputStream();
PrintStream printStream=new PrintStream(outputStream);
while(true){
Scanner scanner=new Scanner(System.in);
System.out.println("请输入消息:");
String s = scanner.nextLine();
printStream.println(s);
printStream.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端
package three;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class server {
public static void main(String[] args) {
ServerSocket ss;
{
try {
ss = new ServerSocket(9999);
while(true){
Socket socket=ss.accept();
new ServerThreadReader(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package three;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
public class ServerThreadReader extends Thread{
private Socket socket;
public ServerThreadReader(Socket socket){
this.socket=socket;
}
@Override
public void run() {
InputStream inputStream= null;
try {
inputStream = socket.getInputStream();
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(inputStream));
String msg;
while((msg = bufferedReader.readLine())!=null){
System.out.println("服务端收到"+msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
小结
- 1.每个Socket接收到,都会创建一个线程,线程的竞争、切换上下文影响性能;
- 2.每个线程都会占用栈空间和CPU资源;
- 3.并不是每个socket都进行IO操作,无意义的线程处理;
- 4.客户端的并发访问增加时。服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终导致进程宕机或者僵死,从而不能对外提供服务。
伪异步I/O编程
概述
在上述案例中:客户端的并发访问增加时。服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终导致进程宕机或者僵死,从而不能对外提供服务。
采用线程池和任务队列实现,当客户端接入时,将客户端的Socket封装成一个Task(该任务实现java.lang.Runnable线程任务接口)交给后端的线程池中进行处理。JDK的线程池维护一个消息队列和N个活跃的线程,对消息队列中Socket任务进行处理,由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。
客户端
package four;
import java.io.IOException;
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 {
Socket socket=new Socket("127.0.0.1",9999);
OutputStream outputStream=socket.getOutputStream();
PrintStream printStream=new PrintStream(outputStream);
while(true){
Scanner scanner=new Scanner(System.in);
System.out.println("请输入消息:");
String s = scanner.nextLine();
printStream.println(s);
printStream.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端
package four;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
try{
ServerSocket socket=new ServerSocket(9999);
HandleSocketServer handleSocketServer=new HandleSocketServer(6,10);
while(true){
Socket socket1=socket.accept();
Runnable target=new ServerRunnableTarget(socket1);
handleSocketServer.execute(target);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
package four;
import java.io.*;
import java.net.Socket;
public class ServerRunnableTarget implements Runnable{
private Socket socket;
public ServerRunnableTarget(Socket socket){
this.socket=socket;
}
@Override
public void run() {
InputStream inputStream= null;
try {
inputStream = socket.getInputStream();
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(inputStream));
String msg;
while((msg = bufferedReader.readLine())!=null){
System.out.println("服务端收到"+msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package four;
import java.util.concurrent.*;
public class HandleSocketServer {
private ExecutorService executorService;
public HandleSocketServer(int maxThread,int QueueSize){
executorService=new ThreadPoolExecutor(3,maxThread,2, TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(QueueSize));
}
public void execute(Runnable target){
executorService.execute(target);
}
}