nio探索

单线程bio

我们首先要解决的问题是:为什么要使用nio?

nio除了叫new io之外,还叫做non-blocking io,也就是非阻塞io,可见这一块的重要性。

阻塞与非阻塞,我们主要讲网络编程。

传统的BIO(Blocking IO):

写一个server:

package nio;

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

public class Server {

    public static void main(String[] args) throws IOException {
        byte[] contents = new byte[1024];
        ServerSocket serverSocket = new ServerSocket(8088);
        while (true){
            System.out.println("--------------------split---------------------------");
            System.out.println("waiting connection from client....");
            Socket socket = serverSocket.accept();
            System.out.println("connected!");

            System.out.println("waiting data from client....");
            InputStream inputStream = socket.getInputStream();

            int read = inputStream.read(contents);
            if(read>0){
                System.out.println("data from client has been received!");
                System.out.println("the data is: " + new String(contents));
            }

        }

    }
}

这里有两个地方会阻塞:

 Socket socket = serverSocket.accept();

只有当客户端来连接,阻塞才会解除。

 InputStream inputStream = socket.getInputStream();
 int read = inputStream.read(contents);

从客户端读数据也会阻塞。

我写两个客户端(或者你一个客户端run两次也可以):

package nio;

import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",8088);
        System.out.println("Client tries to connect server...");
      
            Scanner scanner = new Scanner(System.in);
            System.out.println("please write something...");
            String next = scanner.next();
            socket.getOutputStream().write(next.getBytes());
            System.out.println("***data from client has sent!");
        
    }
}

Client.java

package nio;

import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;

public class Client2 {

    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 8088);
        System.out.println("Client2 tries to connect server...");
        Scanner scanner = new Scanner(System.in);
        System.out.println("please write something...");
        String next = scanner.next();
        socket.getOutputStream().write(next.getBytes());
        System.out.println("***data from client has sent!");
    }
}



Client2.java

开启server,先用client连。

--------------------split---------------------------
waiting connection from client....
--------------------split---------------------------
waiting connection from client....

connected!
waiting data from client....

连上了。

但是卡在等待数据那里了。

这时候我们开启client2。

--------------------split---------------------------
waiting connection from client....

connected!
waiting data from client....

server没有反应,表示没有连上。

对于client2来说,是卡在accept那里了。

如果此时client发送数据了:

Client tries to connect server...
please write something...
client
***data from client has sent!

Process finished with exit code 0

那么client2就会连上:

--------------------split---------------------------
waiting connection from client....

connected!
waiting data from client....
data from client has been received!
the data is: client                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
--------------------split---------------------------
waiting connection from client....
connected!
waiting data from client....

这是一个问题。

一个客户端不可能只发一条数据,它应该保持发送数据的状态,所以我们在client上面加上while(true):

package nio;

import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 8088);
        System.out.println("Client tries to connect server...");
        while (true) {
            Scanner scanner = new Scanner(System.in);
            System.out.println("please write something...");
            String next = scanner.next();
            socket.getOutputStream().write(next.getBytes());
            System.out.println("***data from client has sent!");
        }

    }
}

重启server再次测试:

--------------------split---------------------------
waiting connection from client....
connected!
waiting data from client....

让client连接上:

Client tries to connect server...
please write something...
hello
***data from client has sent!
please write something...

当client发送一条数据后,它还保持着发送数据的状态。

--------------------split---------------------------
waiting connection from client....
connected!
waiting data from client....
data from client has been received!
the data is: hello                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
--------------------split---------------------------
waiting connection from client....

但是server接受到一条数据后就断开与client的连接了,它又在等待一个新的连接,client此时再发数据server是接收不到的。

由此我们得出一个结论:单线程无法解决并发问题。


多线程bio

我们的思路是通过多线程解决。

一个client连过来,服务端就会产生一个socket,此时开一条线程将socket传进去,以此与client通信。

package nio;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;


public class Server {
    int count = 0;
    public static void main(String[] args) throws IOException {
        Server server = new Server();
        ServerSocket serverSocket = new ServerSocket(8088);
        while (true) {
            System.out.println("--------------------split---------------------------");
            System.out.println("waiting connection from client....");
            final Socket socket = serverSocket.accept();
            server.count++;
            Thread thread = new Thread(new IOWithClient(server.count,socket));
            thread.start();
        }
    }
}

class IOWithClient implements Runnable{

    int count;
    Socket socket;
    byte[] contents = new byte[1024];

    public IOWithClient(int count, Socket socket) {
        this.count = count;
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            System.out.println("client" + (count) + " connected!");
            System.out.println("waiting data from client" + (count) + "....");
            while (true) {

                InputStream inputStream = socket.getInputStream();
                int read = inputStream.read(contents);
                if (read > 0) {
                    System.out.println("data from client"+(count)+" has been received!");
                    System.out.println("the data is: " + new String(contents));
                }
            }
        } catch (IOException e) {
            if(e.getMessage().equals("Connection reset")){
                System.out.println("client" + count + " disconnected...");
            }else{
                e.printStackTrace();
            }
        }
    }
}


我这里把task拎出来结构会不会清晰一点。

这里我用了count来保存第几个客户端连过来了,目的是为了控制台打印的时候清晰一点。

这当然能够解决所有的问题(并发的问题),但是它太消耗资源了。

new一个thread,可是很耗内存的。况且,成千上万个thread,最终能有信息通信的又有几个呢?

比如你上淘宝,淘宝为每一个用户都new一个thread来处理,真正发生数据交互的(比如买东西)会有多少?大多数人只是随便逛逛。

就算是用线程池来重用线程,还是多线程。我们需要用一个单线程来解决问题!

这时候,nio就登场了。

(难道bio就没用了吗?如果客户端和服务端的io很频繁,100个连接有99个一直在传输数据,当然是可以用bio的)。


nio的模型

我们已经学过FileChannelByteBuffer了,在网络通信中,数据的传递同样是建立channel

package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("127.0.0.1",9090));
        System.out.println("Client tries to connect server...");
        ByteBuffer byteBuffer = ByteBuffer.allocate(512);
        while (true) {
            Scanner scanner = new Scanner(System.in);
            System.out.println("please write something...");
            String next = scanner.next();
            byteBuffer.put(next.getBytes());
            byteBuffer.flip();
            socketChannel.write(byteBuffer);
            byteBuffer.clear();
            System.out.println("***data from client has sent!");
        }

    }
}

Client.java

package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

public class Client2 {

    public static void main(String[] args) throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("127.0.0.1",9090));
        System.out.println("Client2 tries to connect server...");
        ByteBuffer byteBuffer = ByteBuffer.allocate(512);
        while (true) {
            Scanner scanner = new Scanner(System.in);
            System.out.println("please write something...");
            String next = scanner.next();
            byteBuffer.put(next.getBytes());
            byteBuffer.flip();
            socketChannel.write(byteBuffer);
            byteBuffer.clear();
            System.out.println("***data from client2 has sent!");
        }
    }
}



Client2.java

客户端这里我们通过SocketChannel去连接远程机器。

连上以后会卡在scanner那里。

然后是server部分:

package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * non-blocking server
 */
public class TomcatServer {
    static ByteBuffer byteBuffer = ByteBuffer.allocate(512);
    static List<SocketChannel> channelList = new ArrayList<>();

    public static void main(String[] args) {
        SocketChannel socketChannel = null;
        ServerSocketChannel serverSocketChannel = null;
        try {
            serverSocketChannel = ServerSocketChannel.open();
            SocketAddress socketAddress = new InetSocketAddress(9090);
            serverSocketChannel.bind(socketAddress);

            serverSocketChannel.configureBlocking(false);

            while(true){
                //check if there's any information from the connected client
                //if there is, print them.
                //else,do nothing,and continue checking
                Iterator<SocketChannel> iterator = channelList.iterator();
                int read = 0;
                while(iterator.hasNext()) {

                    try {
                        read = iterator.next().read(byteBuffer);
                    } catch (IOException e) {
                        System.out.println(e.getMessage());
                        iterator.remove();
                        System.out.println("the number of the clients are: " + channelList.size());
                    }
                    if (read > 0) {

                        String data = new String(byteBuffer.array(),0,read);
                        System.out.println("the data from client is : " + data);
                        byteBuffer.clear();
                    }
                }
                //this is non-block
                //whether there's client trying to connect the server, following codes will be executed
                socketChannel = serverSocketChannel.accept();

                if(socketChannel != null){
                    System.out.println("connected!");
                    //make socket non-block
                    //thus IO between server and client has no blocking.
                    socketChannel.configureBlocking(false);
                    //for further checking
                    channelList.add(socketChannel);

                    System.out.println(channelList.size() + " clients have connected the server.");
                }
            }

        } catch (IOException e) {
           e.printStackTrace();
        }finally {
            if(socketChannel != null){
                try {
                    socketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if(serverSocketChannel != null){
                try {
                    serverSocketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

首先代码里面除了main线程没有其他thread,所以是单线程的。

每来一个连接,都会建立一条channel,所以我们用一个channelList来存储所有连上的客户端。

 serverSocketChannel.configureBlocking(false);

首先我们确保serverSocketChannel的非阻塞,这样一个client连上后,另一个也能连。换句话说,socketChannel = serverSocketChannel.accept();不会阻塞。

然后进入while true

逻辑是这样的:取出所有已经连上的channel,一个个遍历,里面要是有信息,就打印出来,要是没有就算了。

这里的while true保证服务端一直监听客户端消息的状态。

我们可以想想,如果不这么设计的话,如果是accept之后再read,这么做会有问题的:


代码实现是:

package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * non-blocking server
 */
public class TomcatServer {
    static ByteBuffer byteBuffer = ByteBuffer.allocate(512);
    static List<SocketChannel> channelList = new ArrayList<>();

    public static void main(String[] args) {
        SocketChannel socketChannel = null;
        ServerSocketChannel serverSocketChannel = null;
        try {
            serverSocketChannel = ServerSocketChannel.open();
            SocketAddress socketAddress = new InetSocketAddress(9090);
            serverSocketChannel.bind(socketAddress);

            serverSocketChannel.configureBlocking(false);

            while (true) {

                socketChannel = serverSocketChannel.accept();

                if (socketChannel != null) {
                    System.out.println("connected!");
                    //make socket non-block
                    //thus IO between server and client has no blocking.
                    socketChannel.configureBlocking(false);
                    //for further checking
                    channelList.add(socketChannel);

                    System.out.println(channelList.size() + " clients have connected the server.");

                    int read = socketChannel.read(byteBuffer);

                    if (read > 0) {
                        System.out.println("content from client is: " + new String(byteBuffer.array()));
                    }

                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (socketChannel != null) {
                try {
                    socketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (serverSocketChannel != null) {
                try {
                    serverSocketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

就算有一个客户端连上了,也不能收到客户端发来的消息,因为一直while循环,所以我们要把读取客户端消息的代码提到前面去。

 socketChannel.configureBlocking(false);

保证了与客户端io的非阻塞,即read = next.read(byteBuffer);是非阻塞的。


遗留的问题

以上,用单线程实现了并发,即是所谓的nio。然而,这依然是有问题的,因为死循环里面遍历channelList并检查里面是否有内容很消耗资源,我们希望,是不是能够把这个遍历的任务交给操作系统函数去做,这样会不会更快?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值