Netty4.x --- 基础篇1《I/O模型》

1 篇文章 0 订阅

1、I/O模型

Linux系统中所有的外部设备都是一个可操作性的文件,一个socket的读写对应相应的文件描述符socketfd。I/O模型在UNIX系统中分为5种:阻塞I/O模型、非阻塞I/O模型、I/O复用模型、型号驱动I/O模型、异步I/O。

由于操作系统中权限不同,并且系统的资源是有限的,如果资源操作过于频繁必然会消耗过多的资源,甚至会造成资源访问的冲突,所以,从宏观上来看分为内核态与用户态。

内核态:控制计算机的硬件资源,CPU资源、存储、I/O等;

用户态:只负责管理自己的应用进程,如果要调用硬件那么需要通过系统的库函数调用;

Unix提供的5中I/O模型

1.1、阻塞IO(所有文件缺省都是阻塞I/O)

所有文件操作都是阻塞的,用户态线程调用recvfrom,recvfrom回去执行内核态调用知道数据包被复制到进程缓冲区才返回,调用recvfrom的整个过程都是阻塞。

1.2、非阻塞IO

用户态线程调用recvfrom时会不断轮询询问是否有数据到来,如果没有则返回EWOULDBLOCK错误,当有数据时,用户态线程也会阻塞知道数据拷贝到进程缓冲区完成再返回。

1.3、I/O复用模型

Linux提供了select/poll,epoll系统调用,select/poll是进程将多个socketfd文件交给select/poll系统调用,select/poll侦测fd是否处于就绪状态,是顺序扫描fd文件;epoll是基于事件驱动方式,相比于顺序扫描性能更高,当有fd就绪时,立刻rollback。

Java NIO的多路复用器就是基于epoll的多路复用技术实现的。

着重说一下I/O多路复用

对比select/poll与epoll

1.3.1、socketFD

select多路复用:由于是顺序循环监听fd,所以单进程打开的fd是有限制的,默认是1024,操作系统在某一时刻只会有少数的socket是活跃的,但是select/poll每次却要现行扫描全部的fd集合,这样效率会线性下降;

epoll多路复用:socketFD不受限制(受限于操作系统的最大文件数,是远远大于1024的),epoll只会对活跃的socket进行操作,但是假如所有socket都处于活跃状态,那么epoll和select的效率差不多;

epoll具体实现?

每个fd上都有callback函数实现,而只有活跃的socket才会去调用callback,其他idle的socket不会,所以epoll实现了一个伪AIO。

1.3.2、mmap内存加速

内核在把FD消息通知给用户态时,为了避免不必要的内存复制,epoll是通过内核与用户态mmap同一块内存实现的,mmap是将文件与进程的虚拟空间进行映射。

1.4、信号驱动I/O模型

进程调用sigaction(一个信号处理函数),用户态线程会立即返回不会阻塞,当数据准备就绪时,为该进程生成一个SIGIO信号,通过这个信号通知进程来调用recvfrom读取数据。

1.5、异步I/O

进程告知内核一个io_read后立即返回,内核态准备完数据后并且将数据赋值到进程缓冲区,通知进程,整个过程用户态不阻塞。

2、BIO

网络编程基本都是client/server模型,client通过连接操作与服务端监听端口建立连接,三次握手建立连接,连接成功后双方通过socket通信。

Acceptor线程负责监听客户端连接,接收到连接之后为每个client创建一个新线程,线程处理完后返回输出流给客户端,销毁线程。

 BIO的的client的1个connection对应server的1个线程,众所周知线程的开销是昂贵的,在并发量高的操作系统中,BIO的缺陷也突出的很明显,资源消耗非常高。

代码模拟:client向server请求当前时间,server创建一个线程处理请求输出当前时间,main线程充当Acceptor来接收connection,MyBioHandler线程用来真正的处理数据。

package org.Netty;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

// 模拟BIO服务器端
public class MyBioServer {
    public static void main(String[] args) {
        Socket socket = null;
        try (ServerSocket serverSocket = new ServerSocket(8080);) {
            while (true) {
                socket = serverSocket.accept();
                new Thread(new MyBioHandler(socket)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                socket = null;
            }
        }
    }
}
package org.Netty;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

// 模拟BIO客户端
public class MyBioClient {
    public static void main(String[] args) {
        try(
                Socket socket = new Socket("127.0.0.1", 8080);
                BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                PrintWriter out = new PrintWriter(socket.getOutputStream(),true)
                ) {
            // 发送 "Query time" 请求
            out.println("Query time");
            String result = in.readLine();
            System.out.println("server return : " + result);

        }catch (IOException e) {
            e.printStackTrace();

        }

    }
}
package org.Netty;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Date;

public class MyBioHandler implements Runnable{
    private Socket socket;
    public MyBioHandler(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try (BufferedReader in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
             PrintWriter out = new PrintWriter(this.socket.getOutputStream(),true);){

            while(true) {
                String read = in.readLine();
                if (read.equals("Query time")) {
                    // 返回server时间
                    out.println(new Date(System.currentTimeMillis()).toString());
                } else {
                    out.println("none");
                }
            }
        }catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

BIO模型在少量客户端连接场景下是比较容易实现的,代码实现起来简单容易理解,一旦请求数增加则会消耗大量cpu、内存的系统资源,无法满足高性能需求。

3、伪异步NIO

将socket封装为task任务,交给server的线程池去执行,而不是一个socket对应新建的一个线程

代码模拟:client向server请求当前时间,server创建将socket封装为task交给线程池去处理

package org.Netty;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

// 伪装异步IO,将接收到的connection调用连接池,而不是去new Thread
public class MyPseudoIOServer {
    public static MyPseudoIOTheadPool executor = new MyPseudoIOTheadPool(10,100);
    public static void main(String[] args) {
        Socket socket = null;

        try(ServerSocket server = new ServerSocket(8080)) {
            while(true) {
                socket = server.accept();
                executor.execute(new TimeServerHandler(socket));
            }
        }catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (null != socket) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                socket = null;
            }
        }
    }
}

package org.Netty;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

// 伪装异步IO,线程池
public class MyPseudoIOTheadPool {
    private ExecutorService executor;
    public MyPseudoIOTheadPool(int maxSize,int queueSize) {
        executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),maxSize,120L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(queueSize));
    }
    public void execute(Runnable task) {
        executor.execute(task);
    }
}

伪异步IO弊端:

InputStream:

输入流的read方法会一直阻塞,直到输入数据可用、检测到文件结尾或引发异常为止,当server端的socket在接收数据时,假如发送方需要60s才能将数据发送完毕,那么读取一方的I/O线程也将会被阻塞60s

outputStream:

输出流的writer方法会一直阻塞,知道所有要发送的字节全部写入完毕,或者发生异常,本质与输入流的read一样

不管是输入流还是输出流,假如生产环境网络性能并不是很好,那么这里阻塞时间可能会很长,所以说伪异步IO只是优化了BIO的线程模型,本质上I/O阻塞问题并没有解决。

参考

《Netty权威指南》

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zyc_2754

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值