Java NIO 的模型图
1.一个selector对应一个线程来管理
2.一个selector 管理多个channel
3.每个channel对应一个自己的buffer
4.程序切换到哪一个channel是有事件决定的 event
5.selector会根据不同事件在多个channnel之间来切换
6.buffer是一个内存块 底层是一个数组
7.对channel的读写都是通过buffer来实现的
8.channel是双向的 可转变的
NIO编程案例 一个服务端 和多个客户端会话
服务端会打印客户端上线下线的提示 还会将接受到的客户端的消息转发给其他的客户端
客户端接受服务端的消息 向服务器发送消息
服务端代码
package com.example.netty.nio;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
/**
* @Description: 服务器端 客户端上线提示 客户端下线提示 接收客户端的消息 并妆发给其他的客户端
* @Auther:
* @Date: 2020/3/18 09:42
* @version:
*/
public class MyChatServer {
private static ServerSocketChannel serverSocketChannel;
private static Selector selector;
private static final int PORT = 6667;
private static String userName;
public MyChatServer() {
try {
//获取选择器
selector = Selector.open();
// 获取ServerChannel
serverSocketChannel = ServerSocketChannel.open();
// 绑定端口
serverSocketChannel.bind(new InetSocketAddress(PORT));
// 设置为非阻塞
serverSocketChannel.configureBlocking(false);
// 注册监听事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 获取服务端名字
userName = serverSocketChannel.getLocalAddress().toString().substring(1);
System.out.println("server is ok ..." + userName);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void listen() throws Exception {
System.out.println("开始监听客户端....");
// 循环监听
while (true){
// 查看是否有事件 最多等待一秒钟
int select = selector.select(1000);
if (select > 0) {
//使用迭代器方便 移除key
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
if (selectionKey.isAcceptable()) {
// 可连接的事件 表示客户端已准备好链接服务器端
SocketChannel channel = serverSocketChannel.accept();
// 设置为非阻塞 链接事件处理 打印客户端 上线提示
channel.configureBlocking(false);
// 把channel 注册到 选择器 读取事件监听
channel.register(selector, SelectionKey.OP_READ);
String string = channel.getRemoteAddress().toString();
System.out.println("客户端上线.." + string);
}
if (selectionKey.isReadable()) {
// 读取数据 并将数据转发到其他的客户端
// 链接事件处理 打印客户端 上线提示
SocketChannel channel = (SocketChannel) selectionKey.channel();
read(channel);
}
//移除这个key
iterator.remove();
}
}
}
}
public static void read(SocketChannel channelSelf) {
try {
// 获取到客户端的消息
ByteBuffer buffer = ByteBuffer.allocate(1024);
channelSelf.read(buffer);
String msg = new String(buffer.array());
System.out.println(channelSelf.getRemoteAddress().toString() + "消息内容" + msg);
// 转发给其他的客户端
Set<SelectionKey> keys = selector.keys();
for (SelectionKey key:keys ){
// 遍历所有注册到选择器上面的channel
Channel channel = key.channel();
// 排除自已以外的所有的socketChannel 转发消息
if(channel instanceof SocketChannel && channel!=channelSelf){
System.out.println("服务器开始转发消息");
ByteBuffer wrap = ByteBuffer.wrap(msg.getBytes());
((SocketChannel) channel).write(wrap);
}
}
} catch (Exception e) {
System.out.println("客户端离线.....");
}
}
public static void main(String[] args) throws Exception{
MyChatServer myChatServer= new MyChatServer();
listen();
}
}
客户端代码
package com.example.netty.nio;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
/**
* @Description: 向客户端发送消息并接受服务器端的消息
* @Auther: shanshaowei
* @Date: 2020/3/18 10:29
* @version:
*/
public class MyChatClient {
private static Selector selector;
private static SocketChannel socketChannel;
private static int PORT = 6667;
private static String userName;
public MyChatClient(){
try{
//创建Selector
selector = Selector.open();
//创建SocketChannel 去链接远程的ip和端口
socketChannel= SocketChannel.open(new InetSocketAddress("127.0.0.1",PORT));
// 设置为非阻塞
socketChannel.configureBlocking(false);
// 注册到选择器并监听
socketChannel.register(selector, SelectionKey.OP_READ);
userName = socketChannel.getLocalAddress().toString();
System.out.println(userName+"is ok....");
}catch (Exception e){
e.printStackTrace();
}
}
public static void read()throws Exception{
int select = selector.select();
if(select>0){
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey next = iterator.next();
if(next.isReadable()){
ByteBuffer allocate = ByteBuffer.allocate(1024);
SocketChannel channel = (SocketChannel)next.channel();
channel.read(allocate);
System.out.println("来自服务器的消息"+new String(allocate.array()));
}
iterator.remove();
}
}
}
public static void send(String msg) throws Exception{
msg=userName+":"+msg;
socketChannel.write(ByteBuffer.wrap(msg.getBytes()));
}
public static void main(String[] args) throws Exception{
//初始化客户端
MyChatClient chatClient= new MyChatClient();
// 创建一个线程 接受服务端信息
new Thread(new Runnable() {
@Override
public void run() {
// 不听的去读
while (true){
try{
read();
}catch (Exception e){
e.printStackTrace();
}
}
}
}).start();
//发送数据给服务器端
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String s = scanner.nextLine();
send(s);
}
}
}
需要多个客户端就拷贝多个客户端的类 客户端控制台输入消息即可通知到服务器