书接上文
上一篇博客说了一下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 版权协议,转载请附上原文出处链接和本声明。