IO

中断和系统调用

BIO---Blocking IO(阻塞I/O模型)

阻塞I/O模型是常见的I/O模型,在读写数据时客户端会发生阻塞。阻塞I/O模型的工作流程为:在用户线程发出I/O请求之后,内核会检查数据是否就绪,此时用户线程一直阻塞等待内存数据就绪;在内存数据就绪后,内核将数据复制到用户线程中,并返回I/O执行结果到用户线程,此时用户线程将解除阻塞状态并开始处理数据。典型的阻塞I/O模型的例子为data = socket.read(),如果内核数据没有就绪,Socket线程就会一直阻塞在read()中等待内核数据就绪。

 

服务端代码示例

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket();
        serverSocket.bind(new InetSocketAddress("127.0.0.1", 8888));
        while (true) {
            Socket socket = serverSocket.accept();//阻塞方法
            new Thread(() -> {
                handle(socket);
            }).start();
        }
    }

    static void handle(Socket socket) {
        try {
            byte[] bytes = new byte[1024];
            int len = socket.getInputStream().read(bytes);
            System.out.println(new String(bytes, 0, len));

            socket.getOutputStream().write(bytes, 0, len);
            socket.getOutputStream().flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

客户端代码示例

public class Client {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("127.0.0.1", 8888);
        socket.getOutputStream().write("helloServer".getBytes());
        socket.getOutputStream().flush();

        byte[] bytes = new byte[1024];
        int len = socket.getInputStream().read(bytes);
        System.out.println(new String(bytes, 0, len));
        socket.close();
    }
}

原理:

通过strace -ff -o out java Server追踪系统调用

socket: 为通信创建端点并返回一个文件描述符。这个文件描述符3对应java中的ServerSocket

bind: 给这个返回的文件描述符绑定一个端口

listen: 监听这个端口

accept: 表示要在3这个ServerSocket上接收客户端

当一个客户端连接上来,就会有一个返回值文件描述符5 ,文件描述符5代表新连接进来的客户端

通过调用内核的系统调用clone,得到一个在操作系统中的一个轻量级的进程,clone返回线程的id

读取文件描述符5也即客户端中的数据,没有就阻塞  在主线程中accept,在自己的线程中recv

非阻塞IO(NIO)

服务端代码

public class NioServer {
    public static void main(String[] args) throws Exception {
        List<SocketChannel> clients = new LinkedList<>();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(9999));
        serverSocketChannel.configureBlocking(false);
        while (true) {
            Thread.sleep(1000);
            SocketChannel client = serverSocketChannel.accept();
            if (client == null) {
                //.........
            } else {
                client.configureBlocking(false);
                int port = client.socket().getPort();
                System.out.println("client...port = " + port);
                clients.add(client);
            }
            ByteBuffer buffer = ByteBuffer.allocateDirect(4096);
            for (SocketChannel c : clients) {
                int num = c.read(buffer);
                if (num > 0) {
                    buffer.flip();
                    byte[] bytes = new byte[buffer.limit()];
                    buffer.get(bytes);
                    System.out.println(new String(bytes) + " : " + client.socket().getPort());
                    buffer.clear();
                }
            }
        }
    }
}

原理:

socket提供了一个参数 SOCK_NONBLOCK

可以对服务端设置非阻塞,也可以对建立连接的客户端也建立非阻塞

没有客户端连接,不会阻塞,直接返回-1

当有一个客户端连接进来,会返回一个大于0的文件描述符,这个文件描述符表示客户端

多路复用器

1.多路复用器解决的是IO状态的问题,而不解决读写数据的问题

2.用更少的系统调用,一下询问所有的IO状态,而不是每一个IO独立的询问他的状态,减少了用户态到内核态切换的过

单线程版本示例

//单线程版本
public class MultiplexSingleThread {
    private ServerSocketChannel serverSocketChannel;
    private Selector selector;//linux中多路复用器(select poll  epoll kqueue)   nginx event{}
    private int port = 8888;

    public void init() {
        try {
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(port));
            serverSocketChannel.configureBlocking(false);


            //如果在epoll模型下,open==>  epoll create ->fd3  java中意思为得到一个selector
            selector = Selector.open();//select poll epoll  优先选择:epoll 但是可以通过-D修正
            // serverSocketChannel 约等于 listen状态的fd4
            /*
            register
            如果 select,poll :jvm里开辟一个数组  fd4 放进去
            epoll: epoll_ctl(fd3,ADD,fd4,EPOLLIN
             */
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void start() {
        init();
        while (true) {
            try {
                Set<SelectionKey> keys = selector.keys();
                System.out.println("keys.size() = " + keys.size());
                //调用多路复用器(select,poll or epoll(epoll_wait))
                /**
                 * select,poll  其实调用内核的  select(fd4) poll(fd4)
                 * epoll:  其实内核的epoll_wait(),参数可以带时间,没有时间,0:阻塞,有时间设置一个超时
                 * selector.wakeup()  结果返回0
                 */
                while (selector.select(500) > 0) {
                    //返回有状态的fd集合
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();
                    while (iterator.hasNext()) {
                        SelectionKey key = iterator.next();
                        iterator.remove();//set 不移除会重复循环处理
                        if (key.isAcceptable()) {
                            /**
                             * 接收一个新的连接并返回新连接的fd
                             * select,poll,因为他们没有内核空间,那么在jvm中保存这个fd
                             * epoll:我们希望通过epoll_ctl把新的客户端fd注册到内核空间
                             */
                            acceptHandler(key);
                        } else if (key.isReadable()) {
//                            readHandler(key);
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //处理接收
    private void acceptHandler(SelectionKey key) {
        try {
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
            SocketChannel client = serverSocketChannel.accept();
            client.configureBlocking(false);

            ByteBuffer buffer = ByteBuffer.allocateDirect(4096);
            client.register(selector, SelectionKey.OP_READ, buffer);
            System.out.println("新客户端----" + client.getRemoteAddress());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

多线程实现版本

public class MuiltiplexplexThreads {
    private ServerSocketChannel server;
    private Selector selector1;
    private Selector selector2;
    private Selector selector3;

    public void initServer() {
        try {
            server = ServerSocketChannel.open();
            server.configureBlocking(false);
            server.bind(new InetSocketAddress(8888));
            selector1 = Selector.open();
            selector2 = Selector.open();
            selector3 = Selector.open();
            server.register(selector1, SelectionKey.OP_ACCEPT);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        MuiltiplexplexThreads service = new MuiltiplexplexThreads();
        service.initServer();
        NioThread t1 = new NioThread(service.selector1, 2);
        NioThread t2 = new NioThread(service.selector2);
        NioThread t3 = new NioThread(service.selector3);
        t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
        t3.start();
        System.out.println("服务器启动了...");
        try {
            System.in.read();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class NioThread extends Thread {
    Selector selector;
    static int selectors;
    int id;
    volatile static BlockingQueue<SocketChannel>[] queues;
    static AtomicInteger idx = new AtomicInteger();

    NioThread(Selector selector, int n) {
        this.selector = selector;
        this.selectors = n;
        queues = new LinkedBlockingQueue[selectors];
        for (int i = 0; i < n; i++) {
            queues[i] = new LinkedBlockingQueue<>();
        }
        System.out.println("Boss 启动");
    }

    NioThread(Selector selector) {
        this.selector = selector;
        id = idx.getAndIncrement() % selectors;
        System.out.println("worker: " + id + " 启动");
    }

    @Override
    public void run() {
        try {
            while (true) {
                while (selector.select(10) > 0) {
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();
                    while (iterator.hasNext()) {
                        SelectionKey key = iterator.next();
                        iterator.remove();
                        if (key.isAcceptable()) {
                            acceptHandler(key);
                        } else if (key.isReadable()) {
                            readHandler(key);
                        }
                    }
                }
                if (!queues[id].isEmpty()) {
                    ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
                    SocketChannel client = queues[id].poll();
                    client.register(selector, SelectionKey.OP_READ, buffer);
                    System.out.println("----------------------------");
                    System.out.println("新客户端: " + client.socket().getPort() + " 分配到: " + id);
                    System.out.println("----------------------------");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void acceptHandler(SelectionKey key) {
        try {
            ServerSocketChannel channel = (ServerSocketChannel) key.channel();
            SocketChannel client = channel.accept();
            client.configureBlocking(false);
            int num = idx.getAndIncrement() % selectors;
            queues[num].add(client);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void readHandler(SelectionKey key) {
    }
}

select多路复用

原理


允许一个程序监控多个文件描述符,是一个同步的IO多路复用器,多个文件描述符复用了一个系统调用

select文件描述符最多1024个,有限制,上限只能建立1024个连接

epoll多路复用

原理

kafka

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值