文章目錄
第一章 Java網絡編程入門
1.前言
我們都知道打開一個瀏覽器,輸入一個URL
如:http://baidu.com,這個地址指向的網址就會從遠程的Web服務器傳到客戶端,然後在瀏覽器裏邊顯示出來。
再者,李四給張三送信,李四不用把信親自送到張三家,而是將信用信封包裝起來,然後通過郵政網絡完成送信,張三收到信,拆開信封,讀取信息。也就是說我們不用知道怎麼發,我們知道發什麼就行了。
所以,服務器程序和客戶程序也一樣,只關心發送什麼樣的數據給對方,而不需要考慮如何將這些數據傳輸給對方。傳輸數據任務由計算機網絡完成。
這樣我們就能得到如下圖的網絡傳輸過程:
本章學習內容:1、計算機基本概念;2、計算機分層模型及具體網絡協議;3、使用一個例子體驗如何創建簡單的Java服務器和Java客戶程序。
其中第二點是重點!
1.1 進程之間的通信
我們知道進程就是一個程序運行,線程就是進程中的任務。
下面來看一個例子:
package 網絡編程;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* 第一章1.1 進程之間的通信演示。
* @author Mr Lin,time 2020-6-4
* @version 1.0
*/
public class Chapter1_1_EchoPlayer {
public String echo(String msg){
return "echo"+msg;
}
public void talk()throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));//轉換流
String msg = null;
while((msg=br.readLine())!=null){
System.out.println(echo(msg));
if("bye".equals(msg))
break;
}
}
public static void main(String[]args) throws IOException {
new Chapter1_1_EchoPlayer().talk();
}
}
運行這個程序,則啓動jvm虛擬機,進入main線程,該進程對應的main線程從本地控制檯獲取輸入流和輸出流。本地控制檯就是一個用戶交互的界面,用戶通過該界面與進程交換
問題的引出:
1、上面程序的echo(String msg)方法負責生成響應結果
,但如果我們要生成響應的程序移動到了遠程主機
,那麼上面的程序就無法滿足需求了。這種情況怎麼做?
解決:
這個時候,我們需要兩個程序——1是客戶程序EchoClient ;2是服務器程序EchoServer.
客戶程序EchoClient
:負責與用戶交互,從本地控制檯獲得標準輸入流和輸出流;同時可以和遠程的EchoServer通信,向EchoServer發送用戶輸入的字符串EchoServer程序
:負責接受EchoClient發過來的字符串,然後把響應的結果發送回去給EchoClient
也就是說客戶機運行EchoClient進程,遠程服務器運行EchoServer進程。客戶機和遠程服務器是通過網絡連接的兩臺主機
能否通信的前提是兩個進程的主機都連接到了計算機網絡。就好像張三和李四通話,他們能通話的前提是他們的電話都能連接到電話的網絡(有信號)。此時他們只需要知道談話的內容,而不必關心自己的話語怎麼傳輸給對方的電話機
即傳輸信息任務由計算機網絡來完成。我們只需要編寫好客戶程序EchoClient和服務器程序EchoServer。
1.2 計算機網絡概念
1、
計算機網絡
:指分佈在不同地理區域的計算機用通信線路
互連起來的網絡系統。通俗點說就是:
計算機網絡就是通過電纜、電話線或者無線通信設備互連的計算機合集。
2、
節點
:網絡中的每臺機器都是一個節點(node)
,大多數是計算機,也可以是連接了網絡的打印機、路由器網關、網橋等。我們一般用主機代表計算機節點
3、
網絡協議
:理解爲主機之間通信的語言
,就好比人與人之間用某種語言交流。
4、
主機地址
:網絡中的每一臺主機都有一個地址,用於標識主機的一個字節序列
,字節序列越長,可以表示的地址就越多,也就是說可以有更多的設備連入網絡。
5、
局域網(LAN,Local Area Network)和廣域網(WAN,Wide Area Network)
:局域網
就是小範圍的計算機網絡,通常限制在幾千米內的範圍
,如一個房間、一個校園;廣域網
則範圍很大
,常常是一個國家或者洲,如互聯網範圍是全球各州。廣域網就是讓分佈較遠的局域網互聯!
6、
協議與標準協議
:就好像互聯網連接了衆多的局域網,每個小網絡內部可能使用不同的協議,那如何使得不同的網絡可以交流?好比講粵語的廣東人遇到了講溫州話的溫州佬,就會用普通話交流一樣,會有一個標準語言——普通話
,與此類似網絡
就有標準的語言——TCP/IP協議。
1.3 網絡模型之OSI參考模型
1、背景
計算機網絡產生之初,每個廠商都有自己的一套網絡體系,它們互不相容。繼而國際標準組織ISO建立了一個分委會,來研究怎麼統一。這個組織提出了一個體繫結構——開放系統互連(Open System Interconnection,簡稱OSI)
。
2、OSI模型的特點
- 開放意味着一個網絡系統只要遵循OSI模型,則可以和世界上任何地方的也同樣遵循OSI模型的網絡系統互連。它爲各種異構系統互連提供了概念性的框架。
3、OSI模型參考模型將網絡分成了7層
從下到上分別是——物理層
、數據鏈路層
、網絡層
、傳輸層
、會話層
、表示層
和應用層
對等層
:不同主機之間相同層稱爲對等層。例如,主機A中的表示層和主機B中的表示層互爲對等層
4、OSI參考模型中各層主要的功能
1、
物理層(Physical Layer)
:傳輸信息肯定離不開物理介質,如雙絞線和同軸電纜等,但是物理層不包括物理介質
。有人把物理介質當作了OSI模型的第零層。
物理層的任務
:爲其上一層提供物理連接,以及規定通信節點之間的機械和電氣等特性,如規定電纜和接線的類型等。
傳輸數據
:比特
(bit);典型設備
:Hub(集線器
)
2、
數據鏈路層(Data Link Layer)
:數據鏈路層負責在兩個相鄰的節點間的線路無差錯傳輸以幀爲單位的數據。每一幀包括一定的數據和一些必要的控制信息。
數據鏈路層任務
:負責建立、維持和釋放數據鏈路的連接。
在傳輸數據時,如果接收方檢測到所傳數據出錯,就要通知發送方重發這一幀。
傳輸數據
:幀
;典型設備
:Switch(交換機
)
3、
網絡層(Network Layer)
:數據發送要經過很多的數據鏈路,也可能通過很多通信子網。此時需要網絡層來做選擇.其將數據鏈路層提供的幀組成數據包
,包裏邊封裝了網絡層包頭
,包頭裏邊含有邏輯信息——源主機和目標主機的網絡地址。
網絡層任務
:選擇合適的網間路由和交換節點,確保數據及時傳說到目標主機。
傳輸數據
:包
;典型設備
:Router(路由器
)
如圖:主機A發送信息給B,先經過了節點1和節點4,最後達到主機B。相鄰兩個節點的線路就是數據鏈路。
數據鏈路層負責數據鏈路上的數據傳輸。從主機到主機B的整個路徑
就是路由
,網絡層就是負責選擇合適的路由。
4、
傳輸層(Transport Layer)
傳輸層任務
:根據通信子網的特性最佳利用網絡資源,爲兩個端系統(源主機和目標主機)的會話層提供建立、維護和取消傳輸連接的功能,以可靠方式或不可靠方式傳輸數據。
可靠方式
:保證把源主機發送的數據正確送到目標主機;
不可靠方式
:不保證把源主機發送的數據正確送到目標主機,數據可能出錯或者丟失。
傳輸單位
:報文
5、
會話層(Session Layer)
該層又叫會晤層或者對話層。會話層及以上層的傳輸單位不再另外命名,統稱爲報文。
會話層任務
:管理進程之間的會話過程,即負責建立、管理、終止進程之間的會話。還通過數據中插入校驗點來實現數據同步。
6、
表示層(Presentation Layer)
表示層任務
:將上層數據進行轉換,保證一個主機的應用層數據可以被另外一個主機應用層理解。
表示層的數據轉換包括數據加密、解密、壓縮、解壓和格式轉換等。
7、
應用層(Application Layer)
應用層確定進程之間通信的實際用途,以滿足用戶的實際需求。
如瀏覽Web站點、收發e-mail、下載文件等。這些都可以是進程之間通信的實際用途
。
如圖:當源主機向目標主機發送數據時,在源主機方
,數據由上層向下傳遞,每層都會給上一層傳來的數據加上一個信息頭(header),然後向下發出
。最後通過物理介質傳輸到目標主機。
在目標主機方,數據從下層向上層傳輸,每層都會對數據進行處理,把信息頭去掉。再向上層傳,最後達到最上層,還原爲實際數據。
封裝過程
:每個層所加入的信息頭有着不同的內容
,如網絡層加入的信息頭包括源地址和目標地址信息;傳輸層加入的信息頭包括報文類型、源端口和目標端口、序列號和應答號等。(圖中AH,PH…等分別表示各層加入的信息頭。注意!數據鏈路層還爲數據加入了信息尾DT
)
我們可以聯繫現實:張三給李四寄信,先要將信寫上很多信息:如發件人、收件人、日期、地址等。然後還要套入信封。這才寄出去。而李四收到信,先得拆開信封,才能得到信的內容。
OSI參考模型將網絡分爲多個層次,每個層都明確分工,這簡化了網絡系統的設計過程。如:設計網絡層只需要考慮如何找到一條發送數據的路徑(即路由)即可。
對等層之間互相通信需要遵循一定的規則,如通信的內容和方式,這種規定稱爲網絡協議(Protocol)。值得注意的是OSI參考模型並沒有具體實現方式,它沒有在各層制定網絡協議,但是它爲其他計算機廠商制定網絡協議提供了參考框架。網絡的各個層次都有相應的協議!
下面是OSI各個層的典型協議,都來自第三方
1.4 TCP/IP參考模型和TCP/IP協議
1.4.1 模型解析
由於OSI模型過於龐大和複雜,使其難以投入實際運用。但是其提出的網絡分層極具指導意義!TCP/IP參考模型就是吸取了網絡分層這一點,且簡化了網絡的層次,並在網絡的各層(除去主機-網絡層外)都提供了完善的協議。這些協議就構成了TCP/IP協議集,簡稱TCP/IP協議。是目前最流行的上商業化協議。主要用於廣域網。
兩種參考模型對比:
所以:TCP/IP參考模型將OSI參考模型中的會話層和表示層的功能合併到了應用層,同時將數據鏈路層和物理層合併到主機-網絡層。
TCP/IP參考模型分爲4個層次:應用層、傳輸層、網絡互聯層和主機-網絡層
。每一層都有相應的協議。所以確切地說TCP/IP協議應該稱爲TCP/IP協議集
,它是TCP/IP參考模型中除了最底下的一層外的其他三層協議的集合!
TCP/IP協議這個協議集合中的最核心協議是IP協議和TCP協議
。主機-網絡層是最底層,其協議由第三方提供。
從表中我們可以看出:應用層的FTP、TELNET、HTTP協議建立在TCP協議基礎上;SNMP、DNS協議又建立在UDP協議上。
各層的主要功能介紹
1、
主機-網絡層
實際上TCP/IP參考模型並沒有真正提供這一層的實現,也沒有提供協議。
它要求第三方實現的主機-網絡層能夠爲上層(網絡互聯層)提供一訪問接口,使得網絡互聯層能利用主機-網絡層傳遞IP數據包
。
相關的第三方協議:
2、
網絡互聯層
是整個參考模型的核心
。功能是將IP數據包發送到目標主機
。爲了儘快發送數據,IP協議會將原始數據分爲多個數據包,然後沿着不同的路徑同時傳遞數據包
。如圖:主機A發送的原始數據分爲3個數據包,然後沿不同路徑到達主機B,殊途同歸。注意!數據包到達的先後順序可能與發送的先後順序不同,所以需要上層(傳輸層)對數據包進行重新排序,才能還原爲原始數據
!
網絡互聯層具備連接異構網的功能
。如圖所示,連接以太網和令牌環網,雖然它們的類型不同,具有不同的拓撲結構,但是它們都向網絡互聯層提供了統一的訪問接口,從而隱藏了下層網絡的差異
!使得不同類型網絡之間可以順利傳輸數據包。
網絡互聯層採用IP協議,它規定了數據包格式,且規定了數據包尋找路由的流程。
3、
傳輸層
傳輸層功能就是使源主機和目標主機的進程可以進行會話。
傳輸層定義了兩種不同服務質量的協議——TCP
(Transmission Control Protocol,傳輸控制協議)和UDP
(User Datagram Protocol,用戶數據報協議)。
TCP協議是可靠的面向連接的協議,能把源主機數據無差錯地發送給目標主機。
在發送端,TCP協議負責把上層傳下來的數據分成報文段傳遞給下層。在接收端,將收到的報文進行重組後遞交給上層。還要處理端到端的流量控制,以避免接收方因數據接受慢而導致沒有足夠的緩衝區來接受發送方發送的大量數據。應用層協議很多都是建立在TCP協議基礎上的。如:FTP,HTTP,TELNET;
UDP協議則是一個不可靠的、無連接協議
,主要使用於不需要對報文進行排序和流量控制的場合
。它無法保證數據報接受的順序和發送的順序一致,甚至無法保證發送的信息都抵達目標主機
。應用層的一些協議建立於UDP協議之上,如SNMP,DNS等。
4、應用層
TCP/IP協議將OSI參考模型的會話層和表示層合併到了應用層實現,針對各種各樣的網絡應用,應用層引入了許多協議。其中基於TCP/IP協議的主要有如下幾種:
注意!!!HTTP和HTTPS的區別:HTTP是數據不加密的,HTTPS是數據經過加密的
1.4.2 IP協議
1、IP網絡
IP網絡就是在網絡層採用了IP協議。IP網絡中的每一臺主機都有唯一的IP地址。
用於標識網絡的主機。IP地址是一個32位的二進制序列
。爲了方便在上層應用中表示IP地址,將32位二進制數每8位一組劃分爲了4個單元,每個單元用一個十進制整數表示,即2的8次方範圍(0~255);如某臺主機IP地址:192.168.1.1
——這就是IPv4標準;
新的Ipv6標準將使用128位的地址,進而大大提高了可用IP地址的數目,將成爲新一代互聯網協議。
2、IP地址與IP網址
IP地址由兩部分組成:IP網址和IP主機地址
。IP網址就是網絡的地址
,IP主機地址就是網絡中主機的地址
。
網絡掩碼就是用來確定IP地址的哪部分是IP網址,哪一部分是IP主機地址。
網絡掩碼與IP地址形式一致,但是有一定的限制。網絡掩碼的二進制序列中,前面的部分都是1,後面的部分都是0,如IP地址假設:192.166.3.4,其網絡掩碼爲255.255.255.0,將網絡掩碼和IP地址進行與操作可得IP網址
。因此IP地址192.166.3.4的IP網址爲192.166.3.0
即主機IP地址&網絡掩碼=IP網址
從圖中我們可以看出,一個網絡可以對應多個主機IP地址,IP地址(主機的地址)與 網絡掩碼與操作得到的IP網址一致。
3、子網劃分
一個公司可能擁有一個網址和多個主機,例如192.166.0.0允許容納的主機地址有28*28-2=(65534);
所以主機數目過多則難以管理,進而需要將一個網絡劃分爲多個子網
,如將該網址劃分爲3個子網:192.166.1.0
,192.166.2.0
,192.166.3.0
,這些子網,很明顯它們的網絡掩碼都是255.255.255.0
;
4、發送數據包的過程
5、域名
域名就是因爲IP地址比較難記憶,
人們就發明一種字符型地址——域名(Domain Name)
。IP地址與域名一一對應。先來看一個 例子:www.javathinker.org,對應的IP地址是221.130.187.148;域名從右往左表述其意義。最右邊是頂層域,最左邊則是機器名。一般的表示:主機機器名.單位名.網絡名.頂層域名。
如:mail.xyz.edu.cn中mail表示xyz學校的一個主機機器名,xyz表示一個學校,edu爲中國教育科研網,cn是中國。頂層域一般是網絡機構所在的國家或者地區名稱縮寫。
如何將域名和IP地址進行轉換?
圖解
6、URL(統一資源定位器)
URL(Uniform Resource Locator)統一資源定位器,專爲標識網絡資源位置而設置的一種編址方式。大家熟悉的網址地址就是URL,一般由3部分組成:
1.4.3 TCP協議和端口
1、TCP協議
IP協議在發送數據時,途中遇到各種事情可能導致丟包,如路由器崩潰,又如一個包沿低速鏈路移動而另一個包沿高速鏈路移動而超過前面的包導致的順序錯亂
而TCP協議使兩臺主機的進程能夠順利通信,而不必擔心丟包或者包的順序搞亂。
因爲TCP會跟蹤包順序,並且在包的順序搞亂時對包進行重新排序。如果包丟失則TCP會請求源主機重新發送包。
2、端口
一個很簡單的問題,主機A的進程QQ向另一臺主機B的QQ發送信息
IP協議會根據主機B的IP地址,把A主機QQ進程發送的信息送達到主機B
- 接下來TCP需要決定把數據發送到主機B中的哪個進程。
TCP採用端口來區分進程
,端口不是物理設備,而是標識進程的一個邏輯地址,更加確切地說是用於標識TCP連接的端點的邏輯地址。主機B的QQ被主機B隨機分配了一個未被佔用的端口,假設是80,而主機A的QQ端口是1000,則意味着兩個進程進行了一次通信就建立了一次端到端的TCP連接。兩個端點就是用端口標識。此時主機B進程QQ的地址是B:80,主機A進程QQ的地址是A:1000。每個進程都有了唯一的地址,TCP就能保證數據順利送達特定的進程。
端口號的範圍:0——65535,其中0到1023端口號一般固定分配給一些服務。
如21端口分配給FTP服務,25端口分配給SMTP(簡單郵件傳輸)服務。80端口分配給HTTP服務(超文本傳輸)等。
端口1024到65535端口號供用戶自定義服務使用
。例如我們自己創建的EchoServer服務使用8000端口,程序運行時就會佔用該端口,程序結束才釋放該端口。
客戶進程的端口一般由主機所在的操作系統動態分配,當客戶進程需要與服務器進程TCP連接,操作系統就會給客戶進程分配一個未被佔用的端口來使用
,客戶進程和服務器進程斷開連接時端口才被釋放。此外還要指出TCP和UDP都是使用端口標識
,在一個主機中,TCP端口與UDP端口的取值範圍是各自獨立的!
允許取值相同的TCP端口值和UDP端口值
1.4.4 RFC簡介
1.4.5 客戶/服務器通信模式
1.5 用Java編寫客戶/服務器程序
需求:將開頭的程序中的客戶端移動到遠程主機,怎麼實現通信?
1、服務端代碼
package SocketProgram;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Chapter1_5_EchoServer {
private ServerSocket serverSocket;//創建監聽端口的對象
public Chapter1_5_EchoServer(int port) throws IOException {
//端口
serverSocket = new ServerSocket(port);
System.out.println("EchoServer start!");
}
public String echo(String msg) {
return "echo:" + msg;
}
private PrintWriter getWriter(Socket socket) throws IOException {
OutputStream socketOut = socket.getOutputStream();//獲取輸出流對象,用於做出響應。
return new PrintWriter(socketOut, true);//使用裝飾類
}
private BufferedReader getReader(Socket socket) throws IOException {
InputStream socketIn = socket.getInputStream();//獲取輸入流對象,用於接受請求。
return new BufferedReader(new InputStreamReader(socketIn));//使用轉換流和裝飾類
}
public void service() {
while (true) {//不斷監控端口
Socket socket = null;
try {
socket = serverSocket.accept();//等待客戶端連接。一旦返回socket對象意味着與一個客戶連接。
System.out.println("New connection accepted" + socket.getInetAddress() +
":" + socket.getPort());
BufferedReader br = getReader(socket);
PrintWriter pw = getWriter(socket);
String msg;
while ((msg = br.readLine()) != null) {//接受請求信息
System.out.println(msg);//打印
pw.println(echo(msg));//做出響應
if (msg.equals("bye")) {
break;//發來bye中止while,退出連接
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (socket != null)
socket.close();//斷開連接
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[]args) throws IOException {
new Chapter1_5_EchoServer(9000).service();
}
}
2、客戶端程序代碼:
package SocketProgram;
import java.io.*;
import java.net.Socket;
public class Chapter1_5_EchoClient {
private String host = "localhost";//主機地址爲本機。
private Socket socket;
public Chapter1_5_EchoClient(int port) throws IOException {
socket = new Socket(host,port);
}
public static void main(String[]args) throws IOException {
new Chapter1_5_EchoClient(9000).talk();
}
private void talk() {
try{
BufferedReader br = getReader(socket);
PrintWriter pw = getWriter(socket);
BufferedReader localReader = new BufferedReader(new InputStreamReader(System.in));
String msg = null;
while((msg=localReader.readLine())!=null){
pw.println(msg);//發送信息
System.out.println(br.readLine());//接受服務器的響應。
if(msg.equals("bye")){
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try{
socket.close();
}catch (IOException e){e.printStackTrace();}
}
}
private PrintWriter getWriter(Socket socket) throws IOException {
OutputStream socketOut = socket.getOutputStream();//獲取輸出流對象,用於做出響應。
return new PrintWriter(socketOut, true);//使用裝飾類
}
private BufferedReader getReader(Socket socket) throws IOException {
InputStream socketIn = socket.getInputStream();//獲取輸入流對象,用於接受請求。
return new BufferedReader(new InputStreamReader(socketIn));//使用轉換流和裝飾類
}
}
客戶端發送請求並收到服務端的響應:
服務器端接受到請求: