1. BIO、NIO、AIO介紹
在不同系統(tǒng)或進(jìn)程間數(shù)據(jù)交互,或高并發(fā)場(chǎng)景下都選喲網(wǎng)絡(luò)通信。早期是基于性能低下的同步阻塞IO(BIO)實(shí)現(xiàn)。后支持非阻塞IO(NIO)。
前置須知:javsse,java多線程,javaIO,java網(wǎng)絡(luò)模型
目的:局域網(wǎng)內(nèi)通信,多系統(tǒng)間底層消息傳遞機(jī)制,高并發(fā)下大數(shù)據(jù)通信,游戲應(yīng)用。
2 .java的io演進(jìn)
2.1 IO模型基本說(shuō)明
IO模型:性能取決于用什么通信模式或架構(gòu)進(jìn)行數(shù)據(jù)傳輸和接收。java共支持3種網(wǎng)絡(luò)編程的IO,見(jiàn)標(biāo)題
2.2 IO模型
javaBIO
同步并阻塞。服務(wù)器一個(gè)鏈接一個(gè)線程,即客戶端請(qǐng)求服務(wù)器只啟動(dòng)一個(gè)線程處理,如果鏈接空閑則浪費(fèi)線程開(kāi)銷。
javaNIO
同步非阻塞。服務(wù)器一個(gè)線程處理多個(gè)鏈接(請(qǐng)求)。即客戶端請(qǐng)求都會(huì)注冊(cè)到多路復(fù)用上。輪詢到有IO請(qǐng)求就進(jìn)行處理。
javaAIO(NIO2.0版本)
異步非阻塞。服務(wù)器一個(gè)有效請(qǐng)求一個(gè)線程,客戶端IO請(qǐng)求都由OS先完成了在通知服務(wù)器創(chuàng)建線程處理,一般用于連接數(shù)較多且鏈接時(shí)間較長(zhǎng)應(yīng)用。
2.3 BIO、NIO、AIO使用場(chǎng)景
- BIO:連接小且固定架構(gòu)。JDK1.4前唯一選擇,簡(jiǎn)單。
- NIO:連接多且較短架構(gòu)。比如聊天,彈幕,服務(wù)間通訊等,JDK1.4后支持,復(fù)雜。
- AIO:連接多且較長(zhǎng)家都。比如相冊(cè)服務(wù),充分調(diào)用OS參與并發(fā),JDK1.7后支持,復(fù)雜。
3. Java BIO
3.1 BIO介紹
相關(guān)類接口間java.io。一個(gè)鏈接創(chuàng)建一個(gè)線程??赏ㄟ^(guò)線程池優(yōu)化成多客戶端鏈接。
3.2 BIO機(jī)制
3.3 傳統(tǒng)的BIO編程實(shí)例
網(wǎng)絡(luò)編程CS架構(gòu)實(shí)現(xiàn)兩個(gè)進(jìn)程間通信,服務(wù)端提供IP+PORT,客戶端通過(guò)鏈接操作向服務(wù)端監(jiān)聽(tīng)的端口地址發(fā)起請(qǐng)求?;赥CP三次握手建立鏈接,通過(guò)套接字Socket進(jìn)行通信。
同步阻塞種服務(wù)端serverSocket負(fù)責(zé)綁定IP地址,啟動(dòng)監(jiān)聽(tīng)端口??蛻舳薙ocket負(fù)責(zé)發(fā)起請(qǐng)求。通過(guò)輸入輸出流進(jìn)行同步阻塞通信。
特點(diǎn):C/S完全同步,耦合。
public class Client {
public static void main(String[] args) throws IOException {
//1.創(chuàng)建socket對(duì)象請(qǐng)求服務(wù)端的鏈接
Socket socket = new Socket("127.0.0.1", 9999);
//2.從socket對(duì)象中獲取一個(gè)字節(jié)輸出流
OutputStream os = socket.getOutputStream();
//3.把字節(jié)輸出流包裝成一個(gè)打印流
PrintStream ps = new PrintStream(os);
ps.println("hello world!服務(wù)端");
ps.flush();
}
}
/**
* 目標(biāo):客戶端發(fā)送消息,服務(wù)端接受消息
*/
public class Server {
public static void main(String[] args) {
try {
System.out.println("服務(wù)端啟動(dòng)");
//1.定義ServerSocket對(duì)象驚醒服務(wù)器端口注冊(cè)
ServerSocket serverSocket = new ServerSocket(9999);
//2.監(jiān)聽(tīng)客戶端的Socket鏈接請(qǐng)求
Socket socket = serverSocket.accept();
//3.從socket管道中得到一個(gè)字節(jié)輸入流對(duì)象
InputStream is = socket.getInputStream();
//4.把字節(jié)輸入流包裝成一個(gè)緩沖字符輸入流 要以行為單位讀取
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg;
if ((msg = br.readLine()) != null) {
System.out.println("服務(wù)端接收到:" + msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
小結(jié):
- 在以上通信中,服務(wù)端會(huì)一直等待客戶端的消息,如果客戶端沒(méi)有進(jìn)行消息的發(fā)送,服務(wù)端將一直進(jìn)入阻塞狀態(tài)
- 同時(shí)服務(wù)端是按照行 獲取信息的,客戶端也必須按照行 發(fā)送。否則服務(wù)端進(jìn)入等待消息的阻塞態(tài)。
3.4 BIO實(shí)現(xiàn)多發(fā)和多收消息
在3中,只能客戶端發(fā)送消息,服務(wù)端接收消息,并不能反復(fù)的接收和發(fā)送消息。改進(jìn):
Client:
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while(true) {
System.out.println("input:");
String msg = sc.nextLine();
ps.println(msg);
ps.flush();
}
Server:
while ((msg = br.readLine()) != null) {
System.out.println("服務(wù)端接收到:" + msg);
}
小結(jié):
- 服務(wù)端只能處理一個(gè)客戶端請(qǐng)求,因?yàn)閱尉€程,一次只能與一個(gè)客戶端進(jìn)行消息通信。
3.5 BIO下接收多個(gè)客戶端
需在服務(wù)端引入多線程解決多客戶端請(qǐng)求。這樣就實(shí)現(xiàn)了一個(gè)客戶端一個(gè)線程模型。
/**
* 目標(biāo):實(shí)現(xiàn)服務(wù)端同時(shí)處理多個(gè)客戶端的socket連接
* 實(shí)現(xiàn):服務(wù)端每接收一個(gè)客戶端socket請(qǐng)求對(duì)象后都交給一個(gè)獨(dú)立線程處理客戶端的數(shù)據(jù)交互。
*/
public class Server {
public static void main(String[] args) {
try{
//1.注冊(cè)端口
ServerSocket ss = new ServerSocket(9999);
//2.定義死循環(huán),不斷接收客戶端的Socket鏈接
while(true) {
Socket socket = ss.accept();
//3.創(chuàng)建一獨(dú)立的線程來(lái)處理與這個(gè)客戶端的socket通信
new ThreadServerReader(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class ThreadServerReader extends Thread {
private Socket socket;
public ThreadServerReader(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
//從socket對(duì)象中得到一個(gè)字節(jié)輸入流
InputStream is = socket.getInputStream();
//使用緩沖字符輸入流包裝字節(jié)輸入流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg;
while ((msg = br.readLine()) != null) {
System.out.println(msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
小結(jié):
- 每個(gè)Socket接收都會(huì)創(chuàng)建線程,線程競(jìng)爭(zhēng),切換上下文會(huì)影響性能。
- 每個(gè)線程都會(huì)占用??臻g和CPU資源
- 并不是每個(gè)Socket都進(jìn)行IO操作,無(wú)意義的線程處理
- 客戶端的并發(fā)訪問(wèn)增加時(shí)。服務(wù)端將呈現(xiàn)1:1的線程開(kāi)銷,訪問(wèn)量越大,系統(tǒng)將發(fā)生線程棧溢出,線程創(chuàng)建失敗,最終導(dǎo)致進(jìn)程宕機(jī)或僵死。
3.6 偽異步IO編程
采用線程池和任務(wù)隊(duì)列實(shí)現(xiàn),當(dāng)客戶端接入,將Socket封裝成一個(gè)Task交由后端的線程池進(jìn)行處理。JDK線程池維護(hù)一個(gè)消息隊(duì)列和N個(gè)活躍線程。對(duì)消息隊(duì)列中socket任務(wù)進(jìn)行處理,由于線程池可設(shè)置消息隊(duì)列的大小和最大線程數(shù),因此,資源可控,無(wú)論多少客戶端并發(fā)訪問(wèn),都不會(huì)資源不夠。
/**
* 目標(biāo):偽異步通信架構(gòu)
*/
public class Server {
public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(9999);
//初始化線程池對(duì)象
HandlerSocketServerPool pool = new HandlerSocketServerPool(6, 10);
while(true) {
Socket socket = ss.accept();
//把socket封裝成任務(wù)對(duì)象交給一個(gè)線程池來(lái)處理
Runnable target = new ServerRunnableTarget(socket);
pool.execute(target);
}
} catch (Exception e){
e.printStackTrace();
}
}
}
//線程池
public class HandlerSocketServerPool {
//1.創(chuàng)建一個(gè)線程池
private ExecutorService excutorService;
//2.初始化
public HandlerSocketServerPool(int maxThreadNum, int queueSize) {
excutorService = new ThreadPoolExecutor(3, maxThreadNum, 120, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueSize));
}
//3.提供方法提交任務(wù)給線程池的任務(wù)來(lái)暫存,等待線程池來(lái)執(zhí)行
public void execute(Runnable target){
excutorService.execute(target);
}
}
//功能
public class ServerRunnableTarget implements Runnable {
private Socket socket;
public ServerRunnableTarget(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
//處理客戶端socket請(qǐng)求
try {
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg;
if ((msg = br.readLine()) != null) {
System.out.println(msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
小結(jié):
- 偽異步io采用線程池實(shí)現(xiàn),因此避免了為每個(gè)請(qǐng)求都創(chuàng)建獨(dú)立線程造成資源耗盡的問(wèn)題,由于底層還是同步阻塞,沒(méi)解決根本問(wèn)題。
- 如果單個(gè)消息處理緩慢,或服務(wù)器全部阻塞。那么后面的socket的io消息都將在隊(duì)列中排隊(duì),新的socket將被拒絕,客戶端會(huì)大量鏈接超時(shí)。
3.7 基于BIO形勢(shì)下的文件上傳
/**
* 實(shí)現(xiàn)客戶端任意類型文件給服務(wù)端保存
*/
public class Client {
public static void main(String[] args) {
try {
Socket socket = new Socket("127.0.0.1", 8888);
//把字節(jié)輸出流包裝成一個(gè)數(shù)據(jù)輸出流
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
//發(fā)送文件后綴給服務(wù)端
dos.writeUTF(".png");
//把文件數(shù)據(jù)發(fā)送給服務(wù)端
InputStream is = new FileInputStream("http://Path");
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) > 0) {
dos.write(buffer, 0, len);
}
dos.flush();
//通知服務(wù)端接收完畢
socket.shutdownOutput();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 接收客戶端任意類型文件并保存
*/
public class Server {
public static void main(String[] args) {
try {
ServerSocket socket = new ServerSocket(8888);
while (true) {
Socket accept = socket.accept();
//交給獨(dú)立線程來(lái)處理與這個(gè)客戶端的文件通信需求
new ServerReaderThread(accept).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class ServerReaderThread extends Thread {
private Socket socket;
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
//得到數(shù)據(jù)輸入流讀取客戶端發(fā)送過(guò)來(lái)的數(shù)據(jù)
DataInputStream dis = new DataInputStream(socket.getInputStream());
//讀取客戶端發(fā)送的文件類型
String suffix = dis.readUTF();
System.out.println("收到文件,類型:" + suffix);
//定義字節(jié)輸出管道,負(fù)責(zé)把客戶端發(fā)過(guò)來(lái)的數(shù)據(jù)寫(xiě)出
FileOutputStream os = new FileOutputStream("C:\\path\\" + UUID.randomUUID().toString() + suffix);
//從數(shù)據(jù)輸入流中讀取文件數(shù)據(jù),寫(xiě)出到字節(jié)輸出流中
byte[] buffer = new byte[1024];
int len;
while ((len = dis.read(buffer)) > 0) {
os.write(buffer, 0, len);
}
os.close();
System.out.println("保存文件成功");
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.8 javaBIO下的端口轉(zhuǎn)發(fā)
需求:一個(gè)客戶端消息可發(fā)送所有的客戶端接收(群聊)
/**
* BIO下服務(wù)端端口轉(zhuǎn)發(fā)
* 服務(wù)端需求:
* 1.注冊(cè)端口
* 2.收到客戶端的socket鏈接,交給獨(dú)立的線程來(lái)處理
* 3.把當(dāng)前連接的客戶端socket存入到一個(gè)所謂的在線socket集合中保存
* 4.接收客戶端消息,然后推送給當(dāng)前所有在線的socket接收
*/
public class Server {
//定義靜態(tài)集合
public static List<Socket> allSocketOnLine = new ArrayList<>();
public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(9999);
while(true){
Socket socket = ss.accept();
//把登陸的客戶端socket存入到一個(gè)在線集合中去
allSocketOnLine.add(socket);
//為當(dāng)前登錄成功的socket分配一個(gè)獨(dú)立的線程來(lái)處理與之通信
new ServerReaderThread(socket).start();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
public class ServerReaderThread extends Thread {
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run(){
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg;
while ((msg = br.readLine())!=null){
//服務(wù)端接收到客戶端的消息推送給當(dāng)前所有在線socket
sendMsgToAllClient(msg);
}
}catch (Exception e){
System.out.println("當(dāng)前有人下線!");
//從在線socket中移除本socket
Server.allSocketOnLine.remove(socket);
e.printStackTrace();
}
}
//把當(dāng)前客戶端發(fā)來(lái)的消息推送全部在線socket
private void sendMsgToAllClient(String msg) throws IOException {
for (Socket sk : Server.allSocketOnLine) {
PrintStream ps = new PrintStream(sk.getOutputStream());
ps.println(msg);
ps.flush();
}
}
}
3.9 基于BIO下即時(shí)通信
項(xiàng)目功能
需要解決客戶端到客戶端的通信,即實(shí)現(xiàn)客戶端間的端口消息轉(zhuǎn)發(fā)。
功能說(shuō)明:
1.客戶端登陸:輸入用戶名和服務(wù)端ip
2.在線人數(shù)實(shí)時(shí)更新:用戶登錄同步更新客戶端聯(lián)系人列表
3.離線人數(shù)更新:下線同步
4.群聊:任一客戶端消息轉(zhuǎn)發(fā)所有客戶端接收
5.私聊:選擇某一對(duì)象發(fā)送消息
6.@消息:可@該用戶,所有人可見(jiàn)
7.消息用戶和時(shí)間點(diǎn):服務(wù)端記錄用戶消息時(shí)間點(diǎn),然后進(jìn)行多路轉(zhuǎn)發(fā)或選擇。
服務(wù)端設(shè)計(jì)
服務(wù)端接收多個(gè)客戶端
服務(wù)端需要接收多個(gè)客戶端的接入。
- 1.服務(wù)端需要接收多個(gè)客戶端,目前我們采取的策略是一個(gè)客戶端對(duì)應(yīng)一個(gè)服務(wù)端線程。
- 2.服務(wù)端除了要注冊(cè)端口以外,還需要為每個(gè)客戶端分配一個(gè)獨(dú)立線程處理與之通信。
服務(wù)端主體代碼,主要進(jìn)行端口注冊(cè),和接收客戶端,分配線程處理該客戶端請(qǐng)求
服務(wù)端接收登錄消息及檢測(cè)離線
接收客戶端的登陸消息。
實(shí)現(xiàn)步驟
- 需要在服務(wù)端處理客戶端的線程的登陸消息。
- 需要注意的是,服務(wù)端需要接收客戶端的消息可能有很多種。
- 分別是登陸消息,群聊消息,私聊消息 和@消息。
- 這里需要約定如果客戶端發(fā)送消息之前需要先發(fā)送消息的類型,類型我們使用信號(hào)值標(biāo)志(1,2,3)。
- 1代表接收的是登陸消息
- 2代表群發(fā)| @消息
- 3代表了私聊消息
- 服務(wù)端的線程中有異常校驗(yàn)機(jī)制,一旦發(fā)現(xiàn)客戶端下線會(huì)在異常機(jī)制中處理,然后移除當(dāng)前客戶端用戶,把最新的用戶列表發(fā)回給全部客戶端進(jìn)行在線人數(shù)更新。
服務(wù)端接收群聊
接收客戶端發(fā)來(lái)的群聊消息推送給當(dāng)前在線的所有客戶端文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-426637.html
實(shí)現(xiàn)步驟
- 接下來(lái)要接收客戶端發(fā)來(lái)的群聊消息。
- 需要注意的是,服務(wù)端需要接收客戶端的消息可能有很多種。
- 分別是登陸消息,群聊消息,私聊消息 和@消息。
- 這里需要約定如果客戶端發(fā)送消息之前需要先發(fā)送消息的類型,類型我們使用信號(hào)值標(biāo)志(1,2,3)。
- 1代表接收的是登陸消息
- 2代表群發(fā)| @消息
- 3代表了私聊消息
服務(wù)端接收私聊
私聊消息的推送邏輯.文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-426637.html
實(shí)現(xiàn)步驟
- 解決私聊消息的推送邏輯,私聊消息需要知道推送給某個(gè)具體的客戶端
- 我們可以接收到客戶端發(fā)來(lái)的私聊用戶名稱,根據(jù)用戶名稱定位該用戶的Socket管道,然后單獨(dú)推送消息給該Socket管道。
- 需要注意的是,服務(wù)端需要接收客戶端的消息可能有很多種。
- 分別是登陸消息,群聊消息,私聊消息 和@消息。
- 這里需要約定如果客戶端發(fā)送消息之前需要先發(fā)送消息的類型,類型我們使用信號(hào)值標(biāo)志(1,2,3)。
- 1代表接收的是登陸消息
- 2代表群發(fā)| @消息
- 3代表了私聊消息
小結(jié)
demo地址:https://gitee.com/xuyu294636185/JAVA_IO_DEMO.git
- 本節(jié)我們解決了私聊消息的推送邏輯,私聊消息需要知道推送給某個(gè)具體的客戶端Socket管道
- 我們可以接收到客戶端發(fā)來(lái)的私聊用戶名稱,根據(jù)用戶名稱定位該用戶的Socket管道,然后單獨(dú)推送消息給該Socket管道。
客戶端設(shè)計(jì)
到了這里,關(guān)于JAVA的BIO、NIO、AIO模式精解(一)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!