BIO的缺点和存在的问题
因为BIO是阻塞的,每个客户端的连接都需要一个线程来accept(),不用多线程的话会导致在处理完一个响应之前无法处理其他请求。因此连接太多的时候需要很多的线程,线程间的调度切换消耗了大量的资源,线程间切换的消耗可能大于处理的消耗。
NIO的优点
accept()不会阻塞,没有连接直接返回null,可以用一个线程接收所有客户端的连接,然后可以交由其他线程或线程池处理。
LinkedList<SocketChannel> clents = new LinkedList<>();
ServerSocketChannel sc = ServerSocketChannel.open();
sc.bind(new InetSocketAddress(9090));
sc.configureBlocking(false);//设置为非阻塞
//可以用一个线程接收所有客户端的连接,然后交由其他线程或线程池处理
while (true) {
Thread.sleep(1000);
SocketChannel clent = sc.accept();//不会阻塞
if (clent!=null){
clent.configureBlocking(false);
clents.add(clent);
}
ByteBuffer buffer = ByteBuffer.allocateDirect(4096);
//遍历所有客户端连接
for (SocketChannel c:clents ) {
int read = c.read(buffer);
if (read>0){
buffer.flip();
byte[] a = new byte[buffer.limit()];
buffer.get(a);
String b = new String(a);
buffer.clear();
}
select, poll和epoll
在上面的NIO中,有连接之后自己遍历获取发生事件的客户端,自己用户态遍历切换到核心态看是否有事件,10000次的话需要核心态切换10000次,效率低,于是有了多路复用(select),把所有客户端连接传递给内核,内核遍历返回可读可写的,减少了系统调用的次数,也就是可以把10000个客户端的连接传给操作系统,返回所有连接中有事件发生的连接,程序再自己读取事件(程序自己读取IO),获取状态原来10000个连接需要10000次系统调用,select一次系统调用即可获取状态。
while (true){
Set<SelectionKey> keys = selector.keys();
while (selector.select(500)>0){//有几个可以读写
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
iterator.remove();//移除避免重复处理
//处理各种事件
if (key.isAcceptable()){
}else if (key.isReadable()){
}else if (key.isWritable()){
}
}
}
}
select和poll每次把所有的fd(文件描述符,也就是所有的连接)传递给操作系统获取状态,而epoll在内核开辟了空间缓存fd,每次只需要传递新的fd即可。
注意:
- 多路复用器只能给你状态,程序需要自己读取IO,如果程序自己读取IO,不管是BIO,NIO,多路复用,都是同步IO模型
- 用select,poll还是epoll由操作系统决定。