贵阳网站备案在哪里,品牌网站建设 蝌蚪5小,关键词优化的作用,织梦网站装修公司源码BIO#xff1a;同步阻塞IO#xff0c;客户端一个连接请求#xff08;socket#xff09;对应一个线程。阻塞体现在: 程序在执行I/O操作时会阻塞当前线程#xff0c;直到I/O操作完成。在线程空闲的时候也无法释放用于别的服务只能等当前绑定的客户端的消息。 BIO的代码实现 …BIO同步阻塞IO客户端一个连接请求socket对应一个线程。阻塞体现在: 程序在执行I/O操作时会阻塞当前线程直到I/O操作完成。在线程空闲的时候也无法释放用于别的服务只能等当前绑定的客户端的消息。 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;public class Main {public static void main(String[] args) throws IOException {int id 0;ServerSocket socket new ServerSocket(9090);System.out.println(服务器成功启动...);while (true) {System.out.println(等待客户端连接...);//监听等待客户端连接 这是阻塞操作Socket client socket.accept();System.out.println(客户 id 成功连接到 client.getInetAddress().getHostAddress());//需要为该客户分配线程执行任务new Thread(new Runnable() {Overridepublic void run() {try {InputStream is client.getInputStream();BufferedReader br new BufferedReader(new InputStreamReader(is));while (true) {//阻塞等待客户端消息String line br.readLine();if (line null) {System.out.println(客户端断开...);clinet.close();break;}System.out.println(line);}} catch (IOException e) {throw new RuntimeException(e);}}});}}
}
bio一个客户端对应一个线程所以每有一个客户接入服务器都会创建一个新的线程创建新线程是通过调用内核的clone()指令来实现的。
想必大家一定发现bio的明显的弊端了吧随着接入的客户端越来越多服务器创建的线程数也就越多在提供服务时在不同线程间的切换需要保护当前现场恢复下个线程运行的环境也会越频繁这会造成cpu利用的极大浪费且这种模式的接入量存在明显的上限。
我们可以思考一下造成这一问题的原因是什么因为是阻塞IO--在系统调用时缺少参数需要等待连接或等待消息传递会被中断等待--所以当前线程会被阻塞在原地无法提供别的服务--此时若有新客户端接入我们不得不创建新线程为新客户端服务--导致线程数越来越多。所以根本问题就在阻塞上阻塞导致原线程无法提供服务。
**以上的系统调用过程**/*
在类Unix操作系统中文件描述符File Descriptor是一个非负整数它是一个指向内核中打开文件的指针。每个打开的文件无论是常规文件、目录、套接字、管道等都会被分配一个文件描述符。文件描述符通常用于后续的系统调用
标准输入stdin通常分配文件描述符 0。
标准输出stdout通常分配文件描述符 1。
标准错误stderr通常分配文件描述符 2。
打开一个文件可能会返回文件描述符 3。
创建一个套接字可能会返回文件描述符 4。
创建一个管道可能会返回文件描述符 5 和 6一个用于读一个用于写。
*///以下两步对应的就是java代码中的ServerSocket socket new ServerSocket(9090);
socket()3;//这个函数请求内核创建一个新的套接字系统调用执行成功并返回了一个文件描述符3
bind(3,9090);//socke绑定9090端口//监听这个socket
listen(3);
while(true) { accept(3, )5;//没有客户端连接时会阻塞当有客户端连接时括号内空的参数就是客户端的一些信息系统调用执行成功并返回了一个文件描述符5(代表客户端)//接下来需要为客户端分配线程 clone(一些共享参数)线程号;//通过系统调用clone()指令实现,执行成功会返回线程号
}
对于那个新创建的线程有如下步骤
//创建InputStream
while(true){recv(5, //阻塞等待客户端的消息
}
所以为了解决bio在高连接数的情况下性能急速下降的问题nio就应运而生。 NIO同步非阻塞IO是Java 1.4版本开始引入的一个新IO API,支持面向缓冲区、基于通道的IO操作,以更加高效的方式进行文件读写。 Buffer和通道可以相互读写程序和Buffer交互所以NIO是面向缓冲区的编程 //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;public class Main {public static void main(String[] args) throws IOException {//客户链LinkedListSocketChannel clients new LinkedList();//开启服务器ServerSocketChannel serverSocketChannel ServerSocketChannel.open();//监听9090serverSocketChannel.bind(new InetSocketAddress(9090));//监听线程设置非阻塞serverSocketChannel.configureBlocking(false);while (true) {//等待客户端连接但是不会阻塞等待由连接时返回连接对象无连接时返回null(系统调用层面返回-1)while (true) {SocketChannel client serverSocketChannel.accept();if(client!null){break;} else {//连接线程设置非阻塞client.configureBlocking(false);int port client.socket().getPort();System.out.println(当前客户端port port);clients.add(client);}}//执行为客户端提供的服务以接收消息为例//创建bufferByteBuffer buffer ByteBuffer.allocate(1024);for (SocketChannel client : clients) {//非阻塞的读int read client.read(buffer);//有消息时read0, 无消息read0, 异常事件read-1if(read-1) {//断开连接释放资源client.close();clients.remove(client);break;}if(read 0) {buffer.flip();byte[] bytes new byte[buffer.limit()];buffer.get(bytes);String s new String(bytes);System.out.println(client.socket().getPort() : s);}}}}
} **系统层面流程**//对应的就是java代码中的ServerSocketChannel serverSocketChannel ServerSocketChannel.open();
socket()4;//这个函数请求内核创建一个新的套接字系统调用执行成功并返回了一个文件描述符4
//对应的就是java代码中的serverSocketChannel.bind(new InetSocketAddress(9090));
bind(4,9090);//socke绑定9090端口
//监听这个socket
listen(4);
//多了一步设置非阻塞
fcntl(4,0_NONBLOCK)0;
while(true){accept(4,)?;//返回具体客户端或-1-1代表没有连接fcntl(?, 0_NONBLOCK);//clients.add(?);for(client : clients) {recv(?);//接收消息}
}
但是需要thread去不断轮询clients当clients非常大的时候循环的事件开销就会很大并且对于客户链来说需要执行读写操作的时间和数量只占很小的一部分所以对于轮询操作不仅耗时而且大部分操作是无效的。
为了解决这个问题又引出了多路复用器的概念由多路复用器来监测并通知thread再由thread执行相应操作。
//对应的就是java代码中的ServerSocketChannel serverSocketChannel ServerSocketChannel.open();
socket()4;//这个函数请求内核创建一个新的套接字系统调用执行成功并返回了一个文件描述符4
//对应的就是java代码中的serverSocketChannel.bind(new InetSocketAddress(9090));
bind(4,9090);//socke绑定9090端口
//监听这个socket
listen(4);
//多了一步设置非阻塞
fcntl(4,0_NONBLOCK)0;
while(true){accept(4,)?;//返回具体客户端或-1-1代表没有连接clients.add(?);int cnt select(4,{客户列表});//由多路复用器来监听客户列表中是否需要执行读写操作并告知threadif(cnt0) {//有事件发生才处理recv(cnt);}
}
当selector连接多个channel时它会监听每一个channel看是否有读写事件发生然后再由selector通知thread哪个channel上需要执行什么事件由thread执行。 图片来自这位大佬的博客BIO、NIO_bio nio-CSDN博客