网络编程
JAVA IO模型
什么是IO?
I/O就是计算机内存与外部设备之间拷贝数据的过程
I/O中的两组概念
注意:需要和线程中的同步线程和异步线程区分开,这里指的是同步IO/异步IO
- 同步IO/异步IO:数据就绪后需要自己去读就是同步,数据就绪后系统直接读好再回调给程序就是异步。
- 阻塞/非阻塞:没有数据传过来时,读会阻塞直到有数据;缓冲区满时,写操作也会阻塞。非阻塞遇到这些情况,都是直接返回。
常见IO模型
- 同步阻塞
- 同步非阻塞
- I/O多路复用
- 异步
JAVA BIO模型
什么是BIO?
BIO是blocking I/O的简称,它是同步阻塞IO,其相关的类和接口在java.io下
BIO模型简单来讲,就是服务端为每一个请求都分配一个线程进行处理,I/O操作都是基于流Stream的操作
代码实现
- 客户端
public class BioClient {
public static void main(String[] args) {
Socket socket = null;
BufferedReader inputStream = null;
BufferedWriter outputStream = null;
try {
// Socket客户端对象,绑定本机 8989端口
socket = new Socket("127.0.0.1", 8989);
inputStream = new BufferedReader(new InputStreamReader(socket.getInputStream()));
outputStream = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
System.out.println("开始向服务端发送数据!");
// \n 不能忘因为是 readLine
outputStream.write("hello sercer,i am client! \n");
outputStream.flush();
String line = inputStream.readLine();
System.out.println("接收服务端返回的数据:" + line);
} catch (IOException e) {
if (inputStream != null){
try {
inputStream.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
if (outputStream != null){
try {
outputStream.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
if (socket != null){
try {
socket.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
}
- 服务端
public class BioServer {
public static void main(String[] args) throws IOException {
// 基于ServerSocket
ServerSocket serverSocket = new ServerSocket(8989);
System.out.println("开开始服务端监听!");
// socket是一个客户端对象,accept是阻塞的
Socket socket = serverSocket.accept();
// 执行业务
new Thread(new ServerHandle(socket)).start();
}
static class ServerHandle implements Runnable {
private final Socket socket;
public ServerHandle(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
while (true){
// 接收来自客户端的数据
String line = bufferedReader.readLine();
System.out.println("接收到一个socket链接:" + socket);
System.out.println("客户端发来数据:"+ line);
// bus
// 向客户端发数据
bufferedWriter.write("hello client,i am server \n");
bufferedWriter.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
BIO 的弊端
- 线程开销
客户端的并发数与后端的线程数成1:1的比列,线程的创建、销毁是非常消耗系统资源的,随着并发量增大,服务端性能将显著下降,甚至会发生线程堆栈溢出等错误
- 线程阻塞
当连接创建后,如果该线程没有操作时,会进行阻塞操作,这样极大的浪费了服务器资源
JAVA NIO模型
什么是NIO?
NIO,称之为New IO 或是 non-block IO(非阻塞IO),这两种说法都可以,其实称之为非阻塞IO更恰当一些。
NIO的三大核心组件
- Buffer(缓冲区)
Buffer是一个对象,包含一些要写入或者写出的数据,体现了与原I/O的一个重要区别,在面向流的I/O中,数据读写是直接进入到Stream中,而在NIO中,所有数据都是用缓冲区处理的,读数据直接从缓冲区读,写数据直接写入缓冲区。
缓冲区的本质是一个数组,通常是一个字节数组(ByteBuffer),也可以使用其他类型,但缓冲区又不仅仅是一个数组,它还提供了对数据结构化访问以及维护读写位置等操作。
- Channel(通道)
Channel是一个通道,管道,网络数据通过Channel读取和写入,Channel和流Stream的不同之处在于
Channel是双向的,流只在一个方向上移动(InputStream/OutputStream),而Channel可以用于读写同时进行,即Channel是全双工的.
Channel继承结构
ServerSocketChannel和SocketChannel
客户端的SocketChannel向服务端ServerSocketChannel发起连接,服务端会生成一个SocketChannel 这个SocketChannel就是客户端的SocketChannel
-Selector(选择器/多路复用器)
Selector会不断轮询注册在其上的Channel,如果某个Channel上面发生读或者写事件,即该Channel处于就绪状态,他就会被Selector轮询出来,然后通过selectedKeys可以获取就绪Channel的集合,进行后续的I/O操作。
代码实现
- 客户端
public class NioClient {
public static void main(String[] args) {
try {
// 1、窗口客户端SocketChannel,绑定客户端本地地址(不选默认随机分配一个可用地址)
SocketChannel socketChannel = SocketChannel.open();
// 2、设置非阻塞模式
socketChannel.configureBlocking(false);
// 3、创建Selector
Selector selector = Selector.open();
// 4、创建Reactor模型
new Thread(new SingleReactorClient(socketChannel,selector)).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class SingleReactorClient implements Runnable {
private final SocketChannel socketChannel;
private final Selector selector;
public SingleReactorClient(SocketChannel socketChannel, Selector selector) {
this.socketChannel = socketChannel;
this.selector = selector;
}
@Override
public void run() {
try {
// 连接服务器
doConnect(socketChannel,selector);
} catch (IOException e) {
e.printStackTrace();
}
// 7、多路复用器执行多路复用程序
while (true){
try {
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
processKey(selectionKey);
iterator.remove();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void processKey(SelectionKey selectionKey) throws IOException {
if (selectionKey.isValid()){
// 8、根据准备就绪的事件类型分别处理
if (selectionKey.isConnectable()){
// 服务端可连接事件准备就绪
SocketChannel sc = (SocketChannel) selectionKey.channel();
if (sc.finishConnect()){
// 8.1、向selector注册可读事件(接收来自服务端的数据)
sc.register(selector,SelectionKey.OP_READ);
// 8.2、处理业务,向服务端发送数据
doService(sc);
}else{
// 连接失败,退出
System.exit(1);
}
}
// 读事件准备就绪
if (selectionKey.isReadable()){
// 9、读服务端返回的数据
SocketChannel sc = (SocketChannel) selectionKey.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
// 前面设置过socketChannel是非阻塞的,故要通过返回值判断读取的字节数
if (readBytes > 0){
readBuffer.flip();// 读写模式切换
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String msg = new String(bytes, "utf-8");
// 接收到服务端返回的数据后进行相关操作
doService(msg);
}else if (readBytes < 0){
// 值为-1表示链路通道已经关闭
selectionKey.cancel();
sc.close();
}else{
// 没读取到数据,忽略
}
}
}
}
private String doService(String msg) {
System.out.println("成功接收来自服务端响应的数据:"+msg);
return "";
}
private void doConnect(SocketChannel socketChannel, Selector selector) throws IOException {
System.out.println("客户端启动成功,开始连接服务器");
// 5、连接服务器
boolean connect = socketChannel.connect(new InetSocketAddress("127.0.0.1", 8989));
// 6、将socketChannel注册到selector并判断是否连接成功,连接成功监听读事件,没有继续监听连接事件
System.out.println("connect="+connect);
if (connect){
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("客户端成功连上服务器,准备发送数据");
// 开始进行业务处理,向服务端发送数据
doService(socketChannel);
}else{
socketChannel.register(selector,SelectionKey.OP_CONNECT);
}
}
private void doService(SocketChannel socketChannel) throws IOException {
System.out.println("客户端开始向服务端发送数据:");
// 向服务器发送数据
byte[] bytes = "hello nioServer,i am nioClient!".getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
socketChannel.write(writeBuffer);
}
}
- 服务端
public class NioServer {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
// 开启端口监听
serverSocketChannel.socket().bind(new InetSocketAddress(8989));
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 开启selector程序即可
new Thread(new SingleReactor(selector)).start();
}
static class SingleReactor implements Runnable {
private final Selector selector;
public SingleReactor(Selector selector) {
this.selector = selector;
}
@Override
public void run() {
// 开启多路复用程序
while (true){
// 检测
try {
int select = selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
iterator.remove();
processSelectedKey(selectionKey);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void processSelectedKey(SelectionKey selectionKey) throws IOException {
if (selectionKey.isValid()){
// 按照事件类型进行区分
if (selectionKey.isAcceptable()){
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector,SelectionKey.OP_READ);
}
}
// 有数据
if (selectionKey.isReadable()){
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
socketChannel.read(buffer);
// 从buffer中拿到数据
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String msg = new String(bytes, Charset.defaultCharset());
System.out.println("收到了来自客户端的数据:"+msg);
buffer.clear();
buffer.put("hello client,i am server".getBytes(StandardCharsets.UTF_8));
buffer.flip();
socketChannel.write(buffer);
}
}
}
}
JAVA AIO模型
什么是AIO
在NIO中,Selector多路复用器在做轮询时,如果没有事件发生,也会进行阻塞,如何优化?
AIO是asynchronous I/O的简称,是异步非阻塞IO,该异步IO是需要依赖于操作系统底层的异步IO实现。
AIO的基本流程
用户线程通过系统调用,告知kernel内核启动某个IO操作,用户线程返回。kernel内核在整个IO操作(包括数据准备、数据复制)完成后,通知用户程序,用户执行后续的业务操作。
AIO不足之处
- windows下是实现成熟,但很少作为百万级以上或者说高并发应用的服务器操作系统来使用。
- Linux系统下,异步IO模型在2.6版本才引入,目前并不完善。所以Linux下,实现高并发编程时都是以NIO多路复用模型模式为主。