io-nio-selector-epoll

c10k问题

2000年左右提出的,BIO模型下的10K个socket处理客户端和服务端数据传输慢的问题。
单线程模拟10k个客户端

package io.bio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;
import java.util.List;

/**
 * Author: ljf
 * CreatedAt: 2021/3/31 下午2:24
 */
public class Ck10Client {
    
    
    public static void main(String[] args) {
    
    
        List<SocketChannel> clients = new LinkedList<>();
        InetSocketAddress serverAddr = new InetSocketAddress("192.168.172.3",9090);

        for(int i = 10000;i<65000;i++){
    
    
            try {
    
    
                SocketChannel client1 = SocketChannel.open();

                /**
                 * linux 中看到的是 :
                 * client1 ... port 10002
                 * client2 ... port 10002
                 */

                client1.bind(new InetSocketAddress("192.168.172.1",i));
                client1.connect(serverAddr);
                clients.add(client1);

                SocketChannel client2 = SocketChannel.open();
                client2.bind(new InetSocketAddress("192.168.8.103",i));
                client2.connect(serverAddr);
                clients.add(client2);

            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
        System.out.println("clients :" + clients.size());

        try {
    
    
            System.in.read();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }
}

服务端和客户端通信,在内核里会有两个socket,一是服务器内核listen客户端的socket,二是客户端进来后相互之间通信的socket(先netstat -natp 看java的pid,然后lsof -p pid 就可以看到了)
1.BIO模型

package io.bio;

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

/**
 * bio 慢的原因:
 * 1.accept阻塞, 后 new thread 那一步会发生系统调用 clone,这里用户态系统太切换
 * 2.客户端连进来后的io流也是阻塞的。
 * Author: ljf
 * CreatedAt: 2021/3/31 下午1:38
 */
public class SocketBIO {
    
    

    public static void main(String[] args) {
    
    
        try {
    
    
            ServerSocket server = new ServerSocket(9090, 5);
            System.out.println("step1: new ServerSocket(9090,5)");
            while (true) {
    
    
                Socket client = server.accept();
                System.out.println("step2:client \t" + client.getPort());

                new Thread(new Runnable() {
    
    
                    @Override
                    public void run() {
    
    
                        InputStream inputStream = null;
                        try {
    
    
                            inputStream = client.getInputStream();
                            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
                            String s = reader.readLine();
                            while (true) {
    
    
                                if (s != null) {
    
    
                                    System.out.println(s);
                                } else {
    
    
                                    inputStream.close();
                                    break;
                                }
                            }
                        } catch (IOException e) {
    
    
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }
}

阻塞发生在服务端accept客户端和服务端等待客户端数据(内核RECV(6)两处。所以慢。

2.NIO模型

package io.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;
import java.util.List;

/**
 * nio 的n 有两个意思:
 * 1.是accept(5 的时候 是 NON_BLOCKINg,和 RECV(6 的时候非阻塞
 * 2.java 的new io ,即新io的意思
 * <p>
 * Author: ljf
 * CreatedAt: 2021/3/31 下午3:16
 */
public class SocketNIO {
    
    

    public static void main(String[] args) {
    
    

        List<SocketChannel> clients = new LinkedList<>();
        try {
    
    
            ServerSocketChannel ss = ServerSocketChannel.open();// 服务端开启监听,接收客户端
            ss.bind(new InetSocketAddress(9090)); // 绑定本地的9090端口
            ss.configureBlocking(false); // 重点,这里是用非阻塞的方式接收客户端

            while (true) {
    
    
                // 接收客户端连接
//                Thread.sleep(1000);
                // accept 调用了内核的accept,没有客户端连进来返回值,在BIO的时候一直卡着,NIO不看着,返回-1,java 返回null
                // 有客户端连进来,accept 返回这个客户端的FD5,client Object
                // NONBLOCKING 就是代码能往下走了,但是往下走的情况要根据客户端是否连进来有不同
                SocketChannel client = ss.accept();
                if (client == null) {
    
    
                    System.out.println("null ...");
                } else {
    
    
                    client.configureBlocking(false); // 重点,socket(服务端的listen
                    // socket<连接请求三次握手后,往这里扔,我去通过accept得到连接的socket>,连接socket<往后的数据读写使用的>)
                    int port = client.socket().getPort();
                    System.out.println("client port : " + port);
                    clients.add(client);
                }

                ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);// 这种是直接在服务器内存分配,还有一种是allocate(int capacity)
// (这种是在jvm里分配,即堆内存)第一种会分配比较慢,第二种是发生系统内存到堆内存的复制,也不一定快,具体看运行情况

                // 遍历已经连进来的客户端能不能读写数据
                for (SocketChannel s : clients) {
    
    
                    int num = s.read(byteBuffer); // >0 -1 0 不会阻塞
                    if (num > 0) {
    
    
                        byteBuffer.flip();
                        byte[] bytes = new byte[byteBuffer.limit()];
                        byteBuffer.get(bytes);

                        String b = new String(bytes);
                        System.out.println(client.socket().getPort() + " : " + b);
                        byteBuffer.clear();
                    }
                }
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }
}

道理在代码注释里有了,目前这个代码写法有一个毛病是clients随着死循环的增多,遍历会慢,所以会越跑越慢,最后要么报文件描述符不够用错误。

猜你喜欢

转载自blog.csdn.net/weixin_39370859/article/details/115357927