基于Java NIO的Socket通信

Java NIO模式的Socket通信,是一种同步非阻塞IO设计模式,它为Reactor模式实现提供了基础。

下面看看,Java实现的一个服务端和客户端通信的例子。

NIO模式的基本原理描述如下:

服务端打开一个通道(ServerSocketChannel),并向通道中注册一个选择器(Selector),这个选择器是与一些感兴趣的操作的标识(SelectionKey,即通过这个标识可以定位到具体的操作,从而进行响应的处理)相关联的,然后基于选择器(Selector)轮询通道(ServerSocketChannel)上注册的事件,并进行相应的处理。

客户端在请求与服务端通信时,也可以向服务器端一样注册(比服务端少了一个SelectionKey.OP_ACCEPT操作集合),并通过轮询来处理指定的事件,而不必阻塞。

下面的例子,主要以服务端为例,而客户端只是简单地发送请求数据和读响应数据。

服务端实现,代码如下所示:

package org.voovan.network.io;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @USER echo
 * @TIME 2017-07-24 16:40
 * @DESC NIO SOCKET
 **/

/**
 * Buffer: 缓冲区,用来临时存放输入或输出数据。
 * Charset:用来把Unicode字符编码和其它字符编码互转。
 * Channel: 针对面向流的侦听套接字的可选择通道
 *    (数据传输通道,用来把Buffer中的数据写入到数据源,或者把数据源中的数据读入到Buffer)。
 * Selector:SelectableChannel 对象的多路复用器。
 */

public class MyServer3 {

    private final static Logger logger = Logger.getLogger(MyServer3.class.getName());

    public static void main(String[] args) {
        Selector selector = null;
        ServerSocketChannel serverSocketChannel = null;

        try {
            // Selector for incoming time requests 打开一个选择器,不同版本jdk有不同实现
            selector = Selector.open();

            // Create a new server socket and set to non blocking mode
            serverSocketChannel = ServerSocketChannel.open();   //打开服务器套接字通道。
            serverSocketChannel.configureBlocking(false); //此通道将被置于非阻塞模式

            // Bind the server socket to the local host and port
            serverSocketChannel.socket().setReuseAddress(true);
            serverSocketChannel.socket().bind(new InetSocketAddress(10000));

            // Register accepts on the server socket with the selector. This
            // step tells the selector that the socket wants to be put on the
            // ready list when accept operations occur, so allowing multiplexed
            // non-blocking I/O to take place.
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            // Here's where everything happens. The select method will
            // return when any operations registered above have occurred, the
            // thread has been interrupted, etc.
            while (selector.select() > 0) {
                // Someone is ready for I/O, get the ready keys
                Iterator<SelectionKey> it = selector.selectedKeys().iterator();

                // Walk through the ready keys collection and process date requests.
                while (it.hasNext()) {
                    SelectionKey readyKey = it.next();
                    it.remove();

                    // The key indexes into the selector so you
                    // can retrieve the socket that's ready for I/O
                    execute((ServerSocketChannel) readyKey.channel());
                }
            }
        } catch (ClosedChannelException ex) {
            logger.log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            logger.log(Level.SEVERE, null, ex);
        } finally {
            try {
                selector.close();
            } catch(Exception ex) {}
            try {
                serverSocketChannel.close();
            } catch(Exception ex) {}
        }
    }

    private static void execute(ServerSocketChannel serverSocketChannel) throws IOException {
        SocketChannel socketChannel = null;
        try {
            socketChannel = serverSocketChannel.accept();
            MyRequestObject myRequestObject = receiveData(socketChannel);
            logger.log(Level.INFO, myRequestObject.toString());

            MyResponseObject myResponseObject = new MyResponseObject(
                    "response for " + myRequestObject.getName(),
                    "response for " + myRequestObject.getValue());
            sendData(socketChannel, myResponseObject);
            logger.log(Level.INFO, myResponseObject.toString());
        } finally {
            try {
                socketChannel.close();
            } catch(Exception ex) {}
        }
    }

    private static MyRequestObject receiveData(SocketChannel socketChannel) throws IOException {
        MyRequestObject myRequestObject = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        try {
            byte[] bytes;
            int size = 0;
            while ((size = socketChannel.read(buffer)) >= 0) {
                buffer.flip();
                bytes = new byte[size];
                buffer.get(bytes);
                baos.write(bytes);
                buffer.clear();
            }
            bytes = baos.toByteArray();
            Object obj = SerializableUtil.toObject(bytes);
            myRequestObject = (MyRequestObject)obj;
        } finally {
            try {
                baos.close();
            } catch(Exception ex) {}
        }
        return myRequestObject;
    }

    private static void sendData(SocketChannel socketChannel, MyResponseObject myResponseObject) throws IOException {
        byte[] bytes = SerializableUtil.toBytes(myResponseObject);
        ByteBuffer buffer = ByteBuffer.wrap(bytes);
        socketChannel.write(buffer);
    }
}
package org.voovan.network.io;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @USER echo
 * @TIME 2017-07-24 16:40
 * @DESC NIO SOCKET
 **/

/**
 * SocketChannel 针对面向流的连接套接字的可选择通道。
 *
 */

public class MyClient3 {

    private final static Logger logger = Logger.getLogger(MyClient3.class.getName());

    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 1000; i++) {
            final int idx = i;
            new Thread(new MyRunnable(idx)).start();
        }
    }

    private static final class MyRunnable implements Runnable {

        private final int idx;

        private MyRunnable(int idx) {
            this.idx = idx;
        }

        public void run() {
            SocketChannel socketChannel = null;
            try {
                socketChannel = SocketChannel.open();
                SocketAddress socketAddress = new InetSocketAddress("localhost", 10000);
                socketChannel.connect(socketAddress);

                MyRequestObject myRequestObject = new MyRequestObject("request_" + idx, "request_" + idx);
                logger.log(Level.INFO, myRequestObject.toString());
                sendData(socketChannel, myRequestObject);

                MyResponseObject myResponseObject = receiveData(socketChannel);
                logger.log(Level.INFO, myResponseObject.toString());
            } catch (Exception ex) {
                logger.log(Level.SEVERE, null, ex);
            } finally {
                try {
                    socketChannel.close();
                } catch (Exception ex) {
                }
            }
        }

        private void sendData(SocketChannel socketChannel, MyRequestObject myRequestObject) throws IOException {
            byte[] bytes = SerializableUtil.toBytes(myRequestObject);
            ByteBuffer buffer = ByteBuffer.wrap(bytes);
            socketChannel.write(buffer);
            socketChannel.socket().shutdownOutput();
        }

        private MyResponseObject receiveData(SocketChannel socketChannel) throws IOException {
            MyResponseObject myResponseObject = null;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            try {
                ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
                byte[] bytes;
                int count = 0;
                while ((count = socketChannel.read(buffer)) >= 0) {
                    buffer.flip();
                    bytes = new byte[count];
                    buffer.get(bytes);
                    baos.write(bytes);
                    buffer.clear();
                }
                bytes = baos.toByteArray();
                Object obj = SerializableUtil.toObject(bytes);
                myResponseObject = (MyResponseObject) obj;
                socketChannel.socket().shutdownInput();
            } finally {
                try {
                    baos.close();
                } catch (Exception ex) {
                }
            }
            return myResponseObject;
        }
    }
}

上述实现,NioTcpServer服务线程启动后,监听指定端口,等待客户端请求的到来,然后NioTcpClient客户端进程启动并发送请求数据,服务端接收到请求数据后,响应客户端(将请求的数据作为响应数据写回到客户端通道SocketChannel,并等待客户端处理)。

实际上,客户端和服务端可以采用同样轮询的非阻塞模式来实现,为简单实现在这个例子中我们把客户端角色简化了,而实际上它可能在另一个系统通信中充当服务端角色。

另外,上面对于不同事件是采用非线程的方式来处理,只是简单地调用处理的方法。在实际中,如果存在大量连接、读写请求,可以考虑使用线程池来更大程度地并发处理,提高服务端处理的速度和吞吐量,提升系统性能。

转载于:https://my.oschina.net/u/3613710/blog/1486478

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值