I/O(INPUTOUTPUT),包括文件I/O和网络I/O。计算机世界中的速度蔑视:内存读取数据:纳秒级。千兆网卡读取数据:细微级。1微秒=1000纳秒,网卡比内存慢一千倍。磁盘读取数据:毫秒级。1毫秒=10万纳秒,硬盘比内存慢10万倍。CPU的一个时钟周期大概是1纳秒,内存离CPU比较近,其他的等不起。CPU处理数据的速度远远快于I/O准备数据的速度。任何编程语言都会遇到这种CPU处理速度和I/O速度不匹配的情况!网络编程中如何优化网络I/O:如何高效利用CPU进行网络数据处理???一、相关概念如何从操作系统层面理解网络I/O?计算机世界有一套自己定义的概念。如果不理解这些概念,就无法真正理解技术的设计思想和本质。所以在我看来,这些概念是理解技术和计算世界的基础。1.1同步与异步、阻塞与非阻塞了解网络I/O绕不开的话题:同步与异步、阻塞与非阻塞。以山治开水为例(山治的行为就像一个用户程序,而开水就像是内核提供的系统调用),这两组概念可以这样翻译成白话。同步/异步关注点是水烧开后我是否需要处理。阻塞/非阻塞关注的是在水烧开的这段时间里有没有做其他事情。1.1.1同步阻断点火后,傻傻地等到开水(阻断),再开水关火(同步)。1.1.2同步无堵点火后,看电视(无堵),不时检查水是否开,关水后关火(同步)。1.1.3异步阻塞按下开关后,傻等水开(阻塞),水开后自动断电(异步)。网络编程中不存在的模型。1.1.4异步和非阻塞开关按下后干什么(非阻塞),水烧开后自动断电(异步)。1.2内核空间、用户空间内核负责读写网络和文件数据。用户程序通过系统调用获取网络和文件数据。1.2.1内核态用户态程序必须进行系统调用来读写数据。通过系统调用接口,线程从用户态切换到内核态,内核读写数据后,再切换回来。进程或线程的不同空间状态。1.2.2线程切换用户态和内核态的切换比较耗时和资源(内存,CPU)优化建议:少切换。共享空间。1.3套接字——套接字只能用套接字编程。应用程序通过系统调用socket()建立连接、接收和发送数据(I/O)。SOCKET支持非阻塞,让应用程序可以非阻塞地调用,只有支持异步,应用程序才能异步调用1.4文件描述符-FD句柄网络编程需要知道FD???FD是什么鬼???Linux:万物皆文件,fd是对文件的引用。就像JAVA中的一切都是对象?程序中的操作就是对象的引用。JAVA中创建对象的数量是有内存限制的,FD的数量也是有限制的。Linux在处理文件和网络连接时,需要打开和关闭FD。每个进程都会有一个默认的FD:0标准输入stdin1标准输出stdout2错误输出stderr1.5连接建立后服务器处理网络请求的过程。等待数据就绪(CPU空闲)。将数据从内核复制到进程(CPU空闲)。如何优化呢?对于一次I/O访问(以read为例),数据会先被复制到操作系统内核的缓冲区中,然后再从操作系统内核的缓冲区中复制到应用程序的地址空间中。因此,当发生读取操作时,它会经历两个阶段:等待数据就绪。将数据从内核复制到进程(Copyingthedatafromthekerneltotheprocess)。也正是因为这两个阶段,在Linux系统升级迭代中出现了以下三种网络模式方案。2.IO模型介绍2.1BlockingI/O-BlockingI/O介绍:最原始的网络I/O模型。该过程将阻塞,直到数据复制完成。缺点:高并发时,server和client点对点连接,线程过多带来的问题:CPU资源浪费,上下文切换。内存成本呈几何级数增长,一个JVM线程的成本约为1MB。publicstaticvoidmain(String[]args)throwsIOException{ServerSocketss=newServerSocket();ss.bind(newInetSocketAddress(Constant.HOST,Constant.PORT));intidx=0;while(true){finalSocketsocket=ss.accept();//阻塞方法newThread(()->{handle(socket);},"thread["+idx+"]").start();}}staticvoidhandle(Socketsocket){byte[]bytes=newbyte[1024];try{StringserverMsg="serversss[Thread:"+Thread.currentThread().getName()+"]";socket.getOutputStream().write(serverMsg.getBytes());//阻塞方法socket.getOutputStream().flush();}catch(Exceptione){e.printStackTrace();}}2.2Non-blockingI/O-NonBlockingIO简介:处理重复的系统调用,并立即返回结果。缺点:当进程有1000fds时,代表用户进程轮询会导致系统调用内核1000次,用户态和内核态的切换会成倍增加成本。publicstaticvoidmain(String[]args)throwsIOException{ServerSocketChannelss=ServerSocketChannel.open();ss.bind(newInetSocketAddress(Constant.HOST,Constant.PORT));System.out.println("NIO服务器启动...");ss.configureBlocking(假);intidx=0;while(true){finalSocketChannelsocket=ss.accept();//阻塞方法newThread(()->{handle(socket);},"线程["+idx+"]").start();}}staticvoidhandle(SocketChannelsocket){try{socket.configureBlocking(false);ByteBufferbyteBuffer=ByteBuffer.allocate(1024);socket.read(byteBuffer);byteBuffer.flip();System.out.println("请请求:"+newString(byteBuffer.array()));Stringresp="服务器响应";byteBuffer.get(resp.getBytes());socket.write(byteBuffer);}catch(IOExceptione){e.printStackTrace();}}2.3I/O多路复用——IO多路复用简介:单个线程可以同时处理多个网络连接。内核负责轮询所有套接字。当套接字有数据到达时,它会通知用户进程。Multiplexing在Linux内核代码的迭代过程中依次支持三种调用,即SELECT、POLL和EPOLL,三种多路复用的网络I/O模型。下面将画图结合Java代码讲解。2.3.1I/O多路复用-select介绍:当一个连接请求到达时检查并处理。缺点:句柄上限——默认开启的fd有1024个限制。重复初始化——每次调用select()时,需要将fd集合从用户态复制到内核态,内核遍历。一一检查所有FD状态效率不高。server端的select就像一条布满socket的条带,client端的connection连接其中一个socket,建立一个channel,然后在channel中依次注册读写事件。记得在处理完ready,read,write事件的时候删除,不然下次可以处理。publicstaticvoidmain(String[]args)throwsIOException{ServerSocketChannelssc=ServerSocketChannel.open();//管道ServerSocketssc.socket().bind(newInetSocketAddress(Constant.HOST,Constant.PORT));ssc。configureBlocking(false);//设置非阻塞System.out.println("NIO单机启动,监听:"+ssc.getLocalAddress());选择器selector=Selector.open();ssc.register(selector,SelectionKey.OP_ACCEPT);//在建立的pipeline上,注册的事件就绪while(true){selector.select();Set
