服務端:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
/**
* NIO 非阻塞狀態的TCP聊天室服務端核心代碼
*
* @author Anonymous 2020/3/16 16:59
*/
public class ChatServer {
/**
* 服務器核心模塊,ServerSocketChannel
*/
private ServerSocketChannel serverSocket;
/**
* 服務端NIO Selector選擇器
*/
private Selector selector;
/**
* 服務端監聽服務指定端口號
*/
private static final int PORT = 8848;
/*
1. 構造方法
2. 接收方法
3. 發送方法(廣播)
4. 同時啓動接收和發送功能 start
*/
/**
* 服務器構造方法,開啓ServerSocketChannel,同時開啓Selector,註冊操作
*
* @throws IOException 異常
*/
public ChatServer() throws IOException {
// 1. 啓動服務器SocketServer NIO服務器
serverSocket = ServerSocketChannel.open();
// 2. 啓動選擇器
selector = Selector.open();
// 3. 端口綁定
serverSocket.bind(new InetSocketAddress(PORT));
// 4. 選擇NIO方式爲非阻塞狀態
serverSocket.configureBlocking(false);
// 5. 註冊SeverSocket,確定當前監聽的狀態是OP_ACCEPT
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
}
/**
* 服務器幹活方法,指定客戶端綁定,數據接收和轉發
*/
public void start(){
try {
while (true) {
if (0 == selector.select(2000)) {
System.out.println("服務器默默的等待連接,無人訪問...");
continue;
}
/*
selectedKeys:
獲取當前所有發生事件操作的對應SelectionKey Set集合
*/
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 1. 連接
if (key.isAcceptable()) {
// 連接客戶端,獲取對應的SocketChannel對象
SocketChannel socket = serverSocket.accept();
socket.configureBlocking(false);
socket.register(selector, SelectionKey.OP_READ);
// 廣播上線
broadcast(socket, socket.getRemoteAddress().toString() + "上線了");
}
// 2. 接收數據轉發
if (key.isReadable()) {
readMsg(key);
}
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 從指定的SelectionKey中讀取數據
*
* @param key 符合OP_READ 要求的SelectionKey
*/
public void readMsg(SelectionKey key) throws IOException {
// 根據指定SelectionKey獲取對應的SocketChannel對象
SocketChannel socket = (SocketChannel) key.channel();
// 創建緩衝區
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 從緩衝區中讀取數據,返回值類型是讀取到的字節數
int length = socket.read(buffer);
// 因爲讀取的數據可能存在0的情況
if (length > 0) {
String message = new String(buffer.array());
// 廣播數據
broadcast(socket, message);
}
}
/**
* 廣播方法,該方法是羣發消息,但是不要發給自己
*
* @param self 當前發送數據的客戶端
* @param message 消息
*/
public void broadcast(SocketChannel self, String message) throws IOException {
/*
獲取當前Selector選擇器中所有的SelectionKey
Selector中註冊的內容有SocketChannel對應的SelectionKey
已經ServerSocketChannel對應的SelectionKey
*/
Set<SelectionKey> keys = selector.keys();
// 遍歷整個SelectionKey Set集合
for (SelectionKey key : keys) {
// 獲取對應SelectionKey的Channel對象
SelectableChannel channel = key.channel();
// 第一: channel對應的是一個SocketChannel對象,並且不是當前發送消息的SocketChannel對象
if (channel instanceof SocketChannel && !channel.equals(self)) {
SocketChannel socketChannel = (SocketChannel) channel;
// 根據指定的Byte類型數組,創建對應的ByteBuffer緩衝區
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
// 發送數據
socketChannel.write(buffer);
}
}
}
public static void main(String[] args) throws IOException {
new ChatServer().start();
}
}
客戶端:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* NIO 非阻塞狀態的TCP聊天室客戶端核心代碼
*
* @author Anonymous 2020/3/16 16:20
*/
public class ChatClient {
/**
* 服務器IP地址
*/
private static final String HOST = "192.168.31.154";
/**
* 服務器連接對應的端口號
*/
private static final int PORT = 8848;
/**
* 返回NIO要求是ScoketChannel對象
*/
private SocketChannel socket;
/**
* 用戶名
*/
private String userName;
/**
* 客戶端構造方法,創建客戶端對象
*
* @param userName 指定的用戶名
*/
public ChatClient(String userName) throws IOException, InterruptedException {
// 1. 打開SocketChannel
socket = SocketChannel.open();
// 2. 設置非阻塞狀態
socket.configureBlocking(false);
// 3. 根據指定的HOST IP地址和對應PORT端口號創建對應的 InetSocketAddress
InetSocketAddress address = new InetSocketAddress(HOST, PORT);
// 4. 連接服務器
if (!socket.connect(address)) {
// 如果沒有連接到服務器,保持請求連接的狀態
while (!socket.finishConnect()) {
System.out.println("服務器請求連接失敗,等待2s繼續請求連接...");
Thread.sleep(2000);
}
}
this.userName = userName;
System.out.println("客戶端 " + userName + " 準備就緒");
}
/*
這裏需要完成兩個方法,一個是發送數據到服務器,一個是接受服務器發送的數據
*/
/**
* 發送數據到服務器,用於廣播消息,羣聊
*
* @param message 指定的消息
*/
public void sendMsg(String message) throws IOException {
// 斷開服務器連接 close
if ("close".equals(message)) {
socket.close();
return;
}
/*
StringBuffer
線程安全,效率低
StringBuilder
線程不安全,效率高
*/
message = userName + ":" + message;
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
socket.write(buffer);
}
public void receiveMsg() throws IOException {
// 準備ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
int length = socket.read(buffer);
if (length > 0) {
System.out.println(new String(buffer.array()));
}
}
}
客戶端線程:
package com.qfedu.b_niochat;
import java.io.IOException;
import java.util.Scanner;
/**
* 客戶端線程代碼
*
* @author Anonymous 2020/3/16 17:37
*/
public class ChatClientThread {
public static void main(String[] args) throws IOException, InterruptedException {
Scanner scanner = new Scanner(System.in);
System.out.println("請輸入用戶名:");
String userName = scanner.nextLine();
if (0 == userName.length()) {
return;
}
ChatClient chatClient = new ChatClient(userName);
// 接收消息
new Thread(() -> {
while (true) {
try {
chatClient.receiveMsg();
Thread.sleep(2000);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// 發送消息
while (scanner.hasNextLine()) {
String msg = scanner.nextLine();
chatClient.sendMsg(msg);
}
}
}
上面代碼中ip需要修改爲自己的ip,端口號改不改皆可,其餘地方無需修改