概述
前面几篇文章我们对Java的IO体系做了一个大致的介绍,从本文开始我们将对BIO、NIO、SELECTOR、EPOLL、Netty等携带例子做更深入的讲解。
如需持续了解请关注后随时查看。
解读
阻塞IO模型:最传统的一种IO模型,即在读写数据过程中会发生阻塞现象。当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才接触block状态。典型的阻塞IO模型的例子为data=socket.read();如果数据没有就绪,就会一直阻塞在read方法。
全图(下面有分解图)
图解
释义
1.不管是java程序,还是c语言程序,如果想作为服务端提供socket连接,那么他必须经过以下几个步骤:
①调用内核的socket接口,得到文件描述符–fd5 ;
②调用内核的bind接口,绑定8090端口;
③调用内核的listen接口,监听fd5的状态;
④调用内核的accept接口,等待客户端调用fd5,Accept进入阻塞状态,直到有客户端进入才可以中断阻塞状态。
⑤当有客户端进入后,系统为客户端生成一个文件描述符fd6,然后接入到socket服务端,accept结束阻塞状态。fd5=fd6开始进行通信。
2.以上的描述都是在只有一个客户端的情况下进行的,但是在实际应用过程中,会有很多个客户端进入,如果只有一个线程进行处理,那么其他的客户端只有全部阻塞。因此需要在accept获取到客户端后新建一个线程(clone系统调用)去处理客户端发送的数据。read(读取)或者recvfrom(接收)等系统调用都需要对线程进行阻塞。
3.以上过程中为了防止主线程因为read(读取)或者recvfrom(接收)数据时阻塞,导致其他客户端无法连接,因此在主线程使用accept接收到一个客户端后便新建一个线程去处理客户端的数据。新建的线程也可能因为read(读取)或者recvfrom(接收)数据也进入阻塞状态。这种处理过程中每个线程对应一个客户端client。
4.以上BIO模型的问题->
①.如果有一万个客户端连接服务端,那么服务端就需要创建一万个线程去处理请求。创建线程需要系统调用是很消耗资源的。消耗内存资源,而且cpu调度过程中线程的上下文切换也需要时间。以上问题的根本原因就是当客户端连接进入服务端后,服务端是阻塞状态的,干不了其他事情。
②.如果要解决以上问题则需要解决socket的阻塞问题,这样就形成了IO进化过程之NIO。
下一篇文章我们将对NIO做详细的介绍
以下我们将通过两个示例对JavaBIO体系做一个简单介绍。
示例代码一为单线程的服务端;
实例代码二为多线程处理数据的服务端;
示例代码一-单线程服务端:
import org.apache.commons.lang.StringUtils;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Created by Bruce on 2020/9/16
* 网络IO之BIO-服务端
* BIO-写法
**/
public class SocketServerBIO {
public static void main(String[] args) throws IOException {
/**
* 创建serverSocket 并绑定 8080端口
* 在系统内核中要经过以下几步来完成Java语言的
“new ServerSocket(8080)”。
* 1. socket() = fd6 获取文件描述符;
* 2. bind(fd6,8080) 文件描述符与端口绑定;
* 3. listen(fd6) 监听文件描述符
*/
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("step1 : new ServerSocket(8080) ");
/**
* 接受socket客户端请求连接
* accept命令是一个阻塞命令,
只有当有客户端连接进入时候,
才会结束阻塞状态。
* 当有客户端连接进入时候 accept(fd6) ==>fd7
与客户端创建的随机端口号和随机文件描述符连接
*/
Socket socket = serverSocket.accept();//阻塞状态1
System.out.println("acceptSocketClient:" + socket.getPort());
/**
* 获取阻塞连接输出 输入流
*/
InputStream inputStream = socket.getInputStream();
/**
* 创建出入缓冲区
*/
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(inputStream));
String text = null;
/**
* read命令也是一个阻塞命令,等待客户端数据传入。
* 客户端输入传入后结束阻塞状态。
* * read(fd7) 读取客户端写入到文件描述符7中的文件。
*/
while (StringUtils.isNotEmpty((text = bufferedReader.readLine() ))){//阻塞状态2
System.out.println("acceptSocketClient:" + socket.getPort() + "---" + text);
}
while (true){
//暂时死循环
}
}
}
示例代码一客户端(仅示例,具体测试时使用nc命令链接)
import org.apache.commons.lang.StringUtils;
import java.io.*;
import java.net.Socket;
/**
* Created by Bruce on 2020/9/16
* * 网络IO之BIO-客户端
* * BIO-写法
**/
public class SocketClientBIO {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",8080);
OutputStream outputStream = socket.getOutputStream(); PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.write("server端口你好,我是client");
printWriter.flush(); //关闭资源
printWriter.close();
outputStream.close();
// InputStream inputStream = socket.getInputStream();
// BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
// String text = null;
// while (StringUtils.isNotEmpty(text = bufferedReader.readLine())){
// System.out.println(text);
// }
socket.close();
}
}
示例代码一-单线程打印测试
在linux环境或者windows环境下使用nc命令链接服务端,查看服务端打印过程。
具体linux系统或者windows系统如何安装nc命令,请从网络搜索或查看目录文档 ‘网络IO涉及到的-linux指令.docx’。
五、Centos-Linux安装nc
六、windows环境下netcat的安装及使用
1. nc客户端打印
Windows-nc命令打印
C:\Users\Administrator>nc 127.0.0.1 8080
123456789aaa
sss
ddd
fff
2. 服务端打印
step1 : new ServerSocket(8080)
acceptSocketClient:45021
acceptSocketClient:45021---123
acceptSocketClient:45021---456
acceptSocketClient:45021---789
acceptSocketClient:45021---aaa
acceptSocketClient:45021---sss
acceptSocketClient:45021---ddd
acceptSocketClient:45021---fff
示例代码二-多线程服务端
import org.apache.commons.lang.StringUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Created by Bruce on 2020/9/16
* 网络IO之BIO-服务端
* * BIO-写法
*
* 多线程
**/
public class SocketServerThreadBIO {
public static void main(String[] args) throws IOException {
{ /**
* 创建serverSocket 并绑定 8080端口
* 在系统内核中要经过以下几步来完成Java语言的
“new ServerSocket(8080)”。
* 1. socket() = fd6 获取文件描述符;
* 2. bind(fd6,8080) 文件描述符与端口绑定;
* 3. listen(fd6) 监听文件描述符
*/
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("step1 : new ServerSocket(8080) ");
while (true){
/**
* 接受socket客户端请求连接
*
* accept命令是一个阻塞命令,只有当有客户端连接进入时候,
才会结束阻塞状态。
* 当有客户端连接进入时候 accept(fd6) ==>fd7
与客户端创建的随机端口号和随机文件描述符连接
*/
Socket socket = serverSocket.accept();//阻塞状态1
System.out.println("acceptSocketClient:" + socket.getPort());
/**
* 每次进入一个客户端则创建一个线程去处理数据
* 因为线程的创建是非常消耗资源的
* 而且线程创建数量也是有限的,在C10K问题下,处理更多的TCP连接请求。
*/
new Thread(new Runnable() {
@Override
public void run() {
/**
* 获取阻塞连接输出 输入流
*/
InputStream inputStream = null;
try {
inputStream = socket.getInputStream();
/**
* 创建出入缓冲区
*/
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(inputStream));
String text = null;
/**
* read命令也是一个阻塞命令,等待客户端数据传入。
* 客户端输入传入后结束阻塞状态。
* * read(fd7) 读取客户端写入到文件描述符7中的文件。
*/
while (StringUtils.isNotEmpty((text = bufferedReader.readLine() ))){//阻塞状态2
System.out.println("acceptSocketClient:" + socket.getPort() + "---" + text);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
}
示例代码二-多线程服务端打印测试:
在linux环境或者windows环境下使用nc命令链接服务端,查看服务端打印过程。
具体linux系统或者windows系统如何安装nc命令,请从网络搜索或查看目录文档 ‘网络IO涉及到的-linux指令.docx’。
五、Centos-Linux安装nc
六、windows环境下netcat的安装及使用
1. nc客户端打印1(Windows-nc命令打印)
C:\Users\Administrator>nc 127.0.0.1 8080
456
123456789
2. nc客户端打印2(Windows-nc命令打印)
C:\Users\Administrator>nc 127.0.0.1 8080
123
789
77777777
3. 服务端打印
step1 : new ServerSocket(8080)
acceptSocketClient:45614
acceptSocketClient:45619
acceptSocketClient:45619---123
acceptSocketClient:45614---456
acceptSocketClient:45619---789
acceptSocketClient:45614---123456789
acceptSocketClient:45619---77777777
往期JavaIO文章:
一、C10K问题经典问答
二、java.nio.ByteBuffer用法小结
三、Channel 通道
四、Selector选择器
五、Centos-Linux安装nc
六、windows环境下netcat的安装及使用
七、IDEA的maven项目的netty包的导入(其他jar同)
八、JAVA IO/NIO
九、网络IO原理-创建ServerSocket的过程
十、网络IO原理-彻底弄懂IO
十一、JAVA中ServerSocket调用Linux系统内核
整体JavaIO体系文章概览