epoll可以用单进程单线程实现高并发
首先我们可以实现单进程单线程实现高并发(模拟非阻塞IO请求)
服务端
//服务端
public class BlockNIOServer {public static void main(String[] args) throws IOException, InterruptedException {//获取通道ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();//切换非阻塞模式serverSocketChannel.configureBlocking(false);//绑定端口serverSocketChannel.bind(new InetSocketAddress(8090));//获取选择器Selector selector = Selector.open();//将该通道注册到select中,让select监听该通道的连接是否准备就绪serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);Iterator<SelectionKey> iterator = null;//通过选择器轮询获取已经准备就绪的事件while (selector.select()>0){iterator = selector.selectedKeys().iterator();while (iterator.hasNext()){SelectionKey selectionKey = iterator.next();//如果获取的是准备连接就绪的事件if (selectionKey.isAcceptable()){System.out.println("有客户端已经准备好连接了....");//开始接受连接客户端SocketChannel accept = serverSocketChannel.accept();//切换非阻塞模式accept.configureBlocking(false);//将通道注册到selector中,让select监听该通道的数据是否准备就绪accept.register(selector,SelectionKey.OP_READ);}else if (selectionKey.isReadable()){SocketChannel socketChannel = (SocketChannel) selectionKey.channel();Random random = new Random();int i = random.nextInt(100);String path = "C:\\Users\\zhengyan\\Desktop\\test1\\"+i+".txt";FileChannel fileChannel = FileChannel.open(Paths.get(path), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);ByteBuffer byteBuffer = ByteBuffer.allocate(1024);while (socketChannel.read(byteBuffer)!=-1){byteBuffer.flip();fileChannel.write(byteBuffer);byteBuffer.clear();}byteBuffer.put("数据已经接受完毕...".getBytes());byteBuffer.flip();socketChannel.write(byteBuffer);fileChannel.close();socketChannel.close();System.out.println("写入数据成功....");}//取消选择键iterator.remove();}}}
}
客户端
//客户端
public class BlockNIOClient {public static void main(String[] args) throws IOException, InterruptedException {//获取通道SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8090));FileChannel fileChannel = FileChannel.open(Paths.get("C:\\Users\\zhengyan\\Desktop\\test1\\x.txt"), StandardOpenOption.READ);//System.out.println("模拟10秒之后发送数据...");//可以开启两个客户端,一个睡10秒发送数据(先请求),一个不用睡眠(后请求),发现,必须等第一个用户处理完毕之后,第二个用户才可以被处理//Thread.sleep(20000);//分配缓冲区大小ByteBuffer byteBuffer = ByteBuffer.allocate(1024);//读取本地文件发送到服务器while (fileChannel.read(byteBuffer)!=-1){byteBuffer.flip();socketChannel.write(byteBuffer);byteBuffer.clear();}//告诉服务器,我的数据已经发送完毕socketChannel.shutdownOutput();//接受服务器返回来的消息StringBuffer stringBuffer = new StringBuffer();int len =-1;while ((len=socketChannel.read(byteBuffer))!=-1){byteBuffer.flip();stringBuffer.append(new String(byteBuffer.array(),0,len));byteBuffer.clear();}System.out.println(stringBuffer);socketChannel.close();fileChannel.close();}
}
假如有三个连接到来了,一个socker通道监听连接是否到来(白色方格),三个socker通道是因为连接到来了,被注册到select中监听数据是否到来(黄色方格),此时select不断的遍历,首先select将其中的一个socker通道(在linux中是fd(int)文件描述符)复制一份到操作系统(这一步是性能的瓶颈所在,让操作提供来看看我这个通道有没有数据到来,或者连接请求的到来),此时操作系统(内核)还会执行别的应用进程,什么执行我们这个检测操作是由操作系统决定(又浪费了一部分时间)。然后一步一步将select选择器中的所有的socket通道全部遍历(select选择器中的socker通道数目越多,性能越差),有的socker通道不活跃,也被检测了,就非常损耗性能。
图中绿色的是select选择器(维护了socker通道),处于应用层,放在用户空间
epoll
epoll有一段特殊的内存空间(操作系统和应用程序共用)
图中绿色的是epoll选择器(维护了socker通道,实现方式是红黑树和链表),处于应用程序和内核共享空间
epoll的第一个优点:不需要额外的复制操作
epoll的第一个优点:采用事件通知,取代了之前的轮询(select)