书接上文

上一篇博客说了一下SelectionKey这个对象的一些常用属性,为这一篇文章的NIO服务器实例算是做了一些铺垫。不过在展示服务端实例代码之前,还要说几点细节上的东西。

Selector如何选择就绪的通道

//这个方法可能会阻塞,直到至少有一个已注册的事件发生,或者当一个或者更多的事件发生时
selector.select();

NIO明明是非阻塞的IO,为何会阻塞

这是第一章Java NIO学习(一)NIO相关概念中说的内容,作为非阻塞的IO操作,NIO为何会有这么一个可能会造成阻塞的方法呢,别急,selector对象有很多方法可以解决阻塞的问题!

//阻塞在select()方法上的线程也可以立刻返回,不阻塞
selector.selectNow();

//可以设置超时时间,防止进程阻塞
selector.select(long timeout);

//可以唤醒阻塞状态下的selector
selector.wakeup();

NIO服务端实例

本代码实例模拟一个游戏服务器,监听玩家上线的事件。

package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NIOServer {
    private int port;
    //声明通道
    private ServerSocketChannel server;
    //Selector
    private Selector selector;
    //缓冲区
    private ByteBuffer receiveBuffer = ByteBuffer.allocate(1024);

    public NIOServer(int port){
        this.port = port;
        try{
            server = ServerSocketChannel.open();
            //非阻塞
            server.configureBlocking(false);
            //服务端通道绑定地址
            server.bind(new InetSocketAddress("127.0.0.1", this.port));
            selector = Selector.open();
            //给服务器通道注册接收操作,可以在这个点接收客户端连接
            server.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("[系统消息提示]NIO服务器初始化完毕,监听端口" + this.port);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    //监听客户端连接
    public void Listener(){
        while(true){
            try {
                //这个方法可能会阻塞,直到至少有一个已注册的事件发生,或者当一个或者更多的事件发生时
                //selector.select(long timeout);可以设置超时时间,防止进程阻塞
                //selector.wakeup();可以唤醒阻塞状态下的selector
                //selector.selectNow();也可以立刻返回,不阻塞
                selector.select();
                //轮询所有选择器接收到的操作
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> selectionKeyIte = selectionKeys.iterator();
                while(selectionKeyIte.hasNext()){
                    SelectionKey selectionKey = selectionKeyIte.next();
                    selectionKeyIte.remove();
                    //业务处理
                    handleKey(selectionKey);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //业务处理
    public void handleKey(SelectionKey selectionKey) throws IOException{
        //服务端的channel
        ServerSocketChannel server = null;
        //获得和客户端连接的通道
        SocketChannel channel = null;
        //接收到的信息
        String receiveText;
        //数据的标记位,判断客户端是否关闭
        int count;
        //客户端请求连接事件
        if(selectionKey.isAcceptable()){
            try {
                server = (ServerSocketChannel) selectionKey.channel();
                //获得服务端和客户端连接的通道
                channel = server.accept();
                //将服务端和客户端连接的设置非阻塞
                channel.configureBlocking(false);
                //注册服务端和客户端通道的读事件
                channel.register(selector, SelectionKey.OP_READ);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else if(selectionKey.isReadable()){
            //获取通道对象,方便后面将通道内的数据读入缓冲区
            channel = (SocketChannel) selectionKey.channel();
            receiveBuffer.clear();
            count = channel.read(receiveBuffer);
            //如果读出来的客户端数据不为空
            if(count>0){
                receiveText = new String(receiveBuffer.array(),0,count);
                System.out.println("[系统消息提示]服务器发现["+receiveText+"]玩家上线");
            }else{
                System.out.println("[系统消息提示]玩家下线");
                //检测到客户端关闭(玩家下线),删除该selectionKey监听事件,否则会一直收到这个selectionKey的动作
                selectionKey.cancel();
            }
        } 
    }

    public static void main(String[] args) {
        int port = 7080;
        NIOServer server = new NIOServer(port);
        server.Listener();
    }
}

运行效果

启动服务器
这里写图片描述

玩家上线
打开windows的cmd,用telnet命令模拟客户端玩家上线访问服务器。

telnet 127.0.0.1 7080

这里写图片描述

这里稍微提一句,博主用的是win7系统,win7的telnet命令有点问题,输入完telnet 127.0.0.1 7080命令后,正常输入的话,服务端只能接受输入的第一个字符,所以要通过“Ctrl+}”(回车键左边第一个按键)来进入telnet的命令行界面,通过“send”命令去输入玩家姓名传给服务器。

玩家下线
关闭一个telnet窗口
这里写图片描述


版权声明:本文为lhxaiee123原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/lhxaiee123/article/details/76392546