Java 之NIO(五) - 非阻塞式网络通信

本文详细介绍Java NIO中Selector的概念及应用,包括Selector、ServerSocketChannel和SocketChannel等关键API的使用,通过示例代码演示了如何构建非阻塞式网络服务。
摘要由CSDN通过智能技术生成

Selector
在新IO中Selector是一个非常重要的概念,在NIO之前通过IO和Socket构造网络通信程序时,所有的服务端将以阻塞式与客户端进行连接,本章内容将介绍通过Selector构造一个非阻塞式网络服务,在学习如何使用Selector构造非阻塞网路之前,我们先看下相关的API。

Selector类的常用方法:
public static Selector open() throws IOException Opens a selector
public abstract int select(long timeout) throws IOException; Selects a set of keys whose corresponding channels are ready for I/O operations.
public abstract Set selectedKeys()Returns this selector’s selected-key set.

在使用NIO开发时,需要使用SelectableChannel类向Select类注册,在网络编程中,我们可以使用其子类,ServerSocketChannel和SocketChannel,以下是关于SelectableChannel的常用方法(阻塞模式与注册):
public abstract SelectableChannel configureBlocking(boolean block) Adjusts this channel’s blocking mode.
public final SelectionKey register(Selector sel, int ops) Registers this channel with the given selector, returning a selection key.
public static ServerSocketChannel open() Opens a server-socket channel.
public abstract ServerSocket socket(); Retrieves a server socket associated with this channel.
在register中需要指定一个Selector对象,这个对象可以通过Selector.open方法获得,而Selector操作集合(ops)则需要从SelectionKey中获取,如下:
public static final int OP_READ = 1 << 0; Operation-set bit for read operations.
public static final int OP_WRITE = 1 << 2; Operation-set bit for write operations.
public static final int OP_CONNECT = 1 << 3; Operation-set bit for socket-connect operations.
public static final int OP_ACCEPT = 1 << 4; Operation-set bit for socket-accept operations.

下面通过一个TCP服务端与客户端通信的实例演示如何运用上述API完成非阻塞方式通信:

服务端程序代码:

package com.ray.nio.demo;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Iterator;
import java.util.Set;
/**
 * 使用NIO的服务器端不会发生阻塞,所以多个客户端可以连接服务器。
 * @author xuleilei
 *
 */
public class SelectorServer {
    public static void main(String[] args) {
        Selector selector = null;
        ServerSocketChannel serverSocketChannel = null;
        try {
            // 创建一个Selector
            selector = Selector.open();
            // 创建一个ServerSocketChannel
            serverSocketChannel = ServerSocketChannel.open();
            // 启动端口监听
            InetSocketAddress ip = new InetSocketAddress(10001);
            serverSocketChannel.socket().bind(ip);
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            // 监听事件
            while (selector.select() > 0) {
                // 事件来源列表
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    // 删除当前事件
                    iterator.remove();
                    // 判断事件类型
                    if (selectionKey.isAcceptable()) {
                        // 连接事件
                        ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
                        SocketChannel clientChannel = server.accept();
                        clientChannel.configureBlocking(false);
                        clientChannel.register(selector, SelectionKey.OP_READ);
                        System.out.println("客户端连接:" + clientChannel.socket().getInetAddress().getHostName() + ":" + clientChannel.socket().getPort());
                    } else if (selectionKey.isReadable()) {
                        // 读取数据事件
                        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                        // 读取数据
                        Charset charset = Charset.forName("UTF-8");
                        CharsetDecoder decoder = charset.newDecoder();
                        ByteBuffer byteBuffer = ByteBuffer.allocate(50);
                        socketChannel.read(byteBuffer);
                        byteBuffer.flip();
                        String msg = decoder.decode(byteBuffer).toString();
                        System.out.println("收到" + msg);
                        // 写入数据
                        CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
                        socketChannel.write(encoder.encode(CharBuffer.wrap("server" + msg)));
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭
            try {
                selector.close();
                serverSocketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

客户端代码实现:

package com.ray.nio.demo;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Iterator;
import java.util.Set;


/**
 * Client程序有两个线程,一个线程负责监听键盘,第二个线程负责将键盘的输入内容通过NIO发送出去。
 * @author xuleilei
 *
 */
public class SelectorClient {
    /**
     * 主线程
     * @param args
     */
    public static void main(String[] args) {
        ClientRunnable clientRunnable = new ClientRunnable();
        Thread thread = new Thread(clientRunnable);
        thread.start();
        
        //输入,输出流
         BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        
        String readline = "";
        try {
            while((readline = bufferedReader.readLine())!=null){
                if(readline.equals("bye")){
                    clientRunnable.close();
                    System.exit(0);
                }
                clientRunnable.sendMessage(readline);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
 
class ClientRunnable implements Runnable{
    private CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
    private CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
    
    private Selector selector = null;
    private SocketChannel socketChannel = null;
    private SelectionKey selectionKey = null;
    
    public ClientRunnable() {
        //创建selector
        try {
            selector = Selector.open();
            
            //创建Socket并注册
            socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            
            selectionKey = socketChannel.register(selector,SelectionKey.OP_CONNECT);
            
            //连接到远程地址
            InetSocketAddress ip = new InetSocketAddress("localhost",10001);
            socketChannel.connect(ip);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    @Override
    public void run() {
        try {
         //监听事件
            while(selector.select() > 0){
                //事件来源列表
                Set<SelectionKey> keySet = selector.keys();
                Iterator<SelectionKey> iterator = keySet.iterator();
                while(iterator.hasNext()){
                    SelectionKey key = iterator.next();
                    //删除当前事件
                    iterator.remove();
                    
                    //判断事件类型
                    if(key.isConnectable()){
                        //连接事件
                        //取得当前SocketChannel对象
                        SocketChannel channel = (SocketChannel)key.channel();
                        if(channel.isConnectionPending()){
                            channel.finishConnect();
                        }
                        channel.register(selector, SelectionKey.OP_READ);
                        System.out.println("连接服务器端成功!");
                    }else if(key.isReadable()){
                        //读取数据事件
                        SocketChannel channel = (SocketChannel)key.channel();
                        
                        //读取数据
                        ByteBuffer buffer = ByteBuffer.allocate(50);
                        channel.read(buffer);
                        String msg = decoder.decode(buffer).toString();
                        System.out.println("收到:"+msg);
                    }
                    
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭
            try {
                selector.close();
                socketChannel.close();
                
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * 发送消息
     * @param msg
     */
    public void sendMessage(String msg){
        try {
            SocketChannel client = (SocketChannel)selectionKey.channel();
            client.write(encoder.encode(CharBuffer.wrap(msg)));
        } catch (CharacterCodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 关闭客户端
     */
    public void close(){
        try {
            selector.close();
            socketChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值