Java - 網(wǎng)絡(luò)IO

發(fā)展歷程

Java1.0開始提供的IO都同步阻塞IO,即BIO
Java1.4開始提供了同步非阻塞IO,即NIO
Java1.7開始出現(xiàn)的NIO2.0版本,真正提供了異步非阻塞IO,即AIO

引申:什么是“同步/異步”?什么是“阻塞/非阻塞”?
一個IO操作其實(shí)分成了兩個步驟:發(fā)起IO請求實(shí)際的IO操作
同步IO異步IO的區(qū)別就在于第二個步驟是否阻塞,如果實(shí)際的IO讀寫阻塞請求進(jìn)程,那么就是同步IO。
阻塞IO非阻塞IO的區(qū)別在于第一步,發(fā)起IO請求是否會被阻塞,如果阻塞直到完成那么就是傳統(tǒng)的阻塞IO,如果不阻塞,那么就是非阻塞IO。

很明顯,通常來說,非阻塞IO比阻塞IO效率高,異步IO比同步IO效率高

BIO

Blocking IO - (同步)阻塞IO

根據(jù)Linux IO模型可知,BIO的IO都是阻塞的。
在此種方式下,用戶進(jìn)程在發(fā)起一個IO操作以后,必須等待IO操作的完成,只有當(dāng)真正完成了IO操作以后,用戶進(jìn)程才能運(yùn)行。JAVA傳統(tǒng)的IO模型屬于此種方式。

BIO的讀寫操作都是阻塞的,即調(diào)用到read()方法時,如果沒有數(shù)據(jù),會一直阻塞等待。BIO對應(yīng)的形象比喻:打電話時,如果對方不說話,本方會一直等待。很顯然,這個效率是很低的。

BIO網(wǎng)絡(luò)編程示例

網(wǎng)絡(luò)編程大部分都是基于C/S模式的。

TCP
TCP Server端使用ServerSocket類,負(fù)責(zé)綁定IP,啟動監(jiān)聽端口。
TCP Client端使用Socket類,負(fù)責(zé)連接Server

UDP
Java通過DatagramPacket類和DatagramSocket類來使用UDP套接字,客戶端和服務(wù)器端都通過DatagramSocketsend()方法和receive()方法來發(fā)送和接收數(shù)據(jù),用DatagramPacket來包裝需要發(fā)送或者接收到的數(shù)據(jù)。
發(fā)送信息時,Java創(chuàng)建一個包含待發(fā)送信息的DatagramPacket實(shí)例,并將其作為參數(shù)傳遞給DatagramSocket實(shí)例的send()方法;接收信息時,Java程序首先創(chuàng)建一個DatagramPacket實(shí)例,該實(shí)例預(yù)先分配了一些空間,并將接收到的信息存放在該空間中,然后把該實(shí)例作為參數(shù)傳遞給DatagramSocket實(shí)例的receive()方法

注意:如果該實(shí)例用來包裝待接收的數(shù)據(jù),則不指定數(shù)據(jù)來源的遠(yuǎn)程主機(jī)和端口,只需指定一個緩存數(shù)據(jù)的byte數(shù)組即可(在調(diào)用receive()方法接收到數(shù)據(jù)后,源地址和端口等信息會自動包含在DatagramPacket實(shí)例中),而如果該實(shí)例用來包裝待發(fā)送的數(shù)據(jù),則要指定要發(fā)送到的目的主機(jī)和端口。

public class ServerSocket implements java.io.Closeable {
    public Socket accept() throws IOException;
    public void bind(SocketAddress endpoint) throws IOException;
    public void close() throws IOException;
}

Client端依賴Socket類,負(fù)責(zé)連接Server

public class Socket implements java.io.Closeable{
    public void bind(SocketAddress bindpoint) throws IOException;
    public void connect(SocketAddress endpoint) throws IOException;
    public synchronized void close() throws IOException;
}
public class DatagramSocket implements java.io.Closeable {
    public synchronized void bind(SocketAddress addr) throws SocketException;
    public void connect(InetAddress address, int port);
    public void send(DatagramPacket p) throws IOException;
    public synchronized void receive(DatagramPacket p) throws IOException;
}

TCP

Server端

單線程模型

//服務(wù)端的端口號
int port = 8080;
//創(chuàng)建服務(wù)端Socket
ServerSocket server = new ServerSocket(port);
//與客戶端建立連接的Socket
Socket socket = null;
while(true){
    //等待客戶端接入
    socket = server.accept();
    //創(chuàng)建InputStream,讀入數(shù)據(jù)
    BufferedReader in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
    //創(chuàng)建OutputStream,寫出數(shù)據(jù)
    PrintWriter out = new PrintWriter(this.socket.getOutputStream());
    //從網(wǎng)絡(luò)中讀取數(shù)據(jù)
    String body = in.readLine();
    //將數(shù)據(jù)寫入到網(wǎng)絡(luò)
    out.println(body);
}

多線程模型

//服務(wù)端的端口號
int port = 8080;
//創(chuàng)建服務(wù)端Socket
ServerSocket server = new ServerSocket(port);
//與客戶端建立連接的Socket
Socket socket = null;
while(true){
    //等待客戶端接入
    socket = server.accept();
    //某個客服端接入后,啟動新的線程,在新線程中與客戶端進(jìn)行讀寫交互
    new Thread(new ServerHandler(socket)).start();
}
public class ServerHandler implements Runnable{
    private Socket socket;
    public ServerHandler(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run(){
        BufferedReader in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
        PrintWriter out = new PrintWriter(this.socket.getOutputStream());
        while(true){
            //從網(wǎng)絡(luò)讀取數(shù)據(jù)
            String body = in.readLine();
            //將數(shù)據(jù)寫入到網(wǎng)絡(luò)
            out.println(body)
        }
    }
}
Client端
String ip = "127.0.0.1";
int port = 8080;
//連接遠(yuǎn)程Server
Socket socket = new Socket(ip, port);
//輸入流
PrintWriter out = new PrintWriter(socket.getOutputStream());
//輸出流
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//向Server發(fā)送信息
out.println("Hello World");
//從Server收到信息
String res = in.readLine();

UDP

UDP的通信建立的步驟

一個典型的UDP客戶端要經(jīng)過下面三步操作:

  1. 創(chuàng)建一個DatagramSocket實(shí)例,可以有選擇地對本地地址和端口號進(jìn)行設(shè)置,如果設(shè)置了端口號,則客戶端會在該端口號上監(jiān)聽從服務(wù)器端發(fā)送來的數(shù)據(jù);
  2. 使用DatagramSocket實(shí)例的send()和receive()方法來發(fā)送和接收DatagramPacket實(shí)例,進(jìn)行通信;
  3. 通信完成后,調(diào)用DatagramSocket實(shí)例的close()方法來關(guān)閉該套接字。

由于UDP是無連接的,因此UDP服務(wù)端不需要等待客戶端的請求以建立連接。另外,UDP服務(wù)器為所有通信使用同一套接字,這點(diǎn)與TCP服務(wù)器不同,TCP服務(wù)器則為每個成功返回的accept()方法創(chuàng)建一個新的套接字。
一個典型的UDP服務(wù)端要經(jīng)過下面三步操作:

  1. 創(chuàng)建一個DatagramSocket實(shí)例,指定本地端口號,并可以有選擇地指定本地地址,此時,服務(wù)器已經(jīng)準(zhǔn)備好從任何客戶端接收數(shù)據(jù)報(bào)文;
  2. 使用DatagramSocket實(shí)例的receive()方法接收一個DatagramPacket實(shí)例,當(dāng)receive()方法返回時,數(shù)據(jù)報(bào)文就包含了客戶端的地址,這樣就知道了回復(fù)信息應(yīng)該發(fā)送到什么地方;
  3. 使用DatagramSocket實(shí)例的send()方法向服務(wù)器端返回DatagramPacket實(shí)例。
image.png
Server端
//端口
int port = 8080;
//創(chuàng)建DatagramSocket
DatagramSocket  server = new DatagramSocket(port);
byte[] recvBuf = new byte[100];
DatagramPacket recvPacket  = new DatagramPacket(recvBuf , recvBuf.length);
//接收數(shù)據(jù)
server.receive(recvPacket);

String sendStr = "Hello ! I'm Server";
byte[] sendBuf;
sendBuf = sendStr.getBytes();
DatagramPacket sendPacket = new DatagramPacket(sendBuf , sendBuf.length , addr , port );
//發(fā)送數(shù)據(jù)
server.send(sendPacket);
Client端
//創(chuàng)建客戶端
DatagramSocket client = new DatagramSocket();
String sendStr = "Hello! I'm Client";
byte[] sendBuf;
sendBuf = sendStr.getBytes();
InetAddress addr = InetAddress.getByName("127.0.0.1");
int port = 8080;
//構(gòu)建發(fā)送數(shù)據(jù)包
DatagramPacket sendPacket = new DatagramPacket(sendBuf ,sendBuf.length , addr , port);
//發(fā)送數(shù)據(jù)
client.send(sendPacket);

byte[] recvBuf = new byte[100];
//構(gòu)建結(jié)束數(shù)據(jù)包
DatagramPacket recvPacket = new DatagramPacket(recvBuf , recvBuf.length);
//接收數(shù)據(jù)
client.receive(recvPacket);
String recvStr = new String(recvPacket.getData() , 0 ,recvPacket.getLength());
System.out.println("收到:" + recvStr);
client.close();

NIO

Non-Blocking IO - 同步非阻塞IO

在此種方式下,用戶進(jìn)程發(fā)起一個IO操作以后便可返回做其它事情,但是用戶進(jìn)程需要時不時的詢問IO操作是否就緒,這就要求用戶進(jìn)程不停的去詢問,從而引入不必要的CPU資源浪費(fèi)。其中目前JAVA的NIO就屬于同步非阻塞IO。

NIO對應(yīng)的形象比喻:打電話時,如果對方?jīng)]有話說,先掛掉電話,干點(diǎn)其他事情。過段時間再打過去,看看對方有沒有話要說。如果對方有話要說,則拿著電話聽對方說話。如此循環(huán)往復(fù)。

Java NIO編程是一個很重要的部分,會做專門的介紹:
Java NIO...

AIO

異步非阻塞IO

適用場景

BIO、NIO、AIO適用場景分析:

  • BIO方式適用于連接數(shù)目比較小且固定的架構(gòu),這種方式對服務(wù)器資源要求比較高,并發(fā)局限于應(yīng)用中,JDK1.4以前的唯一選擇,但程序直觀簡單易理解。
  • NIO方式適用于連接數(shù)目多且連接比較短(輕操作)的架構(gòu),比如聊天服務(wù)器,并發(fā)局限于應(yīng)用中,編程比較復(fù)雜,JDK1.4開始支持。
  • AIO方式使用于連接數(shù)目多且連接比較長(重操作)的架構(gòu),比如相冊服務(wù)器,充分調(diào)用OS參與并發(fā)操作,編程比較復(fù)雜,JDK7開始支持。

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內(nèi)容

  • 本文目的是大概了解 Java 網(wǎng)絡(luò)編程體系,需要一點(diǎn)點(diǎn) Java IO 基礎(chǔ),推薦教程 系統(tǒng)學(xué)習(xí) Java IO。...
    czwbig閱讀 476評論 0 1
  • 計(jì)算機(jī)網(wǎng)絡(luò)概述 網(wǎng)絡(luò)編程的實(shí)質(zhì)就是兩個(或多個)設(shè)備(例如計(jì)算機(jī))之間的數(shù)據(jù)傳輸。 按照計(jì)算機(jī)網(wǎng)絡(luò)的定義,通過一定...
    蛋炒飯_By閱讀 1,235評論 0 10
  • 在一個方法內(nèi)部定義的變量都存儲在棧中,當(dāng)這個函數(shù)運(yùn)行結(jié)束后,其對應(yīng)的棧就會被回收,此時,在其方法體中定義的變量將不...
    Y了個J閱讀 4,427評論 1 14
  • 7.2 面向套接字編程我們已經(jīng)通過了解Socket的接口,知其所以然,下面我們就將通過具體的案例,來熟悉Socke...
    lucas777閱讀 1,194評論 0 2
  • 時間飛逝,歲月如梭,眼看我們就要畢業(yè)了,當(dāng)然我們也準(zhǔn)備了一場畢業(yè)大戲,——《哈姆雷特》。 ...
    張九紫閱讀 697評論 0 1