在socket 通讯层面,BIO就是阻塞io,也就是说,在socket等待连接事件或者读写事件的发生的过程中,当前线程会一直处于阻塞状态,不能做其他事情;
1、bio
public static void main(String[] args) throws IOException {
//bio阻塞io,主线程只能处理一个连接,当前连接没有处理完,是不能接收新链接的,这对于并发来讲,是非常不友好的
? ServerSocket serverSocket = new ServerSocket(9000);
? while(true) {
? ? ? //此处会阻塞,等待客户端连接
? ? ? Socket socket = serverSocket.accept();
? ? ? System.out.println("客户端已连接");
? ? ? byte[] bytes = new byte[128];
? ? ? //此处会阻塞,等待客户端发送数据
? ? ? int len = socket.getInputStream().read(bytes);
? ? ? System.out.println("客户端发送数据:" + new String(bytes, 0, len));
? }
?
}
2、bio-plus
对于上种情形,可以简单地做出改进,也就是利用多线程把建立连接和处理读写事件分开,这样,二者之间就不会互相影响
public static void main(String[] args) throws IOException {
?
? //加强版bio,利用异步线程去做收发数据,主线程只负责建立连接,这样,即时已连接客户端不发送数据,主线程也不会一直阻塞,可以继续循环接收新的连接;
? // 但是,这样在并发高的情况下,对内存是巨大的消耗;于是,nio就应运而生;
? ServerSocket serverSocket = new ServerSocket(9000);
? while(true) {
? ? ? Socket socket = serverSocket.accept();
? ? ? System.out.println("客户端已连接");
? ? ? new Thread(() -> {
? ? ? ? ? byte[] bytes = new byte[128];
? ? ? ? ? int len = 0;
? ? ? ? ? try {
? ? ? ? ? ? ? len = socket.getInputStream().read(bytes);
? ? ? ? ? } catch (IOException e) {
? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? }
? ? ? ? ? System.out.println("客户端发送数据:" + new String(bytes, 0, len));
? ? ? }).start();
? }
?
}
bio-plus版虽然将建立连接和处理读写事件分开了,但是在高并发的情况下仍然“不堪一击”,并且,这种方式虽然看似是解决了bio的阻塞问题,但是属于治标不治本,它的解决方式只不过是利用空间(开辟新线程)去换取时间而已,实质上还是阻塞IO;
3.Nio-Select
于是,nio非阻塞式IO出现了,如上所示,将通道对象serverSocketChannel设置为非阻塞即可,并且引入了selector选择器(可以认为就是socketList),将建立好的连接对象放到socketList中,每次循环就会将所有的socket连接遍历一遍,处理读写事件。这样,单个线程也可以处理多个连接和事件。
public class NioSelect {
? private static List<SocketChannel> socketList=new ArrayList();
? public static void main(String[] args) throws IOException {
? ? ? //nio演示,接收客户端连接和接收数据都不会阻塞;
? ? ? ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
? ? ? serverSocketChannel.socket().bind(new InetSocketAddress(9000));
? ? ? //设置serverSocketChannel为非阻塞
? ? ? serverSocketChannel.configureBlocking(false);
? ? ? System.out.println("服务启动");
? ? ? while(true) {
? ? ? ? ? SocketChannel socketChannel = serverSocketChannel.accept();
? ? ? ? ? if(socketChannel!=null){
? ? ? ? ? ? ? System.out.println("客户端已连接");
? ? ? ? ? ? ? //设置socketChannel为非阻塞
? ? ? ? ? ? ? socketChannel.configureBlocking(false);
? ? ? ? ? ? ? //将客户端连接放到集合中
? ? ? ? ? ? ? socketList.add(socketChannel);
? ? ? ? ? }
? ? ? ? ? //遍历所有的socket连接,获取数据;但是这样会有空轮训的问题,就是没有发送数据的连接也会被遍历,不合理
? ? ? ? ? while(socketList.size()>0){
? ? ? ? ? ? ? for(int i=0;i<socketList.size();i++){
? ? ? ? ? ? ? ? ? ByteBuffer allocate = ByteBuffer.allocate(128);
? ? ? ? ? ? ? ? ? int read = socketList.get(i).read(allocate);
? ? ? ? ? ? ? ? ? if(read>0){
? ? ? ? ? ? ? ? ? ? ? System.out.println("客户端发送消息:"+new String(allocate.array()));
? ? ? ? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? ? ? ? socketList.remove(i);
? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? }
? ? ? ? ? }
? ? ? }
?
?
? }
}
但是,问题仍然存在。比如此时socketList维护了100个连接,只有一个连接向服务端发送了数据,但是,按照代码却需要将着100个连接全部遍历一遍,这显然是不合理的,并且,socketList中可以存放的连接数量也是有限的,无法解决c10k问题。(所谓c10k问题,指的是:服务器如何支持10k个并发连接)
4.Nio-Epoll
public class NioEpoll {
? private static List<SocketChannel> socketList=new ArrayList();
? public static void main(String[] args) throws IOException {
? ? ? //nio演示,接收客户端连接和接收数据都不会阻塞;
? ? ? ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
? ? ? serverSocketChannel.socket().bind(new InetSocketAddress(9000));
? ? ? //设置serverSocketChannel为非阻塞
? ? ? serverSocketChannel.configureBlocking(false);
? ? ? //获取selector选择器
? ? ? Selector selector = Selector.open();
? ? ? //将serverSocketChannel注册到选择器,并且声明需要选择器监听的是accept事件
? ? ? serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
? ? ? System.out.println("服务启动");
? ? ? while(true) {
? ? ? ? ? //选择器开始执行监听,无监听事件发生会一直阻塞,有监听事件发生会跳出阻塞,继续往下执行
? ? ? ? ? selector.select();
? ? ? ? ? Set<SelectionKey> selectionKeys = selector.selectedKeys();
? ? ? ? ? Iterator<SelectionKey> iterator = selectionKeys.iterator();
? ? ? ? ? while(iterator.hasNext()){
? ? ? ? ? ? ? SelectionKey selectionKey = iterator.next();
? ? ? ? ? ? ? //判断发生的是什么事件
? ? ? ? ? ? ? //是接收连接事件,就获取新的客户连接,及事件的注册
? ? ? ? ? ? ? if(selectionKey.isAcceptable()){
? ? ? ? ? ? ? ? ? ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
? ? ? ? ? ? ? ? ? SocketChannel socketChannel = channel.accept();
? ? ? ? ? ? ? ? ? System.out.println("客户端连接成功");
? ? ? ? ? ? ? ? ? socketChannel.configureBlocking(false);
? ? ? ? ? ? ? ? ? //监听的是数据读事件
? ? ? ? ? ? ? ? ? socketChannel.register(selector,SelectionKey.OP_READ);
? ? ? ? ? ? ? ? ? //如果是发生数据读事件,则接收数据,并打印
? ? ? ? ? ? ? }else if(selectionKey.isReadable()){
? ? ? ? ? ? ? ? ? SocketChannel channel = (SocketChannel) selectionKey.channel();
? ? ? ? ? ? ? ? ? ByteBuffer allocate = ByteBuffer.allocate(128);
? ? ? ? ? ? ? ? ? int read = channel.read(allocate);
? ? ? ? ? ? ? ? ? if(read>0){
? ? ? ? ? ? ? ? ? ? ? System.out.println("客户端发送数据:"+new String(allocate.array(),0,read,"GBK"));
? ? ? ? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? ? ? ? System.out.println("客户端已断开连接");
? ? ? ? ? ? ? ? ? ? ? channel.close();
? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? }
? ? ? ? ? ? ? //将处理过的事件从列表中移除,防止重复处理
? ? ? ? ? ? ? iterator.remove();
?
? ? ? ? ? }
? ? ? }
? }
}
这个版本的nio看起来就又进步了一节,这里用epoll选择器代替了上面的select选择器,epoll选择器内部可以分成两部分,一部分是注册到其上的对象列表,另一个部分是有事件发生的列表,于是,可以想到的就是,相比于select遍历所有,epoll只需要遍历有事件发生的对象即可。
其实,在select和epoll之间还有一个poll选择器,只不过poll与select相比,仅仅是存放的连接对象数量变多了,其他的并没有什么区别。
|