使用JavaNIO實作簡易線上多人聊天室
一、 服務端代碼
package chatroom.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
/******
@author 阿昌
@create 2021-09-13 20:23
聊天室服務端
*******
*/
public class ChatServer {
//服務端啟動的方法
public void startServer() throws IOException {
//1、建立Selector選擇器
Selector selector = Selector.open();
//2、建立ServerSocketChannel通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//3、為channel通道綁定端口号
serverSocketChannel.bind(new InetSocketAddress(9090));
serverSocketChannel.configureBlocking(false);//設定非阻塞模式
//4、把serverSocketChannel綁定到selector上
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("伺服器啟動.......");
//5、循環監聽是否有連接配接連入
while (true) {
int select = selector.select();
//如果為0,則為沒連接配接,沒有擷取到,就跳出循環
if (select == 0) {
continue;
}
//擷取可用channel
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//周遊
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
//移除 set 集合目前 selectionKey
iterator.remove();
//6、根據就緒狀态,調用對應方法實作具體業務操作
if (selectionKey.isAcceptable()) {
//6.1 如果 accept 狀态
acceptOperator(serverSocketChannel, selector);
}
if (selectionKey.isReadable()) {
//6.2 如果可讀狀态
readOperator(selector, selectionKey);
}
}
}
}
//處理可讀狀态操作
private void readOperator(Selector selector, SelectionKey selectionKey) throws IOException {
//1 從 SelectionKey 擷取到已經就緒的通道
SocketChannel channel = (SocketChannel) selectionKey.channel();
//2 建立 buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//3 循環讀取用戶端消息
int readLength = channel.read(byteBuffer);
String message = "";//用于接收解碼後的資訊
//表示裡面有資料
if (readLength > 0) {
//切換讀模式
byteBuffer.flip();
//讀取内容
message += Charset.forName("UTF-8").decode(byteBuffer);
}
//4 将 channel 再次注冊到選擇器上,監聽可讀狀态
channel.register(selector, SelectionKey.OP_READ);
//5 把用戶端發送消息,廣播到其他用戶端
if (message.length() > 0) {
//廣播給其他用戶端
System.out.println(message);
castOtherClient(message, selector, channel);
}
}
//廣播到其他用戶端
private void castOtherClient(String message, Selector selector, SocketChannel channel) throws IOException {
//1 擷取所有已經接入 channel
Set<SelectionKey> selectionKeySet = selector.keys();
//2 循環想所有 channel 廣播消息
for (SelectionKey selectionKey : selectionKeySet) {
//擷取每個 channel
Channel tarChannel = selectionKey.channel();
//不需要給自己發送
if (tarChannel instanceof SocketChannel && tarChannel != channel) {//不向自己廣播
((SocketChannel) tarChannel).write(Charset.forName("UTF-8").encode(message));
}
}
}
//處理接入狀态操作
private void acceptOperator(ServerSocketChannel serverSocketChannel, Selector selector) throws IOException {
//1 接入狀态,建立 socketChannel
SocketChannel accept = serverSocketChannel.accept();
//2 把 socketChannel 設定非阻塞模式
accept.configureBlocking(false);
//3 把 channel 注冊到 selector 選擇器上,監聽可讀狀态
accept.register(selector, SelectionKey.OP_READ);
//4 用戶端回複資訊
accept.write(Charset.forName("UTF-8").encode("歡迎進入聊天室,請注意隐私安全"));
}
public static void main(String[] args) throws IOException {
new ChatServer().startServer();
}
}
二、用戶端代碼
package chatroom.client;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Scanner;
/******
@author 阿昌
@create 2021-09-13 20:45
聊天室用戶端
*******
*/
public class ChatClient {
//啟動方法
public void startClient(String name) throws IOException {
//連接配接服務端
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9090));
//接收服務端響應資料
Selector selector = Selector.open();
socketChannel.configureBlocking(false);//設定非阻塞連接配接
socketChannel.register(selector, SelectionKey.OP_READ);//将通道注冊到selector上
//建立線程,來接收服務端的響應資訊
new Thread(new ClientThread(selector)).start();
//向服務端發送資訊
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()){
String msg = scanner.nextLine();
if (msg.length()>0){
//寫入通道消息,讓他發送給服務端
socketChannel.write(Charset.forName("UTF-8").encode(name+": "+msg));
}
}
}
}
三、用戶端異步監聽服務端響應Runnable類
package chatroom.client;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
/******
@author 阿昌
@create 2021-09-13 20:57
用戶端異步監聽服務端響應Runnable類
*******
*/
public class ClientThread implements Runnable {
private Selector selector;
public ClientThread(Selector selector) {
this.selector = selector;
}
@Override
public void run() {
try {
while (true) {
//擷取 channel 數量
int readChannels = selector.select();
if (readChannels == 0) {
continue;
}
//擷取可用的 channel
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//周遊集合
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
//移除 set 集合目前 selectionKey
iterator.remove();
//如果可讀狀态
if (selectionKey.isReadable()) {
//處理可讀狀态操作
readOperator(selector, selectionKey);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//處理可讀狀态操作
private void readOperator(Selector selector, SelectionKey selectionKey) throws IOException {
//1 從 SelectionKey 擷取到已經就緒的通道
SocketChannel socketChannel =(SocketChannel) selectionKey.channel();
//2 建立 buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//3 循環讀取用戶端消息
int readLength = socketChannel.read(byteBuffer);
String message = "";//用于存儲解碼後的消息
if (readLength > 0) {
//切換讀模式
byteBuffer.flip();
//讀取内容
message += Charset.forName("UTF-8").decode(byteBuffer);
}
//4 将 channel 再次注冊到選擇器上,監聽可讀狀态
socketChannel.register(selector, SelectionKey.OP_READ);
//5 把用戶端發送消息,廣播到其他用戶端
if (message.length() > 0) {
//廣播給其他用戶端
System.out.println(message);
}
}
}
四、A/B用戶端
- AClient
public class AClient {
public static void main(String[] args) {
try {
new ChatClient().startClient("阿昌一号");
} catch (IOException e) {
e.printStackTrace();
}
}
}
- BClient
public class BClient {
public static void main(String[] args) {
try {
new ChatClient().startClient("阿昌二号");
} catch (IOException e) {
e.printStackTrace();
}
}
}
五、示範
服務端啟動後,AClient和BClient都發送一條消息後的效果
- 服務端控制台
- AClient用戶端控制台
- BClient用戶端控制台