Java|从操作系统层面分析 Java IO 演进之路( 二 )


clientBytes = new byte[buffer.limit()
; //从缓冲区 读取到内存中 buffer.get(clientBytes); System.out.println(c.socket().getPort() + \":\" + new String(clientBytes)); //清空缓冲区 buffer.clear();).start();public static void main(String[
args) throws IOException { //new socket开启监听 ServerSocketChannel socketChannel = ServerSocketChannel.open(); socketChannel.bind(new InetSocketAddress(9090)); //设置阻塞接受客户端连接 socketChannel.configureBlocking(true); //开始client处理线程 startClientChannelHandleThread(); while (true) { //接受客户端连接; 非阻塞 , 无客户端返回null(操作系统返回-1) SocketChannel client = socketChannel.accept(); if (client == null) { //System.out.println(\"no client\");else { //设置读非阻塞 client.configureBlocking(false); int port = client.socket().getPort(); System.out.println(\"client port :\" + port); clients.add(client);1 发生的系统调用
主线程
socket(AF_INET SOCK_STREAM IPPROTO_IP) = 4bind(4 {sa_family=AF_INET sin_port=htons(9090) sin_addr=inet_addr(\"0.0.0.0\") 16) = 0listen(4 50) = 0fcntl(4 F_SETFL O_RDWR|O_NONBLOCK) = 0accept(4 0x7fe26414e680 0x7fe26c376710) = -1 EAGAIN (Resource temporarily unavailable) 有连接后 , 子线程
read(6 0x7f3f415b1c50 4096) = -1 EAGAIN (Resource temporarily unavailable)read(6 0x7f3f415b1c50 4096) = -1 EAGAIN (Resource temporarily unavailable)... 资源使用情况:

2 优缺点
优点
线程数大大减少 。
缺点
需要程序自己扫描 每个连接read , 需要 O(n)时间复杂度系统调用 (此时可能只有一个连接发送了数据) , 高频系统调用(导致CPU 用户态内核态切换)高 。 导致CPU消耗很高 。
三 多路复用器(select、poll、epoll) 改进:不需要用户扫描所有连接 , 由kernel 给出哪些连接有数据 , 然后应用从有数据的连接读取数据 。
1 epoll
import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.util.Iterator;import java.util.LinkedList;import java.util.Set;/** * 多路复用socket * * @author xiangyong.ding * @version $Id: MultiplexingSocket.java v 0.1 2020年08月09日 12:19 xiangyong.ding Exp $ */public class MultiplexingSocket { static ByteBuffer buffer = ByteBuffer.allocateDirect(4096); public static void main(String[
args) throws Exception { LinkedListSocketChannelclients = new LinkedList(); //1.启动server //new socket开启监听 ServerSocketChannel socketChannel = ServerSocketChannel.open(); socketChannel.bind(new InetSocketAddress(9090)); //设置非阻塞 , 接受客户端 socketChannel.configureBlocking(false); //多路复用器(JDK包装的代理 , select /poll/epoll/kqueue) Selector selector = Selector.open(); //java自动代理 , 默认为epoll //Selector selector = PollSelectorProvider.provider().openSelector();//指定为poll //将服务端socket 注册到 多路复用器 socketChannel.register(selector SelectionKey.OP_ACCEPT); //2. 轮训多路复用器 // 先询问有没有连接如果有则返回数量以及对应的对象(fd) while (selector.select()0) { System.out.println(); SetSelectionKeyselectionKeys = selector.selectedKeys(); IteratorSelectionKeyiter = selectionKeys.iterator(); while (iter.hasNext()) { SelectionKey key = iter.next(); iter.remove(); //2.1 处理新的连接 if (key.isAcceptable()) { //接受客户端连接; 非阻塞 , 无客户端返回null(操作系统返回-1) SocketChannel client = socketChannel.accept(); //设置读非阻塞 client.configureBlocking(false); //同样 , 把client也注册到selector client.register(selector SelectionKey.OP_READ); System.out.println(\"new client : \" + client.getRemoteAddress());//2.2 处理读取数据 else if (key.isReadable()) { readDataFromSocket(key);protected static void readDataFromSocket(SelectionKey key) throws Exception { SocketChannel socketChannel = (SocketChannel) key.channel(); // 非阻塞0 表示读取到的字节数量 0或-1表示未读取到或读取异常 // 请注意:这个例子降低复杂度 , 不考虑报文大于buffer size的情况 int num = socketChannel.read(buffer); if (num0) { buffer.flip(); byte[