????本系列主要介紹java網(wǎng)絡(luò)編程的模型翁逞,沿著模型的進化線結(jié)合案例分析學(xué)習(xí)砸民。本文主要介紹基于OIO的網(wǎng)絡(luò)編程模型孤个。
基于BIO(OIO)的網(wǎng)絡(luò)編程模型
????BIO(OIO)是指阻塞輸入輸出流(舊輸入輸出流)翁锡,基于阻塞輸入輸出流的網(wǎng)絡(luò)編程初始具有以下特點:
1掐松、服務(wù)端啟動后會阻塞直到監(jiān)測到有客戶端連接众旗;
2罢杉、客戶端發(fā)起連接請求,獲取輸入流讀取數(shù)據(jù)贡歧,如果沒有數(shù)據(jù)可讀取將會一直處于阻塞狀態(tài)滩租;
3、所有連接處理都在一個線程中進行利朵,因此在連接數(shù)較多時律想,連接請求需要排隊等候;
案例——服務(wù)端:
package javanio.oionet;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
/**
* @author 54353
* OIO網(wǎng)絡(luò)模型服務(wù)端:
* 向客戶端寫入服務(wù)端接收時間
*/
public class TestServer {
public static void main(String[] args) {
try (ServerSocket serverSocket=new ServerSocket(12121)){
while(true) {//1 循環(huán)等待連接
//2 accept()阻塞直到有連接進來绍弟,返回對等端socket對象
Socket client=serverSocket.accept();
System.out.println("接收到客戶端請求<技础!");
Writer bWriter=new OutputStreamWriter(client.getOutputStream(),"ASCII");
Date date=new Date();
//3 輸出流中寫入時間
while(true) {//確保任務(wù)不停進行
bWriter.write(date.toString()+'\r'+'\n');
//4 確保數(shù)據(jù)寫入
bWriter.flush();
System.out.println("數(shù)據(jù)傳輸完成樟遣!");
}
}
} catch (Exception e) {
System.out.println("端口可能被占用而叼!");
}
}
}
案例——客戶端
package javanio.oionet;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;
public class TestClient2 {
public static void main(String[] args) {
Socket socket=null;
try {
socket=new Socket("127.0.0.1", 12121);
InputStreamReader reader=new InputStreamReader(socket.getInputStream(),"ASCII");
System.out.println("讀取服務(wù)端傳送過來的數(shù)據(jù)!");
for(int c=reader.read();c!=-1;c=reader.read()) {
System.out.print((char)c);
}
} catch (UnknownHostException e) {
System.out.println("請求的主機地址不存在豹悬!");
}catch (Exception e) {
e.printStackTrace();
}finally {
if (socket!=null) {
try {
socket.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
}
}
????這里客戶端同樣的demo有三個葵陵,啟動服務(wù)端,啟動客戶端后瞻佛,服務(wù)端輸出結(jié)果如下:
客戶端2輸出結(jié)果如下:
客戶端3輸出結(jié)果如下:
????TestClient2脱篙,TestClient3啟動后,TestClient2不斷輸出服務(wù)端寫入的時間伤柄,TestClient3卻沒有輸出任何的時間數(shù)據(jù)绊困。這是因為所有客戶端連接共享一個服務(wù)端線程,服務(wù)端在不斷向TestClient2輸出數(shù)據(jù)适刀,TestClient3必須等到TestClient2的請求結(jié)束秤朗,服務(wù)端向其輸出數(shù)據(jù)后才能輸出時間數(shù)據(jù)。這也是本文一開始提到的第三個特點蔗彤。
針對這種模式存在的缺陷川梅,基于OIO提出了多線程的模型,也即是服務(wù)端針對每一個請求開啟一個新的線程去處理數(shù)據(jù):
服務(wù)端的改進:
package javanio.oionet;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
/**
* @author 54353
* OIO網(wǎng)絡(luò)模型服務(wù)端:
* 向客戶端寫入服務(wù)端接收時間
*/
public class TestServer2 {
static class Task implements Runnable{
private Socket client;
public Task(Socket client) {
this.client=client;
}
@Override
public void run() {
System.out.println("接收到客戶端請求H欢簟贫途!");
Writer bWriter;
try {
bWriter = new OutputStreamWriter(client.getOutputStream(),"ASCII");
Date date=new Date();
//4 輸出流中寫入時間
while(true) {//任務(wù)不斷進行
bWriter.write(date.toString()+'\r'+'\n');
//5 確保數(shù)據(jù)寫入
bWriter.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try (ServerSocket serverSocket=new ServerSocket(12122)){
while(true) {//1 循環(huán)等待連接
//2 accept()阻塞直到有連接進來,返回對等端socket對象
Socket client=serverSocket.accept();
//3 一旦有連接進來待侵,開啟新的線程處理連接
new Thread(new Task(client)).start();
}
} catch (Exception e) {
System.out.println("端口可能被占用丢早!");
}
}
}
????在運用多線程處理連接后,輸出結(jié)果下:
服務(wù)端輸出
幾個客戶端輸出結(jié)果相同
????由此可見,運用多線程OIO模型可以解決怨酝,多請求同時發(fā)生時的排隊等候問題傀缩,可以帶來更好的客戶體驗。但是如果線程數(shù)過多农猬,對內(nèi)存的消耗大赡艰,同時線程數(shù)過多,線程輪轉(zhuǎn)帶來的消耗也會非常大斤葱,因此肆無忌憚的使用多線程去處理并發(fā)請求問題只能算是一種蠻干慷垮,對于高并發(fā)場景,更是會帶來災(zāi)難性危害揍堕。
關(guān)于Reactor模式
????Reactor模式也即響應(yīng)模式料身,就本文所涉及的網(wǎng)絡(luò)編程而言,只有獲取到客戶端的連接請求后衩茸,服務(wù)端才執(zhí)行后續(xù)的程序芹血。對于OIO阻塞輸入輸出流,顯然還存在一個明顯缺陷:如果一直未接收到客戶端請求楞慈,服務(wù)端一直處于阻塞狀態(tài)幔烛,后面與接入無關(guān)的代碼也需要等待,這顯然會浪費很多的CPU時間抖部,因此從這點考慮说贝,多線程的OIO模式仍存在進步空間。