文章目录
1. 简介
1.1 Channel
Channel 称为通道,可以异步读写数据,可以从 Buffer(缓冲区)读写数据
1.1 Selector
Selector 能够检测多个 Channel(通道)是否有事件发生。如果有事件发生,可以取到事件的Channel进行处理。如果没有事件,就持续阻塞等待。
NIO基础知识这里就不过多介绍了,下面直接上实例
2. 群聊通讯
2.1 实现目标
- 搭建服务端,接收客户端连接,接收客户端消息并进行消息转发
- 搭建客户端,启动后连接服务端,可以发送消息,也可以接受其他客户端的消息(服务端转发的消息)
2.2 服务端Server
2.2.1 流程图
2.2.2 服务端源码
public class MultiChatServer {
private Selector selector;
private ServerSocketChannel serverSocketChannel;
private static final int PORT = 6666;
public static void main(String[] args) {
MultiChatServer server = new MultiChatServer();
server.start();
}
public MultiChatServer(){
try {
//创建Selector
selector = Selector.open();
//创建ServerSocketChannel
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
serverSocketChannel.configureBlocking(false);
//ServerSocketChannel注册到Selector,并监听连接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
}
}
//服务开始
public void start(){
System.out.println("服务已启动,等待连接...");
try {
while (true) {
//Selector获取连接,如果当前没连接,会一直阻塞等待
selector.select();
//获取触发事件的SelectionKey,遍历KEY,其实也相当于遍历事件
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey selKey = iterator.next();
//处理事件
handleEvent(selKey);
//此处要将SelectionKey移除,否则事件会一直存在
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//处理事件
public void handleEvent(SelectionKey selKey){
try {
//连接事件
if (selKey.isAcceptable()){
//获取连接的客户端SocketChannel,并注册到Selector,监听读取数据事件
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println(socketChannel.getRemoteAddress() + " 已连接");
}
//读取数据事件
if (selKey.isReadable()){
//通过SelectionKey可以取到对应的SocketChannel,然后也就可以取到ByteBuffer的数据
SocketChannel socketChannel = (SocketChannel) selKey.channel();
try {
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (socketChannel.read(buffer) > 0){
String message = new String(buffer.array());
System.out.println("服务器接收到消息:" + message);
transferToOthers(message, selKey); //消息转发
}
}
catch (IOException e){
selKey.cancel();
socketChannel.close();
}
}
}
catch (Exception e){
e.printStackTrace();
}
}
//转发消息给其他客户端
public void transferToOthers(String message, SelectionKey srcKey) throws IOException {
Set<SelectionKey> allClients = selector.keys();
for (SelectionKey clientKey : allClients) {
SelectableChannel channel = clientKey.channel();
//排除服务端(ServerSocketChannel)
if (!(channel instanceof SocketChannel)){
continue;
}
//排除发送该消息的客户端自己
if (clientKey == srcKey){
continue;
}
SocketChannel socketChannel = (SocketChannel) channel;
socketChannel.write(ByteBuffer.wrap(message.getBytes()));
}
}
}
2.3 客户端Client
2.3.1 客户端源码
public class MultiChatClient {
private Selector selector;
private SocketChannel socketChannel;
private static final String SERVER = "127.0.0.1";
private static final int PORT = 6666;
public MultiChatClient(){
try {
//创建Selector
selector = Selector.open();
//创建SocketChannel,连接ServerSocketChannel,并注册到Selector
socketChannel = SocketChannel.open(new InetSocketAddress(SERVER, PORT));
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
MultiChatClient client = new MultiChatClient();
new Thread(() -> {
client.start();
}, "ReceiveMsgThread").start();
try {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()){
String message = scanner.nextLine();
client.sendMessage(message);
}
}
catch (Exception e){
e.printStackTrace();
}
}
public void start(){
try {
//selector 不断监听事件
while (selector.select() > 0){
//遍历事件
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey selKey = iterator.next();
handle(selKey);
iterator.remove();
}
}
}
catch (Exception e){
e.printStackTrace();
}
}
//处理事件
public void handle(SelectionKey selKey) throws IOException {
if (selKey.isReadable()){
SocketChannel channel = (SocketChannel) selKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer);
String message = new String(buffer.array());
System.out.println(message);
}
}
//发送消息
public void sendMessage(String message) throws IOException {
if (message.equals("quit")){
socketChannel.close();
socketChannel = null;
return;
}
String sendMsg = socketChannel.getLocalAddress() + ":" + message;
socketChannel.write(ByteBuffer.wrap(sendMsg.getBytes()));
}
}
2.4 测试
-
启动服务端Server
-
修改客户端启动配置,把这里勾起,表示允许多个同时运行
-
启动2个客户端
-
客户端A在控制台输入一条消息 hello everyone,并回车
-
服务端成功收到消息
-
客户端B也收到消息