今日內(nèi)容
- 網(wǎng)絡(luò)編程三要素
- IP
- 端口號
- 協(xié)議
- TCP通信------->掌握
- 模擬兩臺電腦相互之間互發(fā)信息(聊天)
- 模擬文件上傳
- 模擬B/S結(jié)構(gòu)軟件的服務(wù)器
- NIO------>難點\理解
- Buffer緩沖數(shù)組
- Channel通道
- Selector選擇器
- NIO2(AIO)------>難點\理解
- 異步非阻塞
第一章 網(wǎng)絡(luò)編程入門
1.1 軟件結(jié)構(gòu)
- C/S結(jié)構(gòu) :全稱為Client/Server結(jié)構(gòu),是指客戶端和服務(wù)器結(jié)構(gòu)。常見程序有QQ、迅雷等軟件。
- 特點: 客戶端和服務(wù)器是分開的,需要下載客戶端,對網(wǎng)絡(luò)要求相對低,減少服務(wù)器壓力,相對穩(wěn)定, 開發(fā)和維護(hù)成本高

B/S結(jié)構(gòu) :全稱為Browser/Server結(jié)構(gòu),是指瀏覽器和服務(wù)器結(jié)構(gòu)。常見瀏覽器有谷歌、火狐等。
特點:沒有客戶端,只有服務(wù)器,不需要下載客戶端,直接通過瀏覽器訪問, 對網(wǎng)絡(luò)要求相對高 ,服務(wù)器壓力很大,相對不穩(wěn)定,開發(fā)和維護(hù)成本低

兩種架構(gòu)各有優(yōu)勢,但是無論哪種架構(gòu),都離不開網(wǎng)絡(luò)的支持。網(wǎng)絡(luò)編程,就是在一定的協(xié)議下,編寫代碼實現(xiàn)兩臺計算機在網(wǎng)絡(luò)中進(jìn)行通信的程序。
1.2 網(wǎng)絡(luò)編程三要素
協(xié)議
網(wǎng)絡(luò)通信協(xié)議:通信協(xié)議是計算機必須遵守的規(guī)則,只有遵守這些規(guī)則,計算機之間才能進(jìn)行通信。這就好比在道路中行駛的汽車一定要遵守交通規(guī)則一樣,協(xié)議中對數(shù)據(jù)的傳輸格式、傳輸速率、傳輸步驟等做了統(tǒng)一規(guī)定,通信雙方必須同時遵守,最終完成數(shù)據(jù)交換。
java.net 包中提供了兩種常見的網(wǎng)絡(luò)協(xié)議的支持:
- TCP:傳輸控制協(xié)議 (Transmission Control Protocol)。TCP協(xié)議是面向連接的通信協(xié)議,即傳輸數(shù)據(jù)之前,在發(fā)送端和接收端建立邏輯連接,然后再傳輸數(shù)據(jù),它提供了兩臺計算機之間可靠無差錯的數(shù)據(jù)傳輸。
- TCP協(xié)議特點: 面向連接,傳輸數(shù)據(jù)安全,傳輸速度慢
- 例如: 村長發(fā)現(xiàn)張三家的牛丟了
- TCP協(xié)議: 村長一定要找到張三,面對面的告訴他他家的牛丟了 打電話: 電話一定要接通,并且是張三接的
- 連接三次握手:TCP協(xié)議中,在發(fā)送數(shù)據(jù)的準(zhǔn)備階段,客戶端與服務(wù)器之間的三次交互,以保證連接的可靠。
- 第一次握手,客戶端向服務(wù)器端發(fā)出連接請求,等待服務(wù)器確認(rèn)。 你愁啥?
- 第二次握手,服務(wù)器端向客戶端回送一個響應(yīng),通知客戶端收到了連接請求。我愁你咋地?
- 第三次握手,客戶端再次向服務(wù)器端發(fā)送確認(rèn)信息,確認(rèn)連接。整個交互過程如下圖所示。你再愁試試
- 連接三次握手:TCP協(xié)議中,在發(fā)送數(shù)據(jù)的準(zhǔn)備階段,客戶端與服務(wù)器之間的三次交互,以保證連接的可靠。

? 完成三次握手,連接建立后,客戶端和服務(wù)器就可以開始進(jìn)行數(shù)據(jù)傳輸了。由于這種面向連接的特性,TCP協(xié)議可以保證傳輸數(shù)據(jù)的安全,所以應(yīng)用十分廣泛,例如下載文件、瀏覽網(wǎng)頁等。
- UDP:用戶數(shù)據(jù)報協(xié)議(User Datagram Protocol)。UDP協(xié)議是一個面向無連接的協(xié)議。傳輸數(shù)據(jù)時,不需要建立連接,不管對方端服務(wù)是否啟動,直接將數(shù)據(jù)、數(shù)據(jù)源和目的地都封裝在數(shù)據(jù)包中,直接發(fā)送。每個數(shù)據(jù)包的大小限制在64k以內(nèi)。它是不可靠協(xié)議,因為無連接,所以傳輸速度快,但是容易丟失數(shù)據(jù)。日常應(yīng)用中,例如視頻會議、QQ聊天等。
- UDP特點: 面向無連接,傳輸數(shù)據(jù)不安全,傳輸速度快
- 例如: 村長發(fā)現(xiàn)張三家的牛丟了
- UDP協(xié)議: 村長在村里的廣播站廣播一下張三家的牛丟了,信息丟失,信息發(fā)布速度快
IP地址
- IP地址:指互聯(lián)網(wǎng)協(xié)議地址(Internet Protocol Address),俗稱IP。IP地址用來給一個網(wǎng)絡(luò)中的計算機設(shè)備做唯一的編號。相當(dāng)于每個人的身份證號碼。
**IP地址分類 **
-
IPv4:是一個32位的二進(jìn)制數(shù),通常被分為4個字節(jié),表示成
a.b.c.d的形式,例如192.168.65.100。其中a、b、c、d都是0~255之間的十進(jìn)制整數(shù),那么最多可以表示42億個。 -
IPv6:由于互聯(lián)網(wǎng)的蓬勃發(fā)展,IP地址的需求量愈來愈大,但是網(wǎng)絡(luò)地址資源有限,使得IP的分配越發(fā)緊張。有資料顯示,全球IPv4地址在2011年2月分配完畢。
為了擴大地址空間,擬通過IPv6重新定義地址空間,采用128位地址長度,每16個字節(jié)一組,分成8組十六進(jìn)制數(shù),表示成
ABCD:EF01:2345:6789:ABCD:EF01:2345:6789,號稱可以為全世界的每一粒沙子編上一個網(wǎng)址,這樣就解決了網(wǎng)絡(luò)地址資源數(shù)量不夠的問題。
常用命令
- 查看本機IP地址,在控制臺輸入:
ipconfig
- 檢查網(wǎng)絡(luò)是否連通,在控制臺輸入:
ping 空格 IP地址
ping 220.181.57.216
ping www.baidu.com
特殊的IP地址
- 本機IP地址:
127.0.0.1、localhost。
端口號
網(wǎng)絡(luò)的通信,本質(zhì)上是兩個進(jìn)程(應(yīng)用程序)的通信。每臺計算機都有很多的進(jìn)程,那么在網(wǎng)絡(luò)通信時,如何區(qū)分這些進(jìn)程呢?
如果說IP地址可以唯一標(biāo)識網(wǎng)絡(luò)中的設(shè)備,那么端口號就可以唯一標(biāo)識設(shè)備中的進(jìn)程(應(yīng)用程序)了。
- 端口號:用兩個字節(jié)表示的整數(shù),它的取值范圍是065535**。其中,01023之間的端口號用于一些知名的網(wǎng)絡(luò)服務(wù)和應(yīng)用,普通的應(yīng)用程序需要使用1024以上的端口號。如果端口號被另外一個服務(wù)或應(yīng)用所占用,會導(dǎo)致當(dāng)前程序啟動失敗。**
利用協(xié)議+IP地址+端口號 三元組合,就可以標(biāo)識網(wǎng)絡(luò)中的進(jìn)程了,那么進(jìn)程間的通信就可以利用這個標(biāo)識與其它進(jìn)程進(jìn)行交互。
域名
- 域名----->綁定了ip地址
- 域名是唯一的
1.3 InetAddress類
InetAddress類的概述
- 一個該類的對象就代表一個IP地址對象。
InetAddress類的方法
-
static InetAddress getLocalHost() 獲得本地主機IP地址對象
-
static InetAddress getByName(String host) 根據(jù)IP地址字符串或主機名獲得對應(yīng)的IP地址對象
-
String getHostName();獲得主機名
-
String getHostAddress();獲得IP地址字符串
/** * Created by PengZhiLin on 2021/8/13 9:57 */ public class Test { public static void main(String[] args) throws Exception { //* static InetAddress getLocalHost() 獲得本地主機IP地址對象 //* static InetAddress getByName(String host) 根據(jù)IP地址字符串或主機名獲得對應(yīng)的IP地址對象 // 獲得本機的ip地址對象 InetAddress ip1 = InetAddress.getLocalHost(); System.out.println("ip1:" + ip1);// DESKTOP-F810C9C/192.168.30.98 InetAddress ip2 = InetAddress.getByName("DESKTOP-F810C9C"); System.out.println("ip2:" + ip2);// DESKTOP-F810C9C/192.168.30.98 //* String getHostName();獲得主機名 //* String getHostAddress();獲得IP地址字符串 System.out.println("主機名:"+ip1.getHostName()); System.out.println("ip地址:"+ip1.getHostAddress()); System.out.println(InetAddress.getLocalHost().getHostAddress()); System.out.println("192.168.30.98"); System.out.println("127.0.0.1"); } }
第二章 TCP通信程序
2.1 TCP
TCP通信的流程
- TCP協(xié)議是面向連接的通信協(xié)議,即在傳輸數(shù)據(jù)前先在發(fā)送端和接收器端建立邏輯連接,然后再傳輸數(shù)據(jù)。它提供了兩臺計算機之間可靠無差錯的數(shù)據(jù)傳輸。TCP通信過程如下圖所示:

TCP協(xié)議相關(guān)的類
- Socket : 一個該類的對象就代表一個客戶端程序。
Socket(String host, int port)根據(jù)ip地址字符串和端口號創(chuàng)建客戶端Socket對象
* 注意事項:只要執(zhí)行該方法,就會立即連接指定的服務(wù)器程序,如果連接不成功,則會拋出異常。
如果連接成功,則表示三次握手通過。OutputStream getOutputStream();獲得字節(jié)輸出流對象InputStream getInputStream();獲得字節(jié)輸入流對象void close();關(guān)閉Socket, 會自動關(guān)閉相關(guān)的流- 補充:關(guān)閉通過socket獲得的流,會關(guān)閉socket,關(guān)閉socket,同時也會關(guān)閉通過socket獲得的流
- ServerSocket : 一個該類的對象就代表一個服務(wù)器端程序。
ServerSocket(int port);根據(jù)指定的端口號開啟服務(wù)器。Socket accept();等待客戶端連接并獲得與客戶端關(guān)聯(lián)的Socket對象 如果沒有客戶端連接,該方法會一直阻塞void close();關(guān)閉ServerSocket
2.2 TCP通信案例1
需求
- 客戶端向服務(wù)器發(fā)送字符串?dāng)?shù)據(jù)
分析
客戶端:
1.創(chuàng)建Socket對象,指定要連接的服務(wù)器的ip地址和端口號
2.通過Socket對象獲得字節(jié)輸出流對象
3.使用字節(jié)輸出流寫字符串?dāng)?shù)據(jù)到連接通道中
4.釋放資源
服務(wù)器:
1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號
2.調(diào)用accpet()方法,接收客戶端請求,建立連接,返回Socket對象
3.通過返回的Socket對象獲得字節(jié)輸入流
4.讀數(shù)據(jù)
5.釋放資源
實現(xiàn)
-
客戶端代碼實現(xiàn)
/** * Created by PengZhiLin on 2021/8/13 10:16 */ public class Client { public static void main(String[] args) throws Exception{ //1.創(chuàng)建Socket對象,指定要連接的服務(wù)器的ip地址和端口號 Socket socket = new Socket("127.0.0.1",6666); //2.通過Socket對象獲得字節(jié)輸出流對象 OutputStream os = socket.getOutputStream(); //3.使用字節(jié)輸出流寫字符串?dāng)?shù)據(jù)到連接通道中 os.write("服務(wù)器你好,今晚約嗎?".getBytes()); //4.釋放資源 socket.close(); } } -
服務(wù)端代碼實現(xiàn)
/** * Created by PengZhiLin on 2021/8/13 10:16 */ public class Server { public static void main(String[] args) throws Exception{ //1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號 ServerSocket ss = new ServerSocket(6666); //2.調(diào)用accpet()方法,接收客戶端請求,建立連接,返回Socket對象 Socket socket = ss.accept(); //3.通過返回的Socket對象獲得字節(jié)輸入流 InputStream is = socket.getInputStream(); //4.讀數(shù)據(jù) byte[] bys = new byte[1024]; int len = is.read(bys); System.out.println("服務(wù)器接收到的信息:"+new String(bys,0,len)); //5.釋放資源 socket.close(); ss.close(); } }
2.3 TCP通信案例2
需求
- 客戶端向服務(wù)器發(fā)送字符串?dāng)?shù)據(jù),服務(wù)器回寫字符串?dāng)?shù)據(jù)給客戶端(模擬聊天)
分析
客戶端:
1.創(chuàng)建Socket對象,指定要連接的服務(wù)器的ip地址和端口號
2.通過Socket對象獲得字節(jié)輸出流對象
3.使用字節(jié)輸出流寫字符串?dāng)?shù)據(jù)到連接通道中
4.通過Socket對象獲得字節(jié)輸入流對象
5.讀服務(wù)器回寫的數(shù)據(jù)
6.釋放資源
服務(wù)器:
1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號
2.調(diào)用accpet()方法,接收客戶端請求,建立連接,返回Socket對象
3.通過返回的Socket對象獲得字節(jié)輸入流
4.讀數(shù)據(jù)
5.通過返回的Socket對象獲得字節(jié)輸出流
6.使用字節(jié)輸出流寫字符串?dāng)?shù)據(jù)到連接通道中
7.釋放資源
實現(xiàn)
- TCP客戶端代碼
/**
* Created by PengZhiLin on 2021/8/13 10:16
*/
public class Client {
public static void main(String[] args) throws Exception{
//1.創(chuàng)建Socket對象,指定要連接的服務(wù)器的ip地址和端口號
Socket socket = new Socket("127.0.0.1",6666);
//2.通過Socket對象獲得字節(jié)輸出流對象
OutputStream os = socket.getOutputStream();
//3.使用字節(jié)輸出流寫字符串?dāng)?shù)據(jù)到連接通道中
os.write("服務(wù)器你好,今晚約嗎?".getBytes());
//4.通過Socket對象獲得字節(jié)輸入流對象
InputStream is = socket.getInputStream();
//5.讀服務(wù)器回寫的數(shù)據(jù)
byte[] bys = new byte[1024];
int len = is.read(bys);
System.out.println("客戶端接收到的信息:"+new String(bys,0,len));
//6.釋放資源
socket.close();
}
}
- 服務(wù)端代碼實現(xiàn)
/**
* Created by PengZhiLin on 2021/8/13 10:16
*/
public class Server {
public static void main(String[] args) throws Exception{
//1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號
ServerSocket ss = new ServerSocket(6666);
//2.調(diào)用accpet()方法,接收客戶端請求,建立連接,返回Socket對象
Socket socket = ss.accept();
//3.通過返回的Socket對象獲得字節(jié)輸入流
InputStream is = socket.getInputStream();
//4.讀數(shù)據(jù)
byte[] bys = new byte[1024];
int len = is.read(bys);
System.out.println("服務(wù)器接收到的信息:"+new String(bys,0,len));
//5.通過返回的Socket對象獲得字節(jié)輸出流
OutputStream os = socket.getOutputStream();
//6.使用字節(jié)輸出流寫字符串?dāng)?shù)據(jù)到連接通道中
os.write("客戶端你好,今晚小樹林見!".getBytes());
//7.釋放資源
socket.close();
ss.close();
}
}
2.4 擴展模擬循環(huán)聊天
-
服務(wù)器
/** * Created by PengZhiLin on 2021/8/13 10:16 */ public class Server { public static void main(String[] args) throws Exception{ //1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號 ServerSocket ss = new ServerSocket(6666); //2.調(diào)用accpet()方法,接收客戶端請求,建立連接,返回Socket對象 Socket socket = ss.accept(); // 循環(huán) while (true) { //3.通過返回的Socket對象獲得字節(jié)輸入流 InputStream is = socket.getInputStream(); //4.讀數(shù)據(jù) byte[] bys = new byte[1024]; int len = is.read(bys); System.out.println("服務(wù)器接收到的信息:" + new String(bys, 0, len)); //5.通過返回的Socket對象獲得字節(jié)輸出流 OutputStream os = socket.getOutputStream(); //6.使用字節(jié)輸出流寫字符串?dāng)?shù)據(jù)到連接通道中 Scanner sc = new Scanner(System.in); System.out.println("請輸入您要對客戶端說的話:"); String msg = sc.next(); os.write(msg.getBytes()); //7.釋放資源 //socket.close(); //ss.close(); } } } -
客戶端
/** * Created by PengZhiLin on 2021/8/13 10:16 */ public class Client { public static void main(String[] args) throws Exception{ //1.創(chuàng)建Socket對象,指定要連接的服務(wù)器的ip地址和端口號 Socket socket = new Socket("127.0.0.1",6666); // 循環(huán) while (true) { //2.通過Socket對象獲得字節(jié)輸出流對象 OutputStream os = socket.getOutputStream(); //3.使用字節(jié)輸出流寫字符串?dāng)?shù)據(jù)到連接通道中 Scanner sc = new Scanner(System.in); System.out.println("請輸入您要對服務(wù)器說的話:"); String msg = sc.next(); os.write(msg.getBytes()); //4.通過Socket對象獲得字節(jié)輸入流對象 InputStream is = socket.getInputStream(); //5.讀服務(wù)器回寫的數(shù)據(jù) byte[] bys = new byte[1024]; int len = is.read(bys); System.out.println("客戶端接收到的信息:"+new String(bys,0,len)); //6.釋放資源 //socket.close(); } } }
第三章 綜合案例
3.1 文件上傳案例
需求
- 使用TCP協(xié)議, 通過客戶端向服務(wù)器上傳一個文件
分析
客戶端:
1.創(chuàng)建Socket對象,指定要連接的服務(wù)器的ip地址和端口號
2.創(chuàng)建字節(jié)輸入流對象,關(guān)聯(lián)要上傳的文件路徑
3.通過Socket對象獲得字節(jié)輸出流對象
4.定義一個byte數(shù)組,用來存儲讀取到的字節(jié)數(shù)據(jù)
5.定義一個int變量,用來存儲讀取到的字節(jié)個數(shù)
6.循環(huán)讀
7.寫數(shù)據(jù)
8.釋放資源
服務(wù)器:
1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號
2.調(diào)用accept方法,接收請求建立連接,返回Socket對象
3.通過返回的Socket對象獲得字節(jié)輸入流對象
4.創(chuàng)建字節(jié)輸出流對象,關(guān)聯(lián)目的地文件路徑
5.定義一個byte數(shù)組,用來存儲讀取到的字節(jié)數(shù)據(jù)
5.定義一個int變量,用來存儲讀取到的字節(jié)個數(shù)
6.循環(huán)讀
7.寫數(shù)據(jù)
8.釋放資源

實現(xiàn)
文件上傳
-
服務(wù)器
/** * Created by PengZhiLin on 2021/8/13 10:51 */ public class Server { public static void main(String[] args) throws Exception { //服務(wù)器: //1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號 ServerSocket ss = new ServerSocket(7777); //2.調(diào)用accept方法,接收請求建立連接,返回Socket對象 Socket socket = ss.accept(); //3.通過返回的Socket對象獲得字節(jié)輸入流對象 InputStream is = socket.getInputStream(); //4.創(chuàng)建字節(jié)輸出流對象,關(guān)聯(lián)目的地文件路徑 FileOutputStream fos = new FileOutputStream("day12\\bbb\\mm2.jpg"); //5.定義一個byte數(shù)組,用來存儲讀取到的字節(jié)數(shù)據(jù) byte[] bys = new byte[8192]; //5.定義一個int變量,用來存儲讀取到的字節(jié)個數(shù) int len; //6.循環(huán)讀 while ((len = is.read(bys)) != -1) { //7.寫數(shù)據(jù) fos.write(bys,0,len); } //8.釋放資源 fos.close(); socket.close(); ss.close(); } } -
客戶端
/** * Created by PengZhiLin on 2021/8/13 10:51 */ public class Client { public static void main(String[] args) throws Exception{ //客戶端: //1.創(chuàng)建Socket對象,指定要連接的服務(wù)器的ip地址和端口號 Socket socket = new Socket("127.0.0.1",7777); //2.創(chuàng)建字節(jié)輸入流對象,關(guān)聯(lián)要上傳的文件路徑 FileInputStream fis = new FileInputStream("day12\\aaa\\mm.jpg"); //3.通過Socket對象獲得字節(jié)輸出流對象 OutputStream os = socket.getOutputStream(); //4.定義一個byte數(shù)組,用來存儲讀取到的字節(jié)數(shù)據(jù) byte[] bys = new byte[8192]; //5.定義一個int變量,用來存儲讀取到的字節(jié)個數(shù) int len; //6.循環(huán)讀 while ((len = fis.read(bys)) != -1) { //7.寫數(shù)據(jù) os.write(bys,0,len); } //8.釋放資源 fis.close(); socket.close(); } }
文件上傳成功后服務(wù)器回寫字符串?dāng)?shù)據(jù)
-
服務(wù)器
/** * Created by PengZhiLin on 2021/8/13 10:51 */ public class Server { public static void main(String[] args) throws Exception { //服務(wù)器: //1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號 ServerSocket ss = new ServerSocket(7777); //2.調(diào)用accept方法,接收請求建立連接,返回Socket對象 Socket socket = ss.accept(); //3.通過返回的Socket對象獲得字節(jié)輸入流對象 InputStream is = socket.getInputStream(); //4.創(chuàng)建字節(jié)輸出流對象,關(guān)聯(lián)目的地文件路徑 FileOutputStream fos = new FileOutputStream("day12\\bbb\\mm4.jpg"); //5.定義一個byte數(shù)組,用來存儲讀取到的字節(jié)數(shù)據(jù) byte[] bys = new byte[8192]; //5.定義一個int變量,用來存儲讀取到的字節(jié)個數(shù) int len; System.out.println("服務(wù)器1"); //6.循環(huán)讀 // 循環(huán)讀客戶端寫過來的數(shù)據(jù) while ((len = is.read(bys)) != -1) {// 卡 //7.寫數(shù)據(jù) fos.write(bys,0,len); } System.out.println("服務(wù)器2"); //8.通過Socket獲得字節(jié)輸出流對象 OutputStream os = socket.getOutputStream(); //9.回寫上傳成功信息給客戶端 os.write("文件上傳成功!".getBytes()); //10.釋放資源 fos.close(); socket.close(); ss.close(); } } -
客戶端
/** * Created by PengZhiLin on 2021/8/13 10:51 */ public class Client { public static void main(String[] args) throws Exception{ //客戶端: //1.創(chuàng)建Socket對象,指定要連接的服務(wù)器的ip地址和端口號 Socket socket = new Socket("127.0.0.1",7777); //2.創(chuàng)建字節(jié)輸入流對象,關(guān)聯(lián)要上傳的文件路徑 FileInputStream fis = new FileInputStream("day12\\aaa\\mm.jpg"); //3.通過Socket對象獲得字節(jié)輸出流對象 OutputStream os = socket.getOutputStream(); //4.定義一個byte數(shù)組,用來存儲讀取到的字節(jié)數(shù)據(jù) byte[] bys = new byte[8192]; //5.定義一個int變量,用來存儲讀取到的字節(jié)個數(shù) int len; //6.循環(huán)讀 while ((len = fis.read(bys)) != -1) { //7.寫數(shù)據(jù) os.write(bys,0,len); } // 問題原因:客戶端不再寫數(shù)據(jù)過來了,而服務(wù)器不知道,所以服務(wù)器一直在等待讀客戶端寫過來的數(shù)據(jù) // 解決辦法: 客戶端要告訴服務(wù)器,客戶端不再寫數(shù)據(jù)過來了 // 禁止客戶端寫數(shù)據(jù)的功能 socket.shutdownOutput(); System.out.println("客戶端1"); //8.通過Socket獲得字節(jié)輸入流對象 InputStream is = socket.getInputStream(); //9.讀服務(wù)器回寫的信息 int len2 = is.read(bys);// 卡 System.out.println("服務(wù)器回寫的信息是:"+new String(bys,0,len2)); //10.釋放資源 fis.close(); socket.close(); } }
優(yōu)化文件上傳案例
-
需要優(yōu)化的問題
- 文件名----->目前是固定名稱,改成自動生成名稱
- 服務(wù)器只能接收客戶端上傳一次文件---->改成服務(wù)器循環(huán)接收
- 效率問題---->目前是單線程會影響效率,改成多線程
- 張三先連接服務(wù)器,需要上傳一個2GB的文件
- 李四后連接服務(wù)器,需要上傳一個2KB的文件
- 單線程: 李四一定要等張三上傳完畢才可以上傳,所以李四需要等待的時間很長
- 多線程: 張三,和李四就可以搶著執(zhí)行,李四就有可能先執(zhí)行完畢,就不需要等待
-
優(yōu)化實現(xiàn)
/** * Created by PengZhiLin on 2021/8/13 10:51 */ public class Server { public static void main(String[] args) throws Exception { //服務(wù)器: //1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號 ServerSocket ss = new ServerSocket(7777); // 循環(huán) while (true) { //2.調(diào)用accept方法,接收請求建立連接,返回Socket對象 Socket socket = ss.accept(); // 開啟線程,實現(xiàn)上傳任務(wù) new Thread(new Runnable() { @Override public void run() { try{ //3.通過返回的Socket對象獲得字節(jié)輸入流對象 InputStream is = socket.getInputStream(); //4.創(chuàng)建字節(jié)輸出流對象,關(guān)聯(lián)目的地文件路徑 FileOutputStream fos = new FileOutputStream("day12\\bbb\\" + System.currentTimeMillis() + ".jpg"); //5.定義一個byte數(shù)組,用來存儲讀取到的字節(jié)數(shù)據(jù) byte[] bys = new byte[8192]; //5.定義一個int變量,用來存儲讀取到的字節(jié)個數(shù) int len; System.out.println("服務(wù)器1"); //6.循環(huán)讀 // 循環(huán)讀客戶端寫過來的數(shù)據(jù) while ((len = is.read(bys)) != -1) {// 卡 //7.寫數(shù)據(jù) fos.write(bys, 0, len); } System.out.println("服務(wù)器2"); //8.通過Socket獲得字節(jié)輸出流對象 OutputStream os = socket.getOutputStream(); //9.回寫上傳成功信息給客戶端 os.write("文件上傳成功!".getBytes()); //10.釋放資源 fos.close(); socket.close(); //ss.close(); }catch (Exception e){ e.printStackTrace(); } } }).start(); } } }
3.2 模擬B\S服務(wù)器 擴展
需求
- 模擬網(wǎng)站服務(wù)器,使用瀏覽器訪問自己編寫的服務(wù)端程序,查看網(wǎng)頁效果。
分析
-
準(zhǔn)備頁面數(shù)據(jù),web文件夾。
-
我們模擬服務(wù)器端,ServerSocket類監(jiān)聽端口,使用瀏覽器訪問,查看網(wǎng)頁效果
-
注意:
// 1.瀏覽器工作原理是遇到圖片會開啟一個線程進(jìn)行單獨的訪問,因此在服務(wù)器端加入線程技術(shù)。 // 2. 響應(yīng)頁面的時候需要同時把以下信息響應(yīng)過去給瀏覽器 os.write("HTTP/1.1 200 OK\r\n".getBytes()); os.write("Content-Type:text/html\r\n".getBytes()); os.write("\r\n".getBytes());1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號為8888 2.調(diào)用accept方法接收請求,建立連接,返回Socket對象 3.通過返回的Socket對象獲得字節(jié)輸入流 4.讀請求里面的數(shù)據(jù) 5.從讀到的數(shù)據(jù)中篩選出要訪問的頁面路徑--->day12/web/index.html 6.創(chuàng)建字節(jié)輸入流,關(guān)聯(lián)瀏覽器要訪問的頁面路徑 7.通過Socket對象獲得字節(jié)輸出流 8.定義一個byte數(shù)組,用來存儲讀取到的字節(jié)數(shù)據(jù) 8.定義一個int變量,用來存儲讀取到的字節(jié)個數(shù) 9.循環(huán)讀 10.在循環(huán)中,寫數(shù)據(jù) 11.釋放資源
實現(xiàn)
/**
* Created by PengZhiLin on 2021/8/13 11:37
*/
public class Server {
public static void main(String[] args) throws Exception {
//1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號為8888
ServerSocket ss = new ServerSocket(8888);
while (true) {
//2.調(diào)用accept方法接收請求,建立連接,返回Socket對象
Socket socket = ss.accept();
// 開啟線程
new Thread(new Runnable() {
@Override
public void run() {
try {
//3.通過返回的Socket對象獲得字節(jié)輸入流
InputStream is = socket.getInputStream();
//4.讀請求里面的數(shù)據(jù)
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = br.readLine();
//5.從讀到的數(shù)據(jù)中篩選出要訪問的頁面路徑--->day12/web/index.html
//String[] arr = line.split(" ");
//String path = arr[1].substring(1);
String path = line.split(" ")[1].substring(1);
System.out.println("path:" + path);
//6.創(chuàng)建字節(jié)輸入流,關(guān)聯(lián)瀏覽器要訪問的頁面路徑
FileInputStream fis = new FileInputStream(path);
//7.通過Socket對象獲得字節(jié)輸出流
OutputStream os = socket.getOutputStream();
os.write("HTTP/1.1 200 OK\r\n".getBytes());
os.write("Content-Type:text/html\r\n".getBytes());
os.write("\r\n".getBytes());
//8.定義一個byte數(shù)組,用來存儲讀取到的字節(jié)數(shù)據(jù)
byte[] bys = new byte[8192];
//8.定義一個int變量,用來存儲讀取到的字節(jié)個數(shù)
int len;
//9.循環(huán)讀
while ((len = fis.read(bys)) != -1) {
//10.在循環(huán)中,寫數(shù)據(jù)
os.write(bys,0,len);
}
//11.釋放資源
fis.close();
socket.close();
//ss.close();
} catch (Exception e) {
}
}
}).start();
}
}
}
訪問效果:

第四章 NIO
4.1 NIO概述
在我們學(xué)習(xí)Java的NIO流之前,我們都要了解幾個關(guān)鍵詞
- 同步與異步(synchronous/asynchronous):同步是一種可靠的有序運行機制,當(dāng)我們進(jìn)行同步操作時,后續(xù)的任務(wù)是等待當(dāng)前調(diào)用返回,才會進(jìn)行下一步;而異步則相反,其他任務(wù)不需要等待當(dāng)前調(diào)用返回,通常依靠事件、回調(diào)等機制來實現(xiàn)任務(wù)間次序關(guān)系
- 同步: 調(diào)用方法之后,必須要得到一個返回值
- 異步: 調(diào)用方法之后,沒有返回值,但是會有回調(diào)函數(shù),回調(diào)函數(shù)指的是滿足條件之后會自動執(zhí)行的方法
- 阻塞與非阻塞:在進(jìn)行阻塞操作時,當(dāng)前線程會處于阻塞狀態(tài),無法從事其他任務(wù),只有當(dāng)條件就緒才能繼續(xù),比如ServerSocket新連接建立完畢,或者數(shù)據(jù)讀取、寫入操作完成;而非阻塞則是不管IO操作是否結(jié)束,直接返回,相應(yīng)操作在后臺繼續(xù)處理
- 阻塞:如果沒有達(dá)到方法的目的,就會一直停在那里(等待) , 例如: ServerSocket的accept()方法
- 非阻塞: 不管方法有沒有達(dá)到目的,都直接往下執(zhí)行(不等待)
在Java1.4之前的I/O系統(tǒng)中,提供的都是面向流的I/O系統(tǒng),系統(tǒng)一次一個字節(jié)地處理數(shù)據(jù),一個輸入流產(chǎn)生一個字節(jié)的數(shù)據(jù),一個輸出流消費一個字節(jié)的數(shù)據(jù),面向流的I/O速度非常慢,而在Java 1.4中推出了NIO,這是一個面向塊的I/O系統(tǒng),系統(tǒng)以塊的方式處理數(shù)據(jù),每一個操作在一步中產(chǎn)生或者消費一個數(shù)據(jù),按塊處理要比按字節(jié)處理數(shù)據(jù)快的多。
在 Java 7 中,NIO 有了進(jìn)一步的改進(jìn),也就是 NIO 2,引入了異步非阻塞 IO 方式,也有很多人叫它 AIO(Asynchronous IO)。異步 IO 操作基于事件和回調(diào)機制,可以簡單理解為,應(yīng)用操作直接返回,而不會阻塞在那里,當(dāng)后臺處理完成,操作系統(tǒng)會通知相應(yīng)線程進(jìn)行后續(xù)工作。
NIO之所以是同步,是因為它的accept/read/write方法的內(nèi)核I/O操作都會阻塞當(dāng)前線程
首先,我們要先了解一下NIO的三個主要組成部分:Buffer(緩沖區(qū))、Channel(通道)、Selector(選擇器)
IO: 同步阻塞
NIO:同步非阻塞
AIO: 異步非阻塞
第五章 Buffer類(緩沖區(qū))
5.1 Buffer的概述和分類
概述:Buffer是一個對象,它是對某種基本類型的數(shù)組進(jìn)行了封裝。
作用: 在NIO中,就是通過 Buffer 來讀寫數(shù)據(jù)的。所有的數(shù)據(jù)都是用Buffer來處理的,它是NIO讀寫數(shù)據(jù)的中轉(zhuǎn)池, 通常使用字節(jié)數(shù)組。
Buffer主要有如下幾種:
- ByteBuffer--->byte[]
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
5.2 創(chuàng)建ByteBuffer
-
ByteBuffer類內(nèi)部封裝了一個byte[]數(shù)組,并可以通過一些方法對這個數(shù)組進(jìn)行操作。
-
創(chuàng)建ByteBuffer對象
-
方式一:在堆中創(chuàng)建緩沖區(qū):
public static ByteBuffer allocate(int capacity)
-
方式二: 在系統(tǒng)內(nèi)存創(chuàng)建緩沖區(qū):
public static ByteBuffer allocatDirect(int capacity)-
在堆中創(chuàng)建緩沖區(qū)稱為:間接緩沖區(qū)
-
在系統(tǒng)內(nèi)存創(chuàng)建緩沖區(qū)稱為:直接緩沖區(qū)
- 間接緩沖區(qū)的創(chuàng)建和銷毀效率要高于直接緩沖區(qū)
- 間接緩沖區(qū)的工作效率要低于直接緩沖區(qū)
-
-
方式三:通過數(shù)組創(chuàng)建緩沖區(qū):
public static ByteBuffer wrap(byte[] arr)- 此種方式創(chuàng)建的緩沖區(qū)為:間接緩沖區(qū)
-
-
案例:
/** * Created by PengZhiLin on 2021/8/13 12:09 */ public class Test { public static void main(String[] args) { // 方式一: public static ByteBuffer allocate(int capacity) 堆區(qū) 推薦 ByteBuffer b1 = ByteBuffer.allocate(10); // 方式二: public static ByteBuffer allocateDirect(int capacity) 直接內(nèi)存 ByteBuffer b2 = ByteBuffer.allocateDirect(10); // 方式三: public static ByteBuffer wrap(byte[] array) 堆區(qū) 推薦 byte[] bys = {10,20,30}; ByteBuffer b3 = ByteBuffer.wrap(bys); // 獲取b1封裝的byte數(shù)組: public final byte[] array() byte[] arr = b1.array(); System.out.println(Arrays.toString(arr));// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] System.out.println(Arrays.toString(b3.array()));// [10, 20, 30] } }
5.3 添加數(shù)據(jù)-put
-
public ByteBuffer put(byte b):向當(dāng)前可用位置添加數(shù)據(jù)。
-
public ByteBuffer put(byte[] byteArray):向當(dāng)前可用位置添加一個byte[]數(shù)組
-
public ByteBuffer put(byte[] byteArray,int offset,int len):添加一個byte[]數(shù)組的一部分
-
public byte[] array(); 獲取封裝的字節(jié)數(shù)組
/** * Created by PengZhiLin on 2021/8/13 12:14 */ public class Test1_put { public static void main(String[] args) { // - public ByteBuffer put(byte b):向當(dāng)前可用位置添加數(shù)據(jù)。 //- public ByteBuffer put(byte[] byteArray):向當(dāng)前可用位置添加一個byte[]數(shù)組 //- public ByteBuffer put(byte[] byteArray,int offset,int len):添加一個byte[]數(shù)組的一部分 //- public byte[] array(); 獲取封裝的字節(jié)數(shù)組 // 創(chuàng)建ByteBuffer對象 ByteBuffer b = ByteBuffer.allocate(10); // 添加單個byte數(shù)據(jù) b.put((byte)10); b.put((byte)20); b.put((byte)30); System.out.println("封裝的數(shù)組:"+ Arrays.toString(b.array())); // 添加整個數(shù)組中的所有數(shù)據(jù) byte[] bys = {10,20,30}; b.put(bys); System.out.println("封裝的數(shù)組:"+ Arrays.toString(b.array())); // 添加指定范圍的數(shù)組數(shù)據(jù) b.put(bys,0,2); System.out.println("封裝的數(shù)組:"+ Arrays.toString(b.array())); } }
5.4 容量-capacity
-
Buffer的容量(capacity)是指:Buffer所能夠包含的元素的最大數(shù)量。定義了Buffer后,容量是不可變的。
-
public final int capacity();獲取緩沖數(shù)組的容量 -
示例代碼:
/** * Created by PengZhiLin on 2021/8/13 12:19 */ public class Test2_capacity { public static void main(String[] args) { // 創(chuàng)建ByteBuffer對象 ByteBuffer b = ByteBuffer.allocate(10); System.out.println("ByteBuffer對象的容量:" + b.capacity());// 10 // 添加單個byte數(shù)據(jù) b.put((byte) 10); b.put((byte) 20); b.put((byte) 30); System.out.println("封裝的數(shù)組:" + Arrays.toString(b.array())); System.out.println("ByteBuffer對象的容量:" + b.capacity());// 10 } }
5.5 限制-limit
-
限制limit是指:第一個不能讀或?qū)懭朐氐膇ndex索引。緩沖區(qū)的限制(limit)不能為負(fù),并且不能大于容量。
-
有兩個相關(guān)方法:
- public int limit():獲取此緩沖區(qū)的限制。
- public Buffer limit(int newLimit):設(shè)置此緩沖區(qū)的限制。
-
示例代碼:
/** * Created by PengZhiLin on 2021/8/13 12:22 */ public class Test2_limit { public static void main(String[] args) { // 創(chuàng)建ByteBuffer對象 ByteBuffer b = ByteBuffer.allocate(10); System.out.println("limit:" + b.limit());// 10 // 添加單個byte數(shù)據(jù) b.put((byte) 10); b.put((byte) 20); b.put((byte) 30); System.out.println("limit:" + b.limit());// 10 // 修改limit b.limit(3); System.out.println("limit:" + b.limit());// 3 //b.put((byte) 30);// 報錯,因為3索引這個位置是不能使用了BufferOverflowException異常 } }圖示:

5.6 位置-position
-
位置position是指:當(dāng)前可寫入的索引。位置不能小于0,并且不能大于"限制"。
-
結(jié)論: 操作緩沖數(shù)組,其實就是操作position到limit之間位置上的元素
-
有兩個相關(guān)方法:
- public int position():獲取當(dāng)前可寫入位置索引。
- public Buffer position(int p):更改當(dāng)前可寫入位置索引。
-
示例代碼:
/** * Created by PengZhiLin on 2021/8/13 12:25 */ public class Test4_position { public static void main(String[] args) { // 創(chuàng)建ByteBuffer對象 ByteBuffer b = ByteBuffer.allocate(10); System.out.println("position:" + b.position());// 0 // 添加單個byte數(shù)據(jù) b.put((byte) 10); b.put((byte) 20); b.put((byte) 30); System.out.println("position:" + b.position());// 3 // 修改position的位置 b.position(5); System.out.println("position:" + b.position());// 5 b.put((byte) 30);// --- 添加在索引為5的位置 // 封裝的數(shù)組:[10, 20, 30, 0, 0, 30, 0, 0, 0, 0] System.out.println("封裝的數(shù)組:"+ Arrays.toString(b.array())); } }
5.7 標(biāo)記-mark
-
標(biāo)記mark是指:當(dāng)調(diào)用緩沖區(qū)的reset()方法時,會將緩沖區(qū)的position位置重置為該標(biāo)記的索引。
-
相關(guān)方法:
- public Buffer mark():設(shè)置此緩沖區(qū)的標(biāo)記為當(dāng)前的position位置。
- public Buffer reset() : 將此緩沖區(qū)的位置重置為以前標(biāo)記的位置。
-
示例代碼:
/** * Created by PengZhiLin on 2021/8/13 14:33 */ public class Test5_mark { public static void main(String[] args) { // 創(chuàng)建ByteBuffer對象 ByteBuffer b = ByteBuffer.allocate(10); // 添加單個byte數(shù)據(jù) b.put((byte) 10); b.put((byte) 20); b.put((byte) 30); System.out.println("position:"+b.position());// 3 // 標(biāo)記一下 b.mark(); b.put((byte) 10); b.put((byte) 20); b.put((byte) 30); System.out.println("position:"+b.position());// 6 // 重置一下 b.reset(); System.out.println("position:"+b.position());// 3 // 往索引為3的位置添加元素 b.put((byte) 100); // [10,20,30,100,20,30,0,0,0,0] System.out.println("封裝的數(shù)組:"+ Arrays.toString(b.array())); } }
5.8 clear和flip
- public Buffer clear():還原緩沖區(qū)的狀態(tài)。
- 將position設(shè)置為:0
- 將限制limit設(shè)置為容量capacity;
- 丟棄標(biāo)記mark。
- public Buffer flip():縮小limit的范圍。
- 將當(dāng)前position位置設(shè)置為0;
- 將limit設(shè)置為當(dāng)前position位置;
- 丟棄標(biāo)記。
-
clear方法演示
-
flip方法演示
/** * Created by PengZhiLin on 2021/8/13 14:38 */ public class Test6_clear和flip { public static void main(String[] args) { // 創(chuàng)建ByteBuffer對象 ByteBuffer b = ByteBuffer.allocate(10); // position:0,limit:10,capacity:10 System.out.println("position:" + b.position() + ",limit:" + b.limit() + ",capacity:" + b.capacity()); // 添加單個byte數(shù)據(jù) b.put((byte) 10); b.put((byte) 20); b.put((byte) 30); // position:3,limit:10,capacity:10 System.out.println("position:" + b.position() + ",limit:" + b.limit() + ",capacity:" + b.capacity()); // 調(diào)用flip一下 b.flip(); // position:0,limit:3,capacity:10 System.out.println("position:" + b.position() + ",limit:" + b.limit() + ",capacity:" + b.capacity()); // clear一下 b.clear(); // position:0,limit:10,capacity:10 System.out.println("position:" + b.position() + ",limit:" + b.limit() + ",capacity:" + b.capacity()); } }
第六章 Channel(通道)
6.1 Channel概述
Channel 的概述
Channel(通道):Channel是一個對象,可以通過它讀取和寫入數(shù)據(jù), 可以把它看做是IO中的流,不同的是:Channel是雙向的, Channel對象既可以調(diào)用讀取的方法, 也可以調(diào)用寫出的方法 。
輸入流: 讀
輸出流: 寫
Channel: 讀,寫
public int read(ByteBuffer b)讀數(shù)據(jù),把讀到的數(shù)據(jù)放在b數(shù)組中,返回讀取到的字節(jié)個數(shù),如果讀到文件的末尾就返回-1public int write(ByteBuffer b)寫數(shù)據(jù),把數(shù)據(jù)寫到目的地文件中,寫的是position到limit之間的數(shù)據(jù)
Channel 的分類
在Java NIO中的Channel主要有如下幾種類型:
- FileChannel:從文件讀寫數(shù)據(jù)的 輸入流和輸出流
- DatagramChannel:讀寫UDP網(wǎng)絡(luò)協(xié)議數(shù)據(jù) Datagram
- SocketChannel:讀寫TCP網(wǎng)絡(luò)協(xié)議數(shù)據(jù) Socket
- ServerSocketChannel:可以監(jiān)聽TCP連接 ServerSocket
6.2 FileChannel類的基本使用
獲取FileChannel類的對象
-
java.nio.channels.FileChannel (抽象類):用于讀、寫文件的通道。
-
FileChannel是抽象類,我們可以通過FileInputStream和FileOutputStream的getChannel()方法方便的獲取一個它的子類對象。
FileInputStream fis=new FileInputStream("數(shù)據(jù)源文件路徑"); FileOutputStream fos=new FileOutputStream("目的地文件路徑"); //獲得傳輸通道channel FileChannel inChannel=fis.getChannel(); FileChannel outChannel=fos.getChannel();
使用FileChannel類完成文件的復(fù)制
-
我們將通過CopyFile這個示例讓大家體會NIO的操作過程。CopyFile執(zhí)行三個基本的操作:創(chuàng)建一個Buffer,然后從源文件讀取數(shù)據(jù)到緩沖區(qū),然后再將緩沖區(qū)寫入目標(biāo)文件。
-
public int read(ByteBuffer b)讀數(shù)據(jù),把讀到的數(shù)據(jù)放在b數(shù)組中,返回讀取到的字節(jié)個數(shù),如果讀到文件的末尾就返回-1 -
public int write(ByteBuffer b)寫數(shù)據(jù),把數(shù)據(jù)寫到目的地文件中/** * Created by PengZhiLin on 2021/8/13 14:55 */ public class Test { public static void main(String[] args) throws Exception{ // 1.獲得FileChannel對象 FileInputStream fis = new FileInputStream("day12\\aaa\\mm.jpg"); FileOutputStream fos = new FileOutputStream("day12\\ccc\\mm1.jpg"); FileChannel c1 = fis.getChannel(); FileChannel c2 = fos.getChannel(); // 2.創(chuàng)建ByteBuffer數(shù)組對象 ByteBuffer bys = ByteBuffer.allocate(8192); // 3.循環(huán)讀數(shù)據(jù) while (c1.read(bys) != -1) { // flip一下---把position改為0,limit改為position--->目的是為了寫的時候?qū)懙氖莿倓傋x到的字節(jié)數(shù)據(jù) bys.flip(); // 4.在循環(huán)中,寫數(shù)據(jù) c2.write(bys); // clear一下--->把position改為0,limit改為capacity--->目的是為了還原數(shù)組為最初的狀態(tài)供下一次循環(huán)使用 bys.clear(); } // 5.釋放資源 c2.close(); c1.close(); fos.close(); fis.close(); } }
6.3 FileChannel結(jié)合MappedByteBuffer實現(xiàn)高效讀寫
MappedByteBuffer類的概述
-
上例直接使用FileChannel結(jié)合ByteBuffer實現(xiàn)的管道讀寫,但并不能提高文件的讀寫效率。
-
ByteBuffer有個抽象子類:MappedByteBuffer,它可以將文件直接映射至內(nèi)存,把硬盤中的讀寫變成內(nèi)存中的讀寫, 所以可以提高大文件的讀寫效率。
-
可以調(diào)用FileChannel的map()方法獲取一個MappedByteBuffer,map()方法的原型:
? MappedByteBuffer map(MapMode mode, long position, long size);
? 說明:將節(jié)點中從position開始的size個字節(jié)映射到返回的MappedByteBuffer中。
-
代碼說明:
-
map()方法的第一個參數(shù)mode:映射的三種模式,在這三種模式下得到的將是三種不同的MappedByteBuffer:三種模式都是Channel的內(nèi)部類MapMode中定義的靜態(tài)常量,這里以FileChannel舉例:
1). FileChannel.MapMode.READ_ONLY:得到的鏡像只能讀不能寫(只能使用get之類的讀取Buffer中的內(nèi)容);2). FileChannel.MapMode.READ_WRITE:得到的鏡像可讀可寫(既然可寫了必然可讀),對其寫會直接更改到存儲節(jié)點;
3). FileChannel.MapMode.PRIVATE:得到一個私有的鏡像,其實就是一個(position, size)區(qū)域的副本罷了,也是可讀可寫,只不過寫不會影響到存儲節(jié)點,就是一個普通的ByteBuffer了!!
-
為什么使用RandomAccessFile?
1). 使用InputStream獲得的Channel可以映射,使用map時只能指定為READ_ONLY模式,不能指定為READ_WRITE和PRIVATE,否則會拋出運行時異常!
2). 使用OutputStream得到的Channel不可以映射!并且OutputStream的Channel也只能write不能read!
3). 只有RandomAccessFile獲取的Channel才能開啟任意的這三種模式!
-
復(fù)制2GB以下的文件
- 復(fù)制d:\b.rar文件,此文件大概600多兆,復(fù)制完畢用時不到2秒。此例不能復(fù)制大于2G的文件,因為map的第三個參數(shù)被限制在Integer.MAX_VALUE(字節(jié)) = 2G。
/**
* Created by PengZhiLin on 2021/8/13 15:22
*/
public class Test {
public static void main(String[] args) throws Exception {
// 1.創(chuàng)建RandomAccessFile對象,r表示讀,rw表示讀寫
RandomAccessFile r1 = new RandomAccessFile("day12\\aaa\\mm.jpg", "r");
RandomAccessFile r2 = new RandomAccessFile("day12\\aaa\\mm2.jpg", "rw");
// 2.根據(jù)RandomAccessFile對象獲取FileChanel對象
FileChannel c1 = r1.getChannel();
FileChannel c2 = r2.getChannel();
// 3.獲取數(shù)據(jù)源文件的字節(jié)大小
long size = c1.size();
// 4.映射操作
MappedByteBuffer m1 = c1.map(FileChannel.MapMode.READ_ONLY, 0, size);
MappedByteBuffer m2 = c2.map(FileChannel.MapMode.READ_WRITE, 0, size);
// 5.復(fù)制字節(jié)數(shù)據(jù)
for (long i = 0; i < size; i++) {
byte b = m1.get();
m2.put(b);
}
// 6.釋放資源
c2.close();
c1.close();
r2.close();
r1.close();
}
}
復(fù)制2GB以上的文件
- 下例使用循環(huán),將文件分塊,可以高效的復(fù)制大于2G的文件
/**
* Created by PengZhiLin on 2021/8/13 15:22
*/
public class Test {
public static void main(String[] args) throws Exception {
// 1.創(chuàng)建RandomAccessFile對象,r表示讀,rw表示讀寫
RandomAccessFile r1 = new RandomAccessFile("H:\\a.zip", "r");
RandomAccessFile r2 = new RandomAccessFile("day12\\aaa\\a.zip", "rw");
// 2.根據(jù)RandomAccessFile對象獲取FileChanel對象
FileChannel c1 = r1.getChannel();
FileChannel c2 = r2.getChannel();
// 3.獲取數(shù)據(jù)源文件的字節(jié)大小
long size = c1.size();
// 定義一個變量,表示每次平均映射的字節(jié)大小
long everySize = 500 * 1024 * 1024;
// 映射的總次數(shù):
long count = size % everySize == 0 ? size / everySize : size / everySize + 1;
// 循環(huán)映射
for (long i = 0; i < count; i++) {
//起始位置:
long start = i * everySize;
//每次真正映射的字節(jié)大小:
long trueSize = size - start > everySize ? everySize : size - start;
// 4.映射操作
MappedByteBuffer m1 = c1.map(FileChannel.MapMode.READ_ONLY, start, trueSize);
MappedByteBuffer m2 = c2.map(FileChannel.MapMode.READ_WRITE, start, trueSize);
// 5.復(fù)制字節(jié)數(shù)據(jù)
for (long j = 0; j < trueSize; j++) {
byte b = m1.get();
m2.put(b);
}
}
// 6.釋放資源
c2.close();
c1.close();
r2.close();
r1.close();
}
}
6.4 ServerSocketChannel和SocketChannel創(chuàng)建連接
SocketChannel創(chuàng)建連接
-
客戶端:SocketChannel類用于連接的客戶端,它相當(dāng)于:Socket。
1). 先調(diào)用SocketChannel的open()方法打開通道:
SocketChannel socket = SocketChannel.open()2). 調(diào)用SocketChannel的實例方法connect(SocketAddress add)連接服務(wù)器:
socket.connect(new InetSocketAddress("127.0.0.1", 8888));示例:客戶端連接服務(wù)器:
/** * Created by PengZhiLin on 2021/8/13 16:12 */ public class Client { public static void main(String[] args) throws Exception{ // 1.打開SocketChannel通道 SocketChannel sc = SocketChannel.open(); // 2.連接服務(wù)器 sc.connect(new InetSocketAddress("127.0.0.1",8888)); System.out.println("連接上了..."); } }
ServerSocketChanne創(chuàng)建連接
-
服務(wù)器端:ServerSocketChannel類用于連接的服務(wù)器端,它相當(dāng)于:ServerSocket。
-
調(diào)用ServerSocketChannel的靜態(tài)方法open()就可以獲得ServerSocketChannel對象, 但并沒有指定端口號, 必須通過其套接字的bind方法將其綁定到特定地址,才能接受連接。
ServerSocketChannel serverChannel = ServerSocketChannel.open() -
調(diào)用ServerSocketChannel的實例方法bind(SocketAddress add):綁定本機監(jiān)聽端口,準(zhǔn)備接受連接。
? 注:java.net.SocketAddress(抽象類):代表一個Socket地址。
? 我們可以使用它的子類:java.net.InetSocketAddress(類)
? 構(gòu)造方法:InetSocketAddress(int port):指定本機監(jiān)聽端口。
serverChannel.bind(new InetSocketAddress(8888)); -
調(diào)用ServerSocketChannel的實例方法accept():等待連接。
SocketChannel accept = serverChannel.accept(); System.out.println("后續(xù)代碼...");示例:服務(wù)器端等待連接(默認(rèn)-阻塞模式)
/** * Created by PengZhiLin on 2021/8/13 16:16 */ public class Server { public static void main(String[] args) throws Exception{ // 1.打開服務(wù)器通道 ServerSocketChannel ssc = ServerSocketChannel.open(); // 2.綁定端口號 ssc.bind(new InetSocketAddress(8888)); // 3.等待連接 System.out.println("等待連接..."); SocketChannel socketChannel = ssc.accept();// 阻塞 System.out.println("連接成功..."); } }運行后結(jié)果:
【服務(wù)器】等待連接... -
我們可以通過ServerSocketChannel的configureBlocking(boolean b)方法設(shè)置accept()是否阻塞
/** * Created by PengZhiLin on 2021/8/13 16:16 */ public class Server { public static void main(String[] args) throws Exception{ // 1.打開服務(wù)器通道 ServerSocketChannel ssc = ServerSocketChannel.open(); // 2.綁定端口號 ssc.bind(new InetSocketAddress(8888)); // 設(shè)置非阻塞 ssc.configureBlocking(false); // 3.等待連接 System.out.println("等待連接..."); SocketChannel socketChannel = ssc.accept();// 非阻塞 System.out.println("連接成功..."); } }
6.5 NIO網(wǎng)絡(luò)編程收發(fā)信息
書寫服務(wù)器代碼
/**
* Created by PengZhiLin on 2021/8/13 16:20
*/
public class Server {
public static void main(String[] args) throws Exception{
// 1.打開服務(wù)器通道
ServerSocketChannel ssc = ServerSocketChannel.open();
// 2.綁定端口號
ssc.bind(new InetSocketAddress(8888));
// 3.建立連接
SocketChannel sc = ssc.accept();
// 4.接收數(shù)據(jù)
ByteBuffer b = ByteBuffer.allocate(1024);
int len = sc.read(b);
System.out.println("服務(wù)器接收到的數(shù)據(jù)是:"+new String(b.array(),0,len));
// 5.釋放資源
sc.close();
ssc.close();
}
}
書寫客戶端代碼
/**
* Created by PengZhiLin on 2021/8/13 16:20
*/
public class Client {
public static void main(String[] args) throws Exception{
// 1.打開客戶端通道
SocketChannel sc = SocketChannel.open();
// 2.連接服務(wù)器
sc.connect(new InetSocketAddress("127.0.0.1",8888));
// 3.寫數(shù)據(jù)給服務(wù)器
byte[] bytes = "服務(wù)器你好,今晚約嗎?".getBytes();
ByteBuffer b = ByteBuffer.wrap(bytes);
sc.write(b);
// 4.釋放資源
sc.close();
}
}
第七章 Selector(選擇器)
7.1 多路復(fù)用的概念
選擇器Selector是NIO中的重要技術(shù)之一。它與SelectableChannel聯(lián)合使用實現(xiàn)了非阻塞的多路復(fù)用。使用它可以節(jié)省CPU資源,提高程序的運行效率。
"多路"是指:服務(wù)器端同時監(jiān)聽多個“端口”的情況。每個端口都要監(jiān)聽多個客戶端的連接。
-
服務(wù)器端的非多路復(fù)用效果

如果不使用“多路復(fù)用”,服務(wù)器端需要開很多線程處理每個端口的請求。如果在高并發(fā)環(huán)境下,造成系統(tǒng)性能下降。
-
服務(wù)器端的多路復(fù)用效果

使用了多路復(fù)用,只需要一個線程就可以處理多個通道,降低內(nèi)存占用率,減少CPU切換時間,在高并發(fā)、高頻段業(yè)務(wù)環(huán)境下有非常重要的優(yōu)勢
7.2 選擇器Selector的獲取和注冊
Selector選擇器的概述和作用
概述: Selector被稱為:選擇器,也被稱為:多路復(fù)用器,可以把多個Channel注冊到一個Selector選擇器上, 那么就可以實現(xiàn)利用一個線程來處理這多個Channel上發(fā)生的事件,并且能夠根據(jù)事件情況決定Channel讀寫。這樣,通過一個線程管理多個Channel,就可以處理大量網(wǎng)絡(luò)連接了, 減少系統(tǒng)負(fù)擔(dān), 提高效率。因為線程之間的切換對操作系統(tǒng)來說代價是很高的,并且每個線程也會占用一定的系統(tǒng)資源。所以,對系統(tǒng)來說使用的線程越少越好。
作用: 一個Selector可以監(jiān)聽多個Channel發(fā)生的事件, 減少系統(tǒng)負(fù)擔(dān) , 提高程序執(zhí)行效率 .
Selector選擇器的獲取
Selector selector = Selector.open();
注冊Channel到Selector
通過調(diào)用 channel.register(Selector sel, int ops)方法來實現(xiàn)注冊:
channel.configureBlocking(false);// 一定要設(shè)置非阻塞
SelectionKey key =channel.register(selector,SelectionKey.OP_ACCEPT);
register()方法的第二個參數(shù):是一個int值,意思是在通過Selector監(jiān)聽Channel時對什么事件感興趣。可以監(jiān)聽四種不同類型的事件,而且可以使用SelectionKey的四個常量表示:
-
連接就緒--常量:SelectionKey.OP_CONNECT
-
接收就緒--常量:SelectionKey.OP_ACCEPT (ServerSocketChannel在注冊時只能使用此項)
-
讀就緒--常量:SelectionKey.OP_READ
-
寫就緒--常量:SelectionKey.OP_WRITE
注意:對于ServerSocketChannel在注冊時,只能使用OP_ACCEPT,否則拋出異常。
-
案例演示; 監(jiān)聽一個通道
/** * Created by PengZhiLin on 2021/8/13 16:54 */ public class Test1 { public static void main(String[] args) throws Exception{ // 1.打開服務(wù)器通道 ServerSocketChannel ssc = ServerSocketChannel.open(); // 2.綁定端口號 ssc.bind(new InetSocketAddress(6666)); // 3.設(shè)置非阻塞 ssc.configureBlocking(false); // 4.獲取選擇器 Selector selector = Selector.open(); // 5.把服務(wù)器通道注冊到選擇器上 System.out.println(1); ssc.register(selector, SelectionKey.OP_ACCEPT); System.out.println(2); } } -
示例:服務(wù)器創(chuàng)建3個通道,同時監(jiān)聽3個端口,并將3個通道注冊到一個選擇器中
/** * Created by PengZhiLin on 2021/8/13 16:54
*/
public class Test2 {
public static void main(String[] args) throws Exception{
// 1.打開服務(wù)器通道
ServerSocketChannel ssc1 = ServerSocketChannel.open();
ServerSocketChannel ssc2 = ServerSocketChannel.open();
ServerSocketChannel ssc3 = ServerSocketChannel.open();
// 2.綁定端口號
ssc1.bind(new InetSocketAddress(7777));
ssc2.bind(new InetSocketAddress(8888));
ssc3.bind(new InetSocketAddress(9999));
// 3.設(shè)置非阻塞
ssc1.configureBlocking(false);
ssc2.configureBlocking(false);
ssc3.configureBlocking(false);
// 4.獲取選擇器
Selector selector = Selector.open();
// 5.把服務(wù)器通道注冊到選擇器上
ssc1.register(selector, SelectionKey.OP_ACCEPT);
ssc2.register(selector, SelectionKey.OP_ACCEPT);
ssc3.register(selector, SelectionKey.OP_ACCEPT);
System.out.println(1);
}
}
接下來,就可以通過選擇器selector操作三個通道了。
## 7.3 Selector的常用方法
#### Selector的select()方法:---->面試
- 作用: 選擇器監(jiān)聽客戶端的請求方法
- 阻塞問題:
- 在連接到第一個客戶端之前,會一直阻塞
- 當(dāng)連接到客戶端后,如果客戶端沒有被處理,該方法會計入不阻塞狀態(tài)
- 當(dāng)連接到客戶端后,如果客戶端有被處理,該方法又會進(jìn)入阻塞狀態(tài)
```java
/**
* Created by PengZhiLin on 2021/8/13 17:00
*/
public class Test1_select方法 {
public static void main(String[] args) throws Exception{
// 1.打開服務(wù)器通道
ServerSocketChannel ssc1 = ServerSocketChannel.open();
ServerSocketChannel ssc2 = ServerSocketChannel.open();
ServerSocketChannel ssc3 = ServerSocketChannel.open();
// 2.綁定端口號
ssc1.bind(new InetSocketAddress(7777));
ssc2.bind(new InetSocketAddress(8888));
ssc3.bind(new InetSocketAddress(9999));
// 3.設(shè)置非阻塞
ssc1.configureBlocking(false);
ssc2.configureBlocking(false);
ssc3.configureBlocking(false);
// 4.獲取選擇器
Selector selector = Selector.open();
// 5.把服務(wù)器通道注冊到選擇器上
ssc1.register(selector, SelectionKey.OP_ACCEPT);
ssc2.register(selector, SelectionKey.OP_ACCEPT);
ssc3.register(selector, SelectionKey.OP_ACCEPT);
while (true){
System.out.println(1);
// 監(jiān)聽客戶端的請求
selector.select();
System.out.println(2);
}
}
}
Selector的keys()方法
-
獲取已注冊的所有通道集合
/** * Created by PengZhiLin on 2021/8/13 17:00 */ public class Test1_select方法 { public static void main(String[] args) throws Exception { // 1.打開服務(wù)器通道 ServerSocketChannel ssc1 = ServerSocketChannel.open(); ServerSocketChannel ssc2 = ServerSocketChannel.open(); ServerSocketChannel ssc3 = ServerSocketChannel.open(); // 2.綁定端口號 ssc1.bind(new InetSocketAddress(7777)); ssc2.bind(new InetSocketAddress(8888)); ssc3.bind(new InetSocketAddress(9999)); // 3.設(shè)置非阻塞 ssc1.configureBlocking(false); ssc2.configureBlocking(false); ssc3.configureBlocking(false); // 4.獲取選擇器 Selector selector = Selector.open(); // 5.把服務(wù)器通道注冊到選擇器上 ssc1.register(selector, SelectionKey.OP_ACCEPT); ssc2.register(selector, SelectionKey.OP_ACCEPT); ssc3.register(selector, SelectionKey.OP_ACCEPT); System.out.println("已注冊的所有通道集合:" + selector.keys().size()); while (true) { System.out.println(1); // 監(jiān)聽客戶端的請求 selector.select(); System.out.println(2); System.out.println("已注冊的所有通道集合:" + selector.keys().size()); } } }
Selector的selectedKeys()方法
- 獲取已連接的所有通道集合
/**
* Created by PengZhiLin on 2021/8/13 17:00
*/
public class Test2_selectedKeys方法 {
public static void main(String[] args) throws Exception {
// 1.打開服務(wù)器通道
ServerSocketChannel ssc1 = ServerSocketChannel.open();
ServerSocketChannel ssc2 = ServerSocketChannel.open();
ServerSocketChannel ssc3 = ServerSocketChannel.open();
// 2.綁定端口號
ssc1.bind(new InetSocketAddress(7777));
ssc2.bind(new InetSocketAddress(8888));
ssc3.bind(new InetSocketAddress(9999));
// 3.設(shè)置非阻塞
ssc1.configureBlocking(false);
ssc2.configureBlocking(false);
ssc3.configureBlocking(false);
// 4.獲取選擇器
Selector selector = Selector.open();
// 5.把服務(wù)器通道注冊到選擇器上
ssc1.register(selector, SelectionKey.OP_ACCEPT);
ssc2.register(selector, SelectionKey.OP_ACCEPT);
ssc3.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("已注冊的所有通道集合:" + selector.keys().size());
while (true) {
System.out.println(1);
// 監(jiān)聽客戶端的請求
selector.select();
System.out.println(2);
// 處理客戶端的請求
// 獲取已連接的所有通道集合
Set<SelectionKey> keys = selector.selectedKeys();
// 循環(huán)遍歷所有通道集合
for (SelectionKey key : keys) {
// SelectionKey-->其實就是封裝了ServerSocketChannel
// 通過SelectionKey獲得ServerSocketChannel
// SelectableChannel是ServerSocketChannel的父類
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
// 處理客戶端的請求,建立連接
SocketChannel sc = ssc.accept();
// 接收客戶端發(fā)過來的數(shù)據(jù)
ByteBuffer b = ByteBuffer.allocate(1024);
int len = sc.read(b);
System.out.println("服務(wù)器接收到的數(shù)據(jù)是:" + new String(b.array(), 0, len));
// 處理完了,釋放資源
sc.close();
}
System.out.println("已注冊的所有通道集合:" + selector.keys().size());
}
}
}
7.4 Selector多路復(fù)用
需求
- 使用Selector進(jìn)行多路復(fù)用,監(jiān)聽3個服務(wù)器端口
分析
- 創(chuàng)建3個服務(wù)器通道,設(shè)置成非阻塞
- 獲取Selector選擇器
- 把Selector注冊到三個服務(wù)器通道上
- 循環(huán)去等待客戶端連接
- 遍歷所有被連接的服務(wù)器通道集合
- 處理客戶端請求
實現(xiàn)
-
案例:
/** * Created by PengZhiLin on 2021/8/13 17:00 */ public class Test { public static void main(String[] args) throws Exception { // 1.打開服務(wù)器通道 ServerSocketChannel ssc1 = ServerSocketChannel.open(); ServerSocketChannel ssc2 = ServerSocketChannel.open(); ServerSocketChannel ssc3 = ServerSocketChannel.open(); // 2.綁定端口號 ssc1.bind(new InetSocketAddress(7777)); ssc2.bind(new InetSocketAddress(8888)); ssc3.bind(new InetSocketAddress(9999)); // 3.設(shè)置非阻塞 ssc1.configureBlocking(false); ssc2.configureBlocking(false); ssc3.configureBlocking(false); // 4.獲取選擇器 Selector selector = Selector.open(); // 5.把服務(wù)器通道注冊到選擇器上 ssc1.register(selector, SelectionKey.OP_ACCEPT); ssc2.register(selector, SelectionKey.OP_ACCEPT); ssc3.register(selector, SelectionKey.OP_ACCEPT); System.out.println("已注冊的所有通道集合:" + selector.keys().size()); while (true) { System.out.println(1); // 監(jiān)聽客戶端的請求 selector.select(); System.out.println(2); // 處理客戶端的請求 // 獲取已連接的所有通道集合 Set<SelectionKey> keys = selector.selectedKeys(); // 循環(huán)遍歷所有通道集合 Iterator<SelectionKey> it = keys.iterator(); while (it.hasNext()) { SelectionKey key = it.next(); // SelectionKey-->其實就是封裝了ServerSocketChannel // 通過SelectionKey獲得ServerSocketChannel // SelectableChannel是ServerSocketChannel的父類 ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); // 處理客戶端的請求,建立連接 SocketChannel sc = ssc.accept();// null // 接收客戶端發(fā)過來的數(shù)據(jù) ByteBuffer b = ByteBuffer.allocate(1024); int len = sc.read(b); System.out.println("服務(wù)器接收到的數(shù)據(jù)是:" + new String(b.array(), 0, len)); // 處理完了,釋放資源 sc.close(); // 移除keys集合中已處理的服務(wù)器通道--->迭代器 it.remove(); } System.out.println("已注冊的所有通道集合:" + selector.keys().size()); } } } -
問題: Selector把所有被連接的服務(wù)器對象放在了一個Set集合中,但是使用完后并沒有刪除,導(dǎo)致在遍歷集合時,遍歷到已經(jīng)沒用的對象,出現(xiàn)了異常
-
解決辦法: 使用完了,應(yīng)該從集合中刪除,由于遍歷的同時集合不能刪除,所以使用迭代器進(jìn)行遍歷
第八章 NIO2-AIO(異步、非阻塞)
8.1 AIO概述
同步,異步,阻塞,非阻塞概念回顧
- 同步:調(diào)用方法之后,必須要得到一個返回值。
- 異步:調(diào)用方法之后,沒有返回值,但是會有回調(diào)函數(shù)。回調(diào)函數(shù)指的是滿足條件之后會自動執(zhí)行的方法
- 阻塞:如果沒有達(dá)到方法的目的,就一直停在這里【等待】。
- 非阻塞:不管有沒有達(dá)到目的,都直接【往下執(zhí)行】。
IO: 同步阻塞
NIO: 同步阻塞,同步非阻塞
NIO2:異步非阻塞

AIO相關(guān)類和方法介紹
AIO是異步IO的縮寫,雖然NIO在網(wǎng)絡(luò)操作中,提供了非阻塞的方法,但是NIO的IO行為還是同步的。對于NIO來說,我們的業(yè)務(wù)線程是在IO操作準(zhǔn)備好時,得到通知,接著就由這個線程自行進(jìn)行IO操作,IO操作本身是同步的。
但是對AIO來說,則更加進(jìn)了一步,它不是在IO準(zhǔn)備好時再通知線程,而是在IO操作已經(jīng)完成后,再給線程發(fā)出通知。因此AIO是不會阻塞的,此時我們的業(yè)務(wù)邏輯將變成一個回調(diào)函數(shù),等待IO操作完成后,由系統(tǒng)自動觸發(fā)。
與NIO不同,當(dāng)進(jìn)行讀寫操作時,只須直接調(diào)用API的read或write方法即可。這兩種方法均為異步的,對于讀操作而言,當(dāng)有流可讀取時,操作系統(tǒng)會將可讀的流傳入read方法的緩沖區(qū),并通知應(yīng)用程序;對于寫操作而言,當(dāng)操作系統(tǒng)將write方法傳遞的流寫入完畢時,操作系統(tǒng)主動通知應(yīng)用程序。 即可以理解為,read/write方法都是異步的,完成后會主動調(diào)用回調(diào)函數(shù)。 在JDK1.7中,這部分內(nèi)容被稱作NIO.2---->AIO,主要在Java.nio.channels包下增加了下面四個異步通道:
- AsynchronousSocketChannel
- AsynchronousServerSocketChannel
- AsynchronousFileChannel
- AsynchronousDatagramChannel
在AIO socket編程中,服務(wù)端通道是AsynchronousServerSocketChannel,這個類提供了一個open()靜態(tài)工廠,一個bind()方法用于綁定服務(wù)端IP地址(還有端口號),另外還提供了accept()用于接收用戶連接請求。在客戶端使用的通道是AsynchronousSocketChannel,這個通道處理提供open靜態(tài)工廠方法外,還提供了read和write方法。
在AIO編程中,發(fā)出一個事件(accept read write等)之后要指定事件處理類(回調(diào)函數(shù)),AIO中的事件處理類是CompletionHandler<V,A>,這個接口定義了如下兩個方法,分別在異步操作成功和失敗時被回調(diào)。
void completed(V result, A attachment);
void failed(Throwable exc, A attachment);
8.2 AIO 異步非阻塞連接
需求
- AIO異步非阻塞的連接方法
分析
- 獲取AsynchronousServerSocketChannel對象,綁定端口
- 異步接收客戶端請求
- void accept(A attachment, CompletionHandler<AsynchronousSocketChannel,? super A> handler)
- 第一個參數(shù): 附件,沒啥用,傳入null即可
- 第二個參數(shù): CompletionHandler接口 ,AIO中的事件處理接口
- void completed(V result, A attachment);異步連接成功,就會自動調(diào)用這個方法
- void failed(Throwable exc, A attachment);異步連接失敗,就會自動調(diào)用這個方法
實現(xiàn)
- 服務(wù)器端:
/**
* Created by PengZhiLin on 2021/8/13 17:43
*/
public class Server {
public static void main(String[] args) throws Exception{
// 1.打開異步服務(wù)器通道
AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open();
// 2.綁定端口號
assc.bind(new InetSocketAddress(7777));
// 3.接收請求,建立連接
System.out.println(1);
assc.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel result, Object attachment) {
// 連接成功,就會來到這里
System.out.println(2);
}
@Override
public void failed(Throwable exc, Object attachment) {
// 連接失敗,就會來到這里
System.out.println(3);
}
});
System.out.println(4);
while (true){
}
}
}
小結(jié)
略
8.3 AIO 異步非阻塞連接和異步讀
需求
- 實現(xiàn)異步連接,異步讀
分析
- 獲取AsynchronousServerSocketChannel對象,綁定端口
- 異步接收客戶端請求
- 在CompletionHandler的completed方法中異步讀數(shù)據(jù)
實現(xiàn)
- 服務(wù)器端代碼:
/**
* Created by PengZhiLin on 2021/8/13 17:51
*/
public class Server {
public static void main(String[] args)throws Exception{
// 1.打開異步服務(wù)器通道
AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open();
// 2.綁定端口號
assc.bind(new InetSocketAddress(7777));
// 3.接收請求,建立連接
System.out.println(1);
assc.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel asc, Object attachment) {
// 連接成功
System.out.println(2);
// 創(chuàng)建ByteBuffer對象
ByteBuffer b = ByteBuffer.allocate(1024);
// 異步讀
asc.read(b, null, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer len, Object attachment) {
// 讀成功
System.out.println(5);
System.out.println("服務(wù)器接收到的信息:"+new String(b.array(),0,len));
}
@Override
public void failed(Throwable exc, Object attachment) {
// 讀失敗
System.out.println(6);
}
});
System.out.println(7);
}
@Override
public void failed(Throwable exc, Object attachment) {
// 連接失敗
System.out.println(3);
}
});
System.out.println(4);
// 為了讓程序不結(jié)束
while (true){
}
}
}
總結(jié)
練習(xí):
1.TCP模擬聊天程序(客戶端和服務(wù)器互發(fā)字符串?dāng)?shù)據(jù))---->必須
2.TCP模擬文件上傳---->必須
3.使用FileChannel拷貝文件---->理解
4.使用MappedByteBuffer拷貝2GB以上的文件---->理解
5.Selector多路復(fù)用---->理解
6.服務(wù)器異步連接異步讀,實現(xiàn)服務(wù)器接收客戶端的信息---->理解
- 能夠辨別UDP和TCP協(xié)議特點
- TCP: 面向連接,傳輸數(shù)據(jù)安全,傳輸速度慢
- UDP: 面向無連接,傳輸不數(shù)據(jù)安全,傳輸速度快
- 能夠說出TCP協(xié)議下兩個常用類名稱
- Socket : 一個該類的對象就代表一個客戶端程序。
- Socket(String host, int port) 根據(jù)ip地址字符串和端口號創(chuàng)建客戶端Socket對象
- 注意事項:只要執(zhí)行該方法,就會立即連接指定的服務(wù)器程序,如果連接不成功,則會拋出異常。
如果連接成功,則表示三次握手通過。
- OutputStream getOutputStream(); 獲得字節(jié)輸出流對象
- InputStream getInputStream();獲得字節(jié)輸入流對象
- void close();關(guān)閉Socket, 會自動關(guān)閉相關(guān)的流
- 補充:關(guān)閉通過socket獲得的流,會關(guān)閉socket,關(guān)閉socket,同時也會關(guān)閉通過socket獲得的流
- ServerSocket : 一個該類的對象就代表一個服務(wù)器端程序。
- ServerSocket(int port); 根據(jù)指定的端口號開啟服務(wù)器。
- Socket accept(); 等待客戶端連接并獲得與客戶端關(guān)聯(lián)的Socket對象 如果沒有客戶端連接,該方法會一直阻塞
- void close();關(guān)閉ServerSocket
- 能夠編寫TCP協(xié)議下字符串?dāng)?shù)據(jù)傳輸程序
- 能夠理解TCP協(xié)議下文件上傳案例
- 能夠理解TCP協(xié)議下BS案例
- 能夠說出NIO的優(yōu)點
解決高并發(fā),提高cpu執(zhí)行效率
浙公網(wǎng)安備 33010602011771號