1.1、BIO基本介绍
- java BIO是传统的java io编程,其相关的类和接口在java.io包中
- BIO(Blocking IO) 同步阻塞,服务区实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程来进行处理,如果这个连接不做任何处理会造成不必要的线程开销,可以通过线程池机制改进。
1.2、BIO的工作机制
- 客户端
- 通过Socket对象请求与服务端建立连接
- 从Socket中得到字节输入或者字节输出流进行数据读写操作
- 服务端
- 通过ServerSocket注册端口
- 服务端通过调用accept方法用于监听客户端的Socket请求
- 从Socket中得到字节输入或者字节输出流进行数据读写操作
1.3、BIO传统编程代码实现
- 传统的同步阻塞模型开发中,服务端ServerSocket负责绑定IP地址,启动监听端口;客户端Socket负责 发起 连接操作。连接成功后,双方通过输入和输出流进行同步阻塞式通信。
- 基于BIO模式下的通信,客户端-服务端是完全同步,完全藕合的。
客户端代码如下:
public class ClientTest {
public static void main(String[] args) {
//创建Socket对象请求服务端的连接
Socket socket = null;
try {
socket = new Socket("127.0.0.1",8889);
// 从Socket对象中获取一个字节输出流
OutputStream os = socket.getOutputStream();
//把字节输出流包装成一个打印流
PrintStream ps = new PrintStream(os);
ps.println("hello World! 与服务端通信成功");
ps.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端代码:
public class ServerTest {
public static void main(String[] args) {
System.out.println("===服务端启动===");
//定义一个ServerSocket对象进行服务端的端口注册
ServerSocket ss = null;
try {
ss = new ServerSocket(8889);
//监听客户端的Socket连接请求
Socket socket = ss.accept();
//从socket管道中得到一个字节输入流对象
InputStream is = socket.getInputStream();
//把字节输入流包装成一个缓存字符输入流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg;
if ((msg = br.readLine()) != null) {
System.out.println("服务端接收客户端信息为:" + msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
启动服务端:测试输出(注意需要先启动服务端),服务端请求成功之后,等待客户端发送消息,如果客户端没有发送消息,服务端一直等待
启动客户端
小结:
- 在以上通信中,服务端会一直等待客户端的消息,如果客户端没有进行消息的发送,服务端将一直进入阻塞状态
- 同时服务端是按照行获取消息的,这意味着客户端也必须按照行进行消息的发送,否则服务端将进 入等待消息的阻塞状态
1.4、BIO编程现实多发多收
在上面的编码中,只能实现客户端发送消息,服务端接收消息, 并不能实现反复的收消息和反复的发消 息,我们只需要在客户端案例中,加上反复按照行发送消息的逻辑即可! 案例代码如下:
客户端代码
public class ClientTest01 {
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost",8889);
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
Scanner scanner = new Scanner(System.in);
while (true){
System.out.println("请输入:");
String s = scanner.nextLine();
ps.println(s);
ps.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端代码
public class ServerTest01 {
public static void main(String[] args) {
System.out.println("===服务端启动===");
try {
ServerSocket ss = new ServerSocket(8889);
Socket socket = ss.accept();
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg;
while ((msg = br.readLine()) != null){
System.out.println("服务端接收客户端信息为:" + msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
启动服务端
启动客户端
客户端(客户端一直处于运行中,可以持续输入)输入需要传送的信息
服务端接收客户端传输的数据
1.5、BIO模拟客户端服务端多对一
在上述的案例中,一个服务端只能接收一个客户端的通信请求,
那么如果服务端需要处理很多个客户端
的消 息通信请求应该如何处理呢
,此时我们就需要在服务端引入线程了,也就是说客户端每发起一个请求,服务端就创 建一个新的线程来处理这个客户端的请求,这样就实现了一个客户端一个线程的模型
![](https://img-blog.csdnimg.cn/7b941d4513664bfb91ae17507e762900.png)
客户端一
public class ClientTest02 {
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost",8889);
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
Scanner scanner = new Scanner(System.in);
while (true){
System.out.println("请输入:");
String s = scanner.nextLine();
ps.println(s);
ps.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端二
public class ClientTest03 {
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost",8889);
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
Scanner scanner = new Scanner(System.in);
while (true){
System.out.println("请输入:");
String s = scanner.nextLine();
ps.println(s);
ps.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端三
public class ClientTest04 {
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost",8889);
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
Scanner scanner = new Scanner(System.in);
while (true){
System.out.println("请输入:");
String s = scanner.nextLine();
ps.println(s);
ps.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端代码
public class ServerTest020304 {
public static void main(String[] args) {
try {
System.out.println("服务端开始-------------------------");
ServerSocket ss = new ServerSocket(8889);
while (true){
Socket accept = ss.accept();
new ServerThreadReader(accept).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//静态内部类
static class ServerThreadReader extends Thread{
public Socket socket;
public ServerThreadReader() {
}
public ServerThreadReader(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg;
while ((msg = br.readLine()) != null){
System.out.println("当前线程名称"+Thread.currentThread().getName()+",,,服务端接收到:" + msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
启动服务端
分别启动客户端1,2,3
分别输入对应的msg,效果如下
服务端结果
服务端开始-------------------------
当前线程名称Thread-0,,,服务端接收到:ClientTest0 测试客户端1
当前线程名称Thread-1,,,服务端接收到:ClientTest03 测试客户端2
java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(SocketInputStream.java:210)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at testDemo.src.coolstore.io.nio.ServerTest020304$ServerThreadReader.run(ServerTest020304.java:46)
当前线程名称Thread-3,,,服务端接收到:ClientTest04 测试客户端3
BIO总结
- 每个Socket接收到,都会创建一个线程,线程的竞争、切换上下文影响性能; ・
- 每个线程都会占用栈空间和CPU资源;
- 并不是每个socket都进行lO操作,无意义的线程处理;
- 客户端的并发访问增加时。服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出, 线程创建失败,最终导致进程宕机或者僵死,从而不能对外提供服务。