1. 服務器端代碼
ChatServer類:
package nio.test.server;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.Set;
public class ChatServer {
private static final int DEFAULT_PORT = 8888;
private static final String QUIT = "quit";
private static final int BUFFER = 1024;
private ServerSocketChannel serverSocketChannel; //服務器端用於處理IO的通道
private Selector selector;
private ByteBuffer byteBufferReader = ByteBuffer.allocate(BUFFER); //用來讀取消息
private ByteBuffer byteBufferWriter = ByteBuffer.allocate(BUFFER); //用來轉發消息時寫入其他通道的緩衝區
private Charset charset = Charset.forName("UTF-8"); //標準化編碼解碼
private int port;
public ChatServer(){
this(DEFAULT_PORT);
}
public ChatServer(int port){
this.port = port;
}
private void start(){
try {
serverSocketChannel = ServerSocketChannel.open(); //創建服務器套接字通道
serverSocketChannel.configureBlocking(false); //設置爲非阻塞式調用
serverSocketChannel.socket().bind(new InetSocketAddress(port));
selector = Selector.open(); //打開選擇器
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("啓動服務器,監聽端口:" + port + "...");
while (true) {
selector.select();
//selectionKeys包含了select()接收到的所有事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for(SelectionKey key : selectionKeys){
//處理被觸發的事件
handles(key);
}
selectionKeys.clear(); //把集合清空
}
} catch (IOException e) {
e.printStackTrace();
}finally {
close(selector);//啓到既關閉selector又關閉通道的作用
}
}
/**
* 處理被觸發的事件
* @param key 每當通道被選擇器註冊時,都會創建一個選擇鍵
* @throws IOException
*/
private void handles(SelectionKey key) throws IOException {
// 觸發 ACCEPT事件 --- 和客戶端建立了連接
if(key.isAcceptable()){
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
System.out.println(getClientName(client) + "已連接");
}
// 觸發 READ事件 --- 客戶端發送了消息給服務器端
else if(key.isReadable()){
SocketChannel client = (SocketChannel) key.channel();
String fwdMsg = receive(client); //讀取客戶端消息
if(fwdMsg.isEmpty()){ //客戶端異常
key.cancel(); //不再監視這個通道上的read事件
selector.wakeup();
}else {
forwardMessage(client, fwdMsg); //轉發客戶端消息
// 檢查用戶是否退出
if(readyToQuit(fwdMsg)){
key.cancel();//解除監聽
selector.wakeup();
System.out.println(getClientName(client) + "已斷開");
}
}
}
}
/**
* 用於轉發消息
* @param client
* @param fwdMsg
* @throws IOException
*/
private void forwardMessage(SocketChannel client, String fwdMsg) throws IOException {
for(SelectionKey key : selector.keys()){
Channel connectedClient = key.channel();
if(connectedClient instanceof ServerSocketChannel) continue;
if(key.isValid() && !client.equals(connectedClient)) {
byteBufferWriter.clear();
byteBufferWriter.put(charset.encode((getClientName(client)) + ":" + fwdMsg));
byteBufferWriter.flip(); //寫轉讀
while(byteBufferWriter.hasRemaining()){
((SocketChannel)connectedClient).write(byteBufferWriter);
}
}
}
}
private String receive(SocketChannel client) throws IOException {
byteBufferReader.clear();
while(client.read(byteBufferReader) > 0);
byteBufferReader.flip();
return String.valueOf(charset.decode(byteBufferReader));
}
private String getClientName(SocketChannel client){
return "客戶端[" + client.socket().getPort() + "]";
}
private boolean readyToQuit(String msg){
return QUIT.equals(msg);
}
private void close(Closeable closeable){
if(closeable != null){
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ChatServer chatServer = new ChatServer(6666);
chatServer.start();
}
}
2. 客戶端代碼
ChatClient類:
package nio.test.client;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Set;
public class ChatClient {
private static final String DEFAULT_SERVER_HOST = "127.0.0.1";
private static final int DEFAULT_SERVER_PORT = 6666;
private static final String QUIT = "quit";
private static final int BUFFER = 1024;
private String host;
private int port;
private SocketChannel client;
private ByteBuffer byteBufferReader = ByteBuffer.allocate(BUFFER);
private ByteBuffer byteBufferWriter = ByteBuffer.allocate(BUFFER);
private Selector selector;
private Charset charset = Charset.forName("UTF-8");
public ChatClient(){
this(DEFAULT_SERVER_HOST, DEFAULT_SERVER_PORT);
}
public ChatClient(String host, int port){
this.host = host;
this.port = port;
}
public boolean readyToQuit(String msg){
return QUIT.equals(msg);
}
private void close(Closeable closeable){
if(closeable != null){
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void start(){
try {
client = SocketChannel.open();
client.configureBlocking(false);
selector = Selector.open();
client.register(selector, SelectionKey.OP_CONNECT);
client.connect(new InetSocketAddress(host, port));
while(true){
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for(SelectionKey key : selectionKeys){
handles(key);
}
selectionKeys.clear();
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClosedSelectorException e){
//用戶正常退出
}finally {
close(selector);
}
}
private void handles(SelectionKey key) throws IOException {
// CONNECT事件 連接就緒事件
if(key.isConnectable()){
SocketChannel client = (SocketChannel)key.channel();
if(client.isConnectionPending()){//連接處於就緒狀態
client.finishConnect();
// 處理用戶的輸入信息
new Thread(new UserInputHandler(this)).start();
}
client.register(selector, SelectionKey.OP_READ);
}
// READ事件 服務器轉發消息
else if(key.isReadable()){
SocketChannel client = (SocketChannel)key.channel();
String msg = receive(client);
if(msg.isEmpty()){
// 服務器出現異常
close(selector);
}else{
System.out.println(msg);
}
}
}
public void send(String msg) throws IOException {
if(msg.isEmpty()){
return ;
}else{
byteBufferWriter.clear();
byteBufferWriter.put(charset.encode(msg));
byteBufferWriter.flip();
while(byteBufferWriter.hasRemaining()){
client.write(byteBufferWriter);
}
//檢查用戶是否準備退出
if(readyToQuit(msg)){
close(selector);
}
}
}
private String receive(SocketChannel client) throws IOException {
byteBufferReader.clear();
while(client.read(byteBufferReader) > 0);
byteBufferReader.flip();
return String.valueOf(charset.decode(byteBufferReader));
}
public static void main(String[] args) {
ChatClient chatClient = new ChatClient();
chatClient.start();
}
}
UserInputHandler類:
package nio.test.client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class UserInputHandler implements Runnable{
private ChatClient chatclient;
public UserInputHandler(ChatClient chatClient){
this.chatclient = chatClient;
}
/**r
*
*/
@Override
public void run() {
try {
//等待用戶輸入的消息
BufferedReader consoleReader = new BufferedReader(
new InputStreamReader(System.in)
);
while(true){
String input = consoleReader.readLine();
//向服務器發送消息
chatclient.send(input);
//檢查用戶是否準備退出
if(chatclient.readyToQuit(input)){
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3. 執行效果截圖