3.1 Java BIO 基本介绍
-
Java BIO 就是传统的 java io 编程,其相关的类和接口在 java.io
-
BIO(blocking I/O) : 同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需 要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善(实现多个客户连接服务器).
3.2 Java BIO 工作机制
对 BIO 编程流程的梳理
服务器端启动一个 ServerSocket,注册端口,调用accpet方法监听客户端的Socket连接。
客户端启动 Socket 对服务器进行通信,默认情况下服务器端需要对每个客户 建立一个线程与之通讯
3.3 传统的BIO编程实例回顾
网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定IP地址和端口),客户端通过连接操作向服务端监听的端口地址发起连接请求,基于TCP协议下进行三次握手连接,连接成功后,双方通过网络套接字(Socket)进行通信。
网络套接字(socket)是计算机网络中的一种通信机制,用于实现不同主机之间的数据交换。它是应用层与传输层之间的接口。通过网络套接字,应用程序可以通过网络发送和接收数据。套接字可以用于不同的网络协议,例如TCP套接字用于可靠的数据传输,UDP套接字用于不可靠的数据传输。
通过网络套接字,应用程序可以通过网络发送和接收数据。套接字可以用于不同的网络协议,例如TCP套接字用于可靠的数据传输,UDP套接字用于不可靠的数据传输。
传统的同步阻塞模型开发中,服务端ServerSocket负责绑定IP地址,启动监听端口;客户端Socket负责发起连接操作。连接成功后,双方通过输入和输出流进行同步阻塞式通信。 基于BIO模式下的通信,客户端 - 服务端是完全同步,完全耦合的。
客户端案例如下
package com.itheima._02bio01;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
/**
功能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();
}
}
服务端案例如下
package com.itheima._02bio01;
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 BIO模式下多发和多收消息
客户端代码如下
package com.itheima._03bio02;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
/**
目标: 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",8888);
// (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();
}
}
}
服务端代码如下
package com.itheima._03bio02;
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 {
String s = "886";
System.out.println("886".equals(s));
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.5 BIO模式下接收多个客户端
在上述的案例中,一个服务端只能接收一个客户端的通信请求,那么如果服务端需要处理很多个客户端的消息通信请求应该如何处理呢,此时我们就需要在服务端引入线程了,也就是说客户端每发起一个请求,服务端就创建一个新的线程来处理这个客户端的请求,这样就实现了一个客户端一个线程的模型,图解模式如下:
客户端案例代码如下
/**
目标: 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();
}
}
}
服务端案例代码如下
/**
服务端
*/
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;
}
@Override
public 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()+"下线了!");
}
}
}
小结
1.每个Socket接收到,都会创建一个线程,线程的竞争、切换上下文影响性能;
2.每个线程都会占用栈空间和CPU资源;
3.并不是每个socket都进行IO操作,无意义的线程处理;
4.客户端的并发访问增加时。服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终导致