Java千百问_02基本使用(012)_如何编写非阻塞SocketChannel程序

点击进入_更多_Java千百问

1、如何编写非阻塞SocketChannel程序

了解Socket看这里:Socket是什么
了解 SocketChannel看这里:Socket、SocketChannel有什么区别

使用SocketChannel的最大好处就是可以进行非阻塞IO,每次链接后都会直接返回,不会阻塞线程。将需要多个线程的任务通过几个线程就能完成,降低了了性能消耗。

了解阻塞、非阻塞看这里:阻塞、非阻塞有什么区别
要编写SocketChannel,需要了解java.nio包中如下几个类:
1. ServerSocketChannel
ServerSocket的替代类, 支持阻塞通信与非阻塞通信。

  1. SocketChannel
    Socket的替代类, 支持阻塞通信与非阻塞通信。

  2. Selector
    为ServerSocketChannel监控接收客户端连接就绪事件, 为SocketChannel监控连接服务器读就绪和写就绪事件。

  3. SelectionKey
    代表ServerSocketChannel及SocketChannel向Selector注册事件的句柄。当一个 SelectionKey对象位于Selector对象的selected-keys集合中时,就表示与这个SelectionKey对象相关的事件发生了。

我们的通过客户端和服务端两部分代码来介绍。

服务端代码:

public class SocketChannelServer {

    private int port = 8000;// 端口

    public SocketChannelServer() throws Exception {
        Selector selector = Selector.open();
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false); // 设置为非阻塞方式,如果为true 那么就为传统的阻塞方式
        serverChannel.socket().bind(new InetSocketAddress(port)); // 绑定IP 及 端口
        serverChannel.register(selector, SelectionKey.OP_ACCEPT); // 注册

        while (true) {
            System.out.println("Waiting accept!");
            Thread.sleep(1000);
            selector.select();// 刚启动时,没有客户端连接时,会堵塞在这里

            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = keys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();// 为了防止重复迭代
                if (key.isAcceptable()) {
                    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                    SocketChannel socketChannel = serverSocketChannel.accept();// 新的连接
                    System.out.println("Client accept!" + socketChannel);
                    socketChannel.configureBlocking(false);
                    // socketChannel.register(selector,
                    // SelectionKey.OP_WRITE);// 注册write
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));// 注册read
                } else if (key.isWritable()) {// 写入
                    SocketChannel socketChannel = (SocketChannel) key.channel();// 获得与客户端通信的信道
                    String sendMsg = "hello world!";
                    ByteBuffer writeBuffer = ByteBuffer.wrap(sendMsg.getBytes());
                    System.out.println("server send msg===" + sendMsg);
                    socketChannel.write(writeBuffer);
                    key.cancel();
                } else if (key.isReadable()) {// 读取
                    SocketChannel socketChannel = (SocketChannel) key.channel();// 获得与客户端通信的信道

                    ByteBuffer readbuffer = (ByteBuffer) key.attachment();// 得到并清空缓冲区
                    readbuffer.clear();

                    long bytesRead = socketChannel.read(readbuffer); // 读取信息获得读取的字节数

                    if (bytesRead != -1) {

                        readbuffer.flip();// 准备读取

                        String receiveMsg = "";// 将字节转化为字符串
                        while (readbuffer.hasRemaining()) {
                            receiveMsg += String.valueOf((char) readbuffer.get());
                        }
      Thread.sleep(5000);// 服务端等待5秒再打印,但是客户端不会等待
                        System.out.println("server receive msg===" + receiveMsg);
                    }
                    key.cancel();
                }
            }
        }
    }

    public static void main(String[] args) throws Exception {
        new SocketChannelServer();
    }
}

运行服务端代码后,程序会进行监听,直到接收到客户端请求为止。结果如下:

waitting connet…

客户端代码:

public class SocketChannelClient {

    private String host = "127.0.0.1";// 要发送给服务端的ip

    private int port = 8000;// 要发送给服务端的端口

    public SocketChannelClient() throws IOException {
        SocketChannel sc = SocketChannel.open(new InetSocketAddress(host, port));// 打开一个SocketChannel并连接到服务器
        sc.configureBlocking(false);

        // 从server接受消息
        ByteBuffer readbuffer = ByteBuffer.allocate(20);
        sc.read(readbuffer);

        readbuffer.flip();// 准备读取

        String receiveMsg = "";// 将字节转化为字符串
        while (readbuffer.hasRemaining()) {
            receiveMsg += String.valueOf((char) readbuffer.get());
        }
        System.out.println("client receive msg===" + receiveMsg);

        // 发送消息给server
        String sendMsg = "I am a coder.";
        ByteBuffer writeBuffer = ByteBuffer.wrap(sendMsg.getBytes());
        System.out.println("client send msg===" + sendMsg);
        sc.write(writeBuffer);
        sc.close();
    }

    public static void main(String[] args) throws IOException {
        new SocketChannelClient();
    }
}

我们客户端做了2件事情,一是接受服务端消息,一是给服务端发送消息。由于我们要测试多个客户端连接同一个服务端,所以我们需要多次运行客户端代码。这里我们运行两次之后(称为客户端1、客户端2),查看服务端的Console,未超过5秒时会出现以下结果,说明已经连接成功。这里需要注意,最后一句再次打印了Waiting accept!,说明并没有一直等待客户端发送请求,而是继续监听请求,即没有被阻塞:

Waiting accept!
Client accept!java.nio.channels.SocketChannel[connected local=/127.0.0.1:8000 remote=/127.0.0.1:59481]
Waiting accept!

这时,每个客户端Console如下,成功接收到服务端的消息,并发出给服务端的消息,之后便立刻释放了线程,并没有一直等待服务端的执行:

client receive msg===
client send msg===I am a coder.

再回到服务端Console,5秒后,会打印我们收到的请求:

Waiting accept!
Client accept!java.nio.channels.SocketChannel[connected local=/127.0.0.1:8000 remote=/127.0.0.1:59481]
Waiting accept!
server receive msg===I am a coder.
Waiting accept!
Client accept!java.nio.channels.SocketChannel[connected local=/127.0.0.1:8000 remote=/127.0.0.1:59485]
Waiting accept!
server receive msg===I am a coder.
Waiting accept!

这里我们可以看到处理了2个客户端的请求。在服务端代码中,我们可以注册write,达到向客户端写数据的需求。

你可能感兴趣的:(Socket详解,高性能socket,非阻塞socket,套接字Channel,java-nio套接)