Java NIO實現(xiàn)群聊系統(tǒng)
本文實例為大家分享了Java NIO實現(xiàn)群聊系統(tǒng)的具體代碼,供大家參考,具體內(nèi)容如下
前面的文章介紹了NIO的三大核心組件并編寫了BIO的一個demo實例,本文使用NIO寫一個小應(yīng)用實例,鞏固并加深對NIO的理解。
實例要求:
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)目的:進(jìn)一步理解NIO非阻塞網(wǎng)絡(luò)編程機制
服務(wù)端代碼:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
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();
}
}
//監(jiān)聽
public void listen() {
System.out.println("監(jiān)聽線程: " + Thread.currentThread().getName());
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ā)生異常處理....
}
}
//讀取客戶端消息
private void readData(SelectionKey key) {
//取到關(guān)聯(lián)的channle
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ā)消息中...");
System.out.println("服務(wù)器轉(zhuǎn)發(fā)數(shù)據(jù)給客戶端線程: " + Thread.currentThread().getName());
//遍歷 所有注冊到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();
}
}
//可以寫一個Handler
class MyHandler {
public void readData() {
}
public void sendInfoToOtherClients(){
}
}
客戶端代碼:
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;
import java.util.Set;
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("127.0.0.1", 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);
}
}
}
注意:必須設(shè)置通道為非阻塞,才能向Selector注冊,否則報 java.nio.channels.IllegalBlockingModeException 錯
注意:在客戶端上要想獲取得到服務(wù)端的數(shù)據(jù),也需要注冊在register上(監(jiān)聽讀事件)
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java ScheduledExecutorService定時任務(wù)案例講解
這篇文章主要介紹了Java ScheduledExecutorService定時任務(wù)案例講解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
詳談java中int和Integer的區(qū)別及自動裝箱和自動拆箱
這篇文章主要介紹了詳談java中int和Integer的區(qū)別及自動裝箱和自動拆箱,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
深入了解Springboot核心知識點之?dāng)?shù)據(jù)訪問配置
這篇文章主要為大家介紹了Springboot核心知識點中的數(shù)據(jù)訪問配置,文中的示例代碼講解詳細(xì),對我們了解SpringBoot有一定幫助,快跟隨小編一起學(xué)習(xí)一下吧2021-12-12
spring boot openfeign從此和httpClient說再見詳析
這篇文章主要給大家介紹了關(guān)于spring boot openfeign從此和httpClient說再見的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧2018-06-06
Dom4j解析XML_動力節(jié)點Java學(xué)院整理
這篇文章主要介紹了Dom4j解析XML,dom4j是一個Java的XML API,類似于jdom,用來讀寫XML文件的,有興趣的可以了解一下2017-07-07
Java synchronized線程交替運行實現(xiàn)過程詳解
這篇文章主要介紹了Java synchronized線程交替運行實現(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-11-11

