这几天学习NIO…难受了好几天,今天20点30终于明白的七七八八,记下来。。
NIO和前几天的TCP聊天室有点像,对照来看,会舒服点,所以也写一个聊天室。
Selector 选择器
什么选择器就是个“集合”-----类似于map的集合,可以用来记录每一台客户端和服务器的通讯。
为啥说他类似于map呢?因为他也类似<key ,value>只不过map的key和value都是数据,Selecter的key是selectionKey,value是“事件”;都是可以通过“key”去操作“value”;
- 选择器是单例只能实例一个,所以通过open()来实例化
ServerSocketChannel
SernerSocketChannel —我叫他大管道。在一个NIO服务器里只可以有一个。负责开辟端口,设置这个NIO是不是非阻塞模式的。理解的时候可以类比 网络编程的ServerSocket。(有点类似)
- ServerSocketChannel 也是单例
SocketChannel
*这个东西 —我叫他小管道,可以插到大管道上。(可以插好多个的那个,因为可以插好多所以就可以省去多线程)NIO中SocketChannel通过与客户端通讯。一个客户端一个小管道。SocketChannel用来传输数据。理解他可以与网络编程的Socket对比。
SelectionKey选择键(selectionKey)
选择键就是选择器的“key” 在注册的时候,作为“小管道”与事件的标记,来标记这个事件是哪个小管道的。
注册
注册就是把你要做的事情放到选择器里面 这个你要做的事情就会去选择器排队,等着执行
选择器结构
小管道 | 要执行的操作 | SelectionKey |
---|---|---|
SocketChannl | 操作 | 相当于这个的id |
操作只有三种 建立连接 读文件 写文件
每次注册一个。就会多一个这个结构
小管道 | 要执行的操作 | SelectionKey |
---|---|---|
socketchannl1 | read | key1 |
socketchannl2 | read | key2 |
socketchannl1 | write | key3 |
socketchannl2 | write | key4 |
socketchannl1 | read | key5 |
socketchannl2 | read | key6 |
代码 服务器
public class NIOServerP {
static Map<SocketChannel, String> onlinopeople = new HashMap<SocketChannel, String>();
//这个Map集合用来记录有几个用户登录(记录有几个小管道)
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();//选择器是单例只能实例一个,所以通过open()来实例化
ServerSocketChannel serverSocketChannel =ServerSocketChannel.open();//大管道也是一样的,单例
serverSocketChannel.configureBlocking(false);//设置非阻塞模式
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//大管道注册
SocketAddress address = new InetSocketAddress(18888);
serverSocketChannel.socket().bind(address);//给管道绑定端口
while (true){/死循环 一直监控没有可以执行的事件
if (selector.select(3000) == 0){//3秒监控一次
if(onlinopeople.size() == 0){
System.out.println("还没有人上线");
continue;
}else {
System.out.println("上线人数为"+onlinopeople.size());
continue;
}
}
System.out.println("有人上线");
boolean stute = keyhander(selector);
if(stute){
break;
}
}
}
public static boolean keyhander(Selector selector) throws IOException {
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();//将选择器放入迭代器
while (iterator.hasNext()){ //判断每一个事件是什么操作 连接?读?写?②
SelectionKey key = iterator.next();
if (key.isAcceptable()) { // 连接?②
System.out.println("上线人数为"+(onlinopeople.size()+1));
Buffer buffer = ByteBuffer.allocate(1024);
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
onlinopeople.put(socketChannel,null);
socketChannel.register(key.selector(),SelectionKey.OP_READ,buffer);//带着收到的缓存区的数据去注册①
}else if(key.isReadable()){ //读?②
System.out.println("有新数据");
StringBuffer sb= new StringBuffer();
SocketChannel socketChannel = (SocketChannel) key.channel();//通过key操作管道。把管道“接”到这变量里
ByteBuffer byteBuffer = (ByteBuffer) key.attachment(); //拿到注册时一起带着的数据 ①
while (socketChannel.read(byteBuffer) > 0){ //这个地方可以类比inputstream
byteBuffer.flip(); //byteButter 缓存类的操作。。。不懂百度③
while (byteBuffer.hasRemaining()){
char[] arrayChar = Charset.forName("UTF-8").decode(byteBuffer).array();
sb.append(arrayChar);
}
byteBuffer.clear(); //③
}
System.out.println("收到的数据为"+sb);
//收到信息后向客户端返回消息
byteBuffer.clear(); //③
String message = "我收到";
byteBuffer.put(message.getBytes());
socketChannel.register(key.selector(),SelectionKey.OP_WRITE,byteBuffer);//带着收到的缓存区的数据去注册
}else if(key.isWritable()){ //写②
System.out.println("开始输出数据");
SocketChannel socketChannel =(SocketChannel) key.channel();
ByteBuffer byteBuffer = (ByteBuffer)key.attachment();//
for(Map.Entry<SocketChannel,String> entry : onlinopeople.entrySet()){ //通过map给每个登录的客户端发送消息
byteBuffer.rewind(); //③
entry.getKey().write(byteBuffer);
}
socketChannel.register(key.selector(),SelectionKey.OP_READ,byteBuffer);
byteBuffer.clear(); //③
}else if(key.isValid()){
System.out.println("有异常无效事件。【可尝试重新处理该事件】");
} else {
System.out.println("有其它情况");
}
iterator.remove(); //操作完成后删掉注册的东西
}
return false;
}
}
代码 客户端
public class NIOClient {
public static void main(String[] args) throws IOException {
//选择器 初始化(实例化+数据初始化)、实例化==创建一个类对象
Selector selector = Selector.open();
//初始化套接字连接通道。
SocketChannel channel = SocketChannel.open();
//NIO:支持两种阻塞式、非阻塞式
channel.configureBlocking(false);
//在该服务通道上绑定IP、端口
SocketAddress ip = new InetSocketAddress("127.0.0.1", 18888);
channel.connect(ip);
//向selector注册感兴趣的Key事件
channel.register(selector, SelectionKey.OP_CONNECT);
while (true) {
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
handleKeys(keys);
}
}
public static void handleKeys(Set<SelectionKey> keys) throws IOException {
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isConnectable()) {
ByteBuffer buffer = ByteBuffer.allocate(1024); //==> byte[1024]
//建立连接
SocketChannel channel = (SocketChannel) key.channel();
//与服务器建立稳定连接
channel.finishConnect();
System.out.println("与服务建立稳定连接!");
channel.register(key.selector(), SelectionKey.OP_WRITE, buffer);
} else if (key.isWritable()) {
SocketChannel channel = (SocketChannel) key.channel();
System.out.println("客户端将向服务器发送数据,请输入:");
//向客户端任意输出点东西
Scanner scanner = new Scanner(System.in);
String message = scanner.next();
ByteBuffer buffer = (ByteBuffer) key.attachment();
buffer.put(message.getBytes());
buffer.flip();
channel.write(buffer);
buffer.clear();
channel.register(key.selector(), SelectionKey.OP_READ, buffer);
} else if (key.isReadable()) {
System.out.println("接收客户端数据");
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
//读取OS已经帮你接收完毕的数据
StringBuffer sb = new StringBuffer();
while (channel.read(buffer) > 0) {
buffer.flip();
while (buffer.hasRemaining()) {
char[] arrayChar = Charset.forName("UTF-8").decode(buffer).array();
String temp = new String(arrayChar);
temp = temp.trim();
sb.append(temp);
}
buffer.clear();
}
System.out.println("客户端接收到的服务器数据为:" + sb);
buffer.clear();
channel.register(key.selector(), SelectionKey.OP_WRITE, buffer);
} else if (key.isValid()) {
System.out.println("通讯异常,是否重试处理或限定次数。");
//continue;
}
iterator.remove();
}
}
}