Java基于NIO實現(xiàn)群聊系統(tǒng)
本文實例為大家分享了Java基于NIO實現(xiàn)群聊系統(tǒng)的具體代碼,供大家參考,具體內(nèi)容如下
實例要求:
1.編寫一個 NIO 群聊系統(tǒng),實現(xiàn)服務(wù)器端和客戶端之間的數(shù)據(jù)簡單通訊(非阻塞)
2.實現(xiàn)多人群聊
3.服務(wù)器端:可以監(jiān)測用戶上線,離線,并實現(xiàn)消息轉(zhuǎn)發(fā)功能
4.客戶端:通過 Channel 可以無阻塞發(fā)送消息給其它所有用戶,同時可以接受其它用戶發(fā)送的消息(有服務(wù)器轉(zhuǎn)發(fā)得到)
5.目的:進一步理解 NIO 非阻塞網(wǎng)絡(luò)編程機制
6.示意圖分析和代碼

// 服務(wù)端:
package com.atguigu.nio.groupchat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
public class GroupChatServer {
//定義屬性
private Selector selector;
private ServerSocketChannel listenChannel;
private static final int PORT = 6667;
//構(gòu)造器
//初始化工作
public GroupChatServer() {
try {
//得到選擇器
selector = Selector.open();
//ServerSocketChannel
listenChannel = ServerSocketChannel.open();
//綁定端口
listenChannel.socket().bind(new InetSocketAddress(PORT));
//設(shè)置非阻塞模式
listenChannel.configureBlocking(false);
//將該 listenChannel 注冊到 selector
listenChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
}
}
public void listen() {
try {
//循環(huán)處理
while (true) {
int count = selector.select();
if (count > 0) { //有事件處理
// 遍歷得到 selectionKey 集合
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
//取出 selectionkey
SelectionKey key = iterator.next();
//監(jiān)聽到 accept
if (key.isAcceptable()) {
SocketChannel sc = listenChannel.accept();
sc.configureBlocking(false);
//將該 sc 注冊到 seletor
sc.register(selector, SelectionKey.OP_READ);
//提示
System.out.println(sc.getRemoteAddress() + " 上線 ");
}
if (key.isReadable()) {//通道發(fā)送read事件,即通道是可讀的狀態(tài)
// 處理讀(專門寫方法..)
readData(key);
}
//當(dāng)前的 key 刪除,防止重復(fù)處理
iterator.remove();
}
} else {
System.out.println("等待....");
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//發(fā)生異常處理....
}
}
//讀取客戶端消息
public void readData(SelectionKey key) {
SocketChannel channel = null;
try {
//得到 channel
channel = (SocketChannel) key.channel();
//創(chuàng)建 buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
int count = channel.read(buffer);
//根據(jù) count 的值做處理
if (count > 0) {
//把緩存區(qū)的數(shù)據(jù)轉(zhuǎn)成字符串
String msg = new String(buffer.array());
//輸出該消息
System.out.println("form客戶端:" + msg);
//向其它的客戶端轉(zhuǎn)發(fā)消息(去掉自己),專門寫一個方法來處理
sendInfoToOtherClients(msg, channel);
}
} catch (IOException e) {
try {
System.out.println(channel.getRemoteAddress() + "離線了..");
//取消注冊
key.cancel();
//關(guān)閉通道
channel.close();
} catch (IOException e2) {
e2.printStackTrace();
}
}
}
//轉(zhuǎn)發(fā)消息給其它客戶(通道)
private void sendInfoToOtherClients(String msg, SocketChannel self) throws IOException {
System.out.println("服務(wù)器轉(zhuǎn)發(fā)消息中...");
//遍歷所有注冊到 selector 上的 SocketChannel,并排除 self
for (SelectionKey key : selector.keys()) {
//通過 key 取出對應(yīng)的 SocketChannel
Channel targetChannel = key.channel();
//排除自己
if (targetChannel instanceof SocketChannel && targetChannel != self) {
//轉(zhuǎn)型
SocketChannel dest = (SocketChannel) targetChannel;
//將 msg 存儲到 buffer
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
//將 buffer 的數(shù)據(jù)寫入通道
dest.write(buffer);
}
}
}
public static void main(String[] args) {
//創(chuàng)建服務(wù)器對象
GroupChatServer groupChatServer = new GroupChatServer();
groupChatServer.listen();
}
}
// 客戶端:
package com.atguigu.nio.groupchat;
~~import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
public class GroupChatClient {
//定義相關(guān)的屬性
private final String HOST = "127.0.0.1";//服務(wù)器的ip
private final int PORT = 6667;//服務(wù)器端口
private Selector selector;
private SocketChannel socketChannel;
private String username;
//構(gòu)造器,完成初始化工作
public GroupChatClient() throws IOException {
selector = Selector.open();
//連接服務(wù)器
socketChannel = SocketChannel.open(new InetSocketAddress(HOST, PORT));
//設(shè)置非阻塞
socketChannel.configureBlocking(false);
//將 channel 注冊到selector
socketChannel.register(selector, SelectionKey.OP_READ);
//得到 username
username = socketChannel.getLocalAddress().toString().substring(1);
System.out.println(username + " is ok...");
}
//向服務(wù)器發(fā)送消息
public void sendInfo(String info) {
info = username + " 說:" + info;
try {
socketChannel.write(ByteBuffer.wrap(info.getBytes()));
} catch (IOException e) {
e.printStackTrace();
}
}
//讀取從服務(wù)器端回復(fù)的消息
public void readInfo() {
try {
int readChannels = selector.select();
if (readChannels > 0) {//有可以用的通道
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isReadable()) {
//得到相關(guān)的通道
SocketChannel sc = (SocketChannel) key.channel();
//得到一個 Buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
//讀取
sc.read(buffer);
//把讀到的緩沖區(qū)的數(shù)據(jù)轉(zhuǎn)成字符串
String msg = new String(buffer.array());
System.out.println(msg.trim());
}
}
iterator.remove(); //刪除當(dāng)前的 selectionKey,防止重復(fù)操作
} else {
//System.out.println("沒有可以用的通道...");
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
//啟動我們客戶端
GroupChatClient chatClient = new GroupChatClient();
//啟動一個線程,每個 3 秒,讀取從服務(wù)器發(fā)送數(shù)據(jù)
new Thread() {
public void run() {
while (true) {
chatClient.readInfo();
try {
Thread.currentThread().sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
//發(fā)送數(shù)據(jù)給服務(wù)器端
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String s = scanner.nextLine();
chatClient.sendInfo(s);
}
}
}
運行結(jié)果




以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
編寫Spring MVC控制器的14個技巧(小結(jié))
這篇文章主要介紹了編寫Spring MVC控制器的14個技巧,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
SpringBoot?整合數(shù)據(jù)源的具體實踐
本文主要介紹了SpringBoot?整合數(shù)據(jù)源的具體實踐,利用?Spring?Boot?的自動配置和簡化的注解來簡化數(shù)據(jù)源配置工作,從而更專注于應(yīng)用程序的業(yè)務(wù)邏輯開發(fā),感興趣的可以了解一下2023-11-11
Activiti explorer.war示例工程使用過程圖解
這篇文章主要介紹了Activiti explorer.war示例工程使用過程圖解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-03-03
Java數(shù)據(jù)庫連接池技術(shù)的入門教程
這篇文章主要給大家介紹了關(guān)于Java數(shù)據(jù)庫連接池技術(shù)的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03

