Java網絡編程總結
一、概述
計算機網絡是通過傳輸介質、通信設施和網絡通信協議,把分散在不同地點的計算機設備互連起來,實現資源共享和數據傳輸的系統。網絡編程就就是編寫程序使聯網的兩個(或多個)設備(例如計算機)之間進行數據傳輸。Java語言對網絡編程提供了良好的支持,通過其提供的接口我們可以很方便地進行網絡編程。下面先對網絡編程的一些基礎知識進行介紹,最后給出使用Java語言進行網絡編程的實例。
二、計算機網絡
計算機網絡20世紀60年代出現,經歷了20世紀70年代、80年代和90年代的發展,進入21世紀后,計算機網絡已經成為信息社會的基礎設施,深入到人類社會的方方面面,與人們的工作、學習和生活息息相關。
網絡協議
如同人與人之間相互交流是需要遵循一定的規矩一樣,計算機之間能夠進行相互通信是因為它們都共同遵守一定的規則,即網絡協議。
網絡體系結構
計算機網絡是個復雜的系統,按照人們解決復雜問題的方法,把計算機網絡實現的功能分到不同的層次上,層與層之間用接口連接。通信的雙方具有相同的層次,層次實現的功能由協議數據單元(PDU)來描述。不同系統中的同一層構成對等層,對等層之間通過對等層協議進行通信,理解彼此定義好的規則和約定。
計算機網絡體系結構是計算機網絡層次和協議的集合,網絡體系結構對計算機網絡實現的功能,以及網絡協議、層次、接口和服務進行了描述,但並不涉及具體的實現。接口是同一節點內相鄰層之間交換信息的連接處,也叫服務訪問點(SAP)。

三、OSI參考模型
前面我們介紹了計算機網絡的體系結構,因為計算機網絡是個復雜的系統,所以把計算機網絡實現的功能分到不同的層次上,而計算機網絡體系結構是計算機網絡層次和協議的集合。那么,計算機網絡如何進行分層呢?下面先介紹的是OSI參考模型。
簡介
世界上第一個網絡體系結構由IBM公司提出(1974年,SNA),以后其他公司也相繼提出自己的網絡體系結構如:Digital公司的DNA,美國國防部的TCP/IP等,多種網絡體系結構並存,其結果是若采用IBM的結構,只能選用IBM的產品,只能與同種結構的網絡互聯。
為了促進計算機網絡的發展,國際標准化組織ISO於1977年成立了一個委員會,在現有網絡的基礎上,提出了不基於具體機型、操作系統或公司的網絡體系結構,稱為開放系統互連參考模型,即OSI/RM (Open System Interconnection Reference Model)。OSI模型把網絡通信的工作分為7層,分別是物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層和應用層。

OSI模型層次功能
- 物理層
物理層處於OSI的最底層,是整個開放系統的基礎。物理層涉及通信信道上傳輸的原始比特流(bits),它的功能主要是為數據端設備提供傳送數據的通路以及傳輸數據。
- 數據鏈路層
數據鏈路層的主要任務是實現計算機網絡中相鄰節點之間的可靠傳輸,把原始的、有差錯的物理傳輸線路加上數據鏈路協議以后,構成邏輯上可靠的數據鏈路。需要完成的功能有鏈路管理、成幀、差錯控制以及流量控制等。其中成幀是對物理層的原始比特流進行界定,數據鏈路層也能夠對幀的丟失進行處理。
- 網絡層
網絡層涉及源主機節點到目的主機節點之間可靠的網絡傳輸,它需要完成的功能主要包括路由選擇、網絡尋址、流量控制、擁塞控制、網絡互連等。
- 傳輸層
傳輸層起着承上啟下的作用,涉及源端節點到目的端節點之間可靠的信息傳輸。傳輸層需要解決跨越網絡連接的建立和釋放,對底層不可靠的網絡,建立連接時需要三次握手,釋放連接時需要四次揮手。
- 會話層和表示層
會話層的主要功能是負責應用程序之間建立、維持和中斷會話,同時也提供對設備和結點之間的會話控制,協調系統和服務之間的交流,並通過提供單工、半雙工和全雙工3種不同的通信方式,使系統和服務之間有序地進行通信。
表示層關心所傳輸數據信息的格式定義,其主要功能是把應用層提供的信息變換為能夠共同理解的形式,提供字符代碼、數據格式、控制信息格式、加密等的統一表示。
- 應用層
應用層為OSI的最高層,是直接為應用進程提供服務的。其作用是在實現多個系統應用進程相互通信的同時,完成一系列業務處理所需的服務。
四、TCP/IP參考模型
OSI參考模型的初衷是提供全世界范圍的計算機網絡都要遵循的統一標准,但是由於存在模型和協議自身的缺陷,遲遲沒有成熟的產品推出。TCP/IP協議在實踐中不斷完善和發展取得成功,作為網絡的基礎,Internet的語言,可以說沒有TCP/IP協議就沒有互聯網的今天。
簡介
TCP/IP,即Transmission Control Protocol/Internet Protocol的簡寫,中譯名為傳輸控制協議/因特網互聯協議,是Internet最基本的協議、Internet國際互聯網絡的基礎。
TCP/IP協議是一個開放的網絡協議簇,它的名字主要取自最重要的網絡層IP協議和傳輸層TCP協議。TCP/IP協議定義了電子設備如何連入因特網,以及數據如何在它們之間傳輸的標准。TCP/IP參考模型采用4層的層級結構,每一層都呼叫它的下一層所提供的協議來完成自己的需求,這4個層次分別是:網絡接口層、互聯網層(IP層)、傳輸層(TCP層)、應用層。

OSI 和 TCP/IP模型對比
TCP/IP模型層次功能
- 網絡接口層
TCP/IP協議對網絡接口層沒有給出具體的描述,網絡接口層對應着物理層和數據鏈路層。
- 互聯網層 ( IP層 )
互聯網層是整個TCP/IP協議棧的核心。它的功能是把分組發往目標網絡或主機。同時,為了盡快地發送分組,可能需要沿不同的路徑同時進行分組傳遞。因此,分組到達的順序和發送的順序可能不同,這就需要上層必須對分組進行排序。互聯網層除了需要完成路由的功能外,也可以完成將不同類型的網絡(異構網)互連的任務。除此之外,互聯網層還需要完成擁塞控制的功能。
- 傳輸層 ( TCP層 )
TCP層負責在應用進程之間建立端到端的連接和可靠通信,它只存在與端節點中。TCP層涉及兩個協議,TCP和UDP。其中,TCP協議提供面向連接的服務,提供按字節流的有序、可靠傳輸,可以實現連接管理、差錯控制、流量控制、擁塞控制等。UDP協議提供無連接的服務,用於不需要或無法實現面向連接的網絡應用中。
- 應用層
應用層為Internet中的各種網絡應用提供服務。
五、常見網絡協議
上面主要介紹了OSI參考模型和TCP/IP模型的相關內容,從下面這張圖可以看出TCP/IP協議簇中不同的層次中有着很多不同的網絡協議,下面主要介紹傳輸層的TCP、UDP協議和應用層的HTTP協議。

TCP協議
- 簡介
TCP(Transmission Control Protocol ,傳輸控制協議)是面向連接的傳輸層協議。TCP層是位於IP層之上,應用層之下的中間層。不同主機的應用層之間經常需要可靠的、像管道一樣的連接,但是IP層不提供這樣的流機制,而是提供不可靠的包交換。TCP協議采用字節流傳輸數據。
- TCP報文段格式
TCP報文段包括協議首部和數據兩部分,協議首部的固定部分有20個字節,首部的固定部分后面是選項部分。

下面是報文段首部各個字段的含義
- 源端口號以及目的端口號,各占2個字節,端口是傳輸層和應用層的服務接口,用於尋找發送端和接收端的進程,一般來講,通過端口號和IP地址,可以唯一確定一個TCP連接,在網絡編程中,通常被稱為一個socket接口。
- 序號,占4字節,用來標識從TCP發送端向TCP接收端發送的數據字節流。
- 確認序號,占4字節,包含發送確認的一端所期望收到的下一個序號,因此,確認序號應該是上次已經成功收到數據字節序號加1.
- 數據偏移,占4位,用於指出TCP首部長度,若不存在選項,則這個值為20字節,數據偏移的最大值為60字節。
- 保留字段占6位,暫時可忽略,值全為0
- 標志位
URG(緊急) : 為1時表明緊急指針字段有效
ACK(確認):為1時表明確認號字段有效
PSH(推送):為1時接收方應盡快將這個報文段交給應用層
RST(復位):為1時表明TCP連接出現故障必須重建連接
SYN(同步):在連接建立時用來同步序號
FIN (終止): 為1時表明發送端數據發送完畢要求釋放連接 - 接收窗口占2個字節,用於流量控制和擁塞控制,表示當前接收緩沖區的大小。在計算機網絡中,通常是用接收方的接收能力的大小來控制發送方的數據發送量。TCP連接的一端根據緩沖區大小確定自己的接收窗口值,告訴對方,使對方可以確定發送數據的字節數。
- 校驗和占2個字節,范圍包括首部和數據兩部分。
- 選項是可選的,默認情況是不選。
三次握手與四次揮手
TCP是面向連接的協議,因此每個TCP連接都有3個階段:連接建立、數據傳送和連接釋放。連接建立經歷三個步驟,通常稱為“三次握手”。
TCP三次握手過程如下:

TCP三次握手
- 第一次握手
客戶機發送連接請求報文段到服務器,並進入SYN_SENT狀態,等待服務器確認。(SYN = 1,seq=x) - 第二次握手
服務器收到連接請求報文,如果同意建立連接,向客戶機發回確認報文段,並為該TCP連接分配TCP緩存和變量。(SYN=1,ACK=1,seq=y,ack=x+1)。 - 第三次握手
客戶機收到服務器的確認報文段后,向服務器給出確認報文段,並且也要給該連接分配緩存和變量。此包發送完畢,客戶端和服務器進入ESTABLISHED(TCP連接成功)狀態,完成三次握手。(ACK=1,seq=x+1,ack=y+1)。
TCP四次揮手過程如下:

由於TCP連接是全雙工的,因此每個方向都必須單獨進行關閉。這原則是當一方完成它的數據發送任務后就能發送一個FIN來終止這個方向的連接。收到一個 FIN只意味着這一方向上沒有數據流動,一個TCP連接在收到一個FIN后仍能發送數據。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。
- TCP客戶端發送一個FIN,用來關閉客戶到服務器的數據傳送。
- 服務器收到這個FIN,它發回一個ACK,確認序號為收到的序號加1。和SYN一樣,一個FIN將占用一個序號。
- 服務器關閉客戶端的連接,發送一個FIN給客戶端。
- 客戶端發回ACK報文確認,並將確認序號設置為收到序號加1。
UDP協議
- 簡介
UDP,用戶數據報協議,英文全稱是User Datagram Protocol,它是TCP/IP協議簇中無連接的運輸層協議。
- UDP協議格式

從圖中可以看到,UDP協議十分簡單,它由兩部分組成:首部和數據。其中,首部僅有8個字節,包括源端口和目的端口,長度(UDP用於數據報的長度)、校驗和。
HTTP協議
- 簡介
HTTP,超文本傳輸協議,英文全稱是Hypertext Transfer Protocol,它是互聯網上應用最為廣泛的一種網絡協議。HTTP是一種應用層協議,它是基於TCP協議之上的請求/響應式的協議,即一個客戶端與服務器建立連接后,向服務器發送一個請求;服務器接到請求后,給予相應的響應信息。HTTP協議默認的端口號為80.
現在使用的HTTP協議是HTTP/1.1版本,1997年之前采用的是HTTP1.0版本。HTTP連接在1.0版本中采用非持續連接工作方式,1.1版本采用的是持續連接工作方式,持續連接是指服務器在發送響應后仍然在一段時間內保持這條由TCP運輸層協議建立起來的連接,使客戶機和服務器可以繼續在這條連接上傳輸HTTP報文。
是否采用持續連接工作方式,1.0中默認是關閉的,需要在HTTP頭加入"Connection:Keep-Alive",才能啟用Keep-Alive。HTTP1.1中默認啟用Keep-Alive,如果加入"Connection:close",才關閉。目前大部分瀏覽器都是用HTTP1.1協議,也就是說默認都會發起Keep-Alive的連接請求了,所以是否能完成一個完整的Keep- Alive連接就看服務器設置情況。
- HTTP報文
HTTP協議是基於TCP協議之上的請求/響應式協議,下面主要介紹HTTP報文的格式,HTTP報文主要有請求報文和響應報文兩種。首先看請求報文的格式:

HTTP請求報文格式
HTTP請求報文由請求行、首部行和實體主體組成,由瀏覽器發送給服務器。上面這張圖中SP表示空格,cr lf表示回車和換行。

上面這張圖是HTTP響應報文,它由狀態行、首部行和實體主體組成。下面兩張圖是在谷歌瀏覽器內訪問服務器查看的HTTP請求和響應。

HTTP請求報文例子

HTTP請求方法和響應狀態碼
在上面的HTTP請求報文例子中,我們可以看到請求方法是GET,這表示請求讀取由URL所標志的信息,除了GET,還有其它幾種常用的方法。

在HTTP響應報文的例子中,我們可以看到狀態碼是200,表示響應成功。下表是其它狀態碼,總共5大類,33種。
HTTP響應報文的狀態碼

HTTPS和HTTP的區別
HTTPS(全稱:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全為目標的HTTP通道,簡單講是HTTP的安全版。即HTTP下加入SSL層,HTTPS的安全基礎是SSL,因此加密的詳細內容就需要SSL。它是一個URI scheme(抽象標識符體系),句法類同http:體系。用於安全的HTTP數據傳輸。https:URL表明它使用了HTTP,但HTTPS存在不同於HTTP的默認端口及一個加密/身份驗證層(在HTTP與TCP之間)。
超文本傳輸協議HTTP協議被用於在Web瀏覽器和網站服務器之間傳遞信息。HTTP協議以明文方式發送內容,不提供任何方式的數據加密,如果攻擊者截取了Web瀏覽器和網站服務器之間的傳輸報文,就可以直接讀懂其中的信息,因此HTTP協議不適合傳輸一些敏感信息,比如信用卡號、密碼等。
為了解決HTTP協議的這一缺陷,需要使用另一種協議:安全套接字層超文本傳輸協議HTTPS。為了數據傳輸的安全,HTTPS在HTTP的基礎上加入了SSL協議,SSL依靠證書來驗證服務器的身份,並為瀏覽器和服務器之間的通信加密。
HTTPS和HTTP的區別主要為以下四點:1、https協議需要到ca申請證書,一般免費證書很少,需要交費。2、http是超文本傳輸協議,信息是明文傳輸,https 則是具有安全性的ssl加密傳輸協議。3、http和https使用的是完全不同的連接方式,用的端口也不一樣,前者是80,后者是443。4、http的連接很簡單,是無狀態的;HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,比http協議安全。
六、常見問題
到這里,關於計算機網絡部分的總結內容就結束了,下面是幾個常見的問題,匯總在這里。
-
OSI參考模型的分為哪幾層,每層的功能?
OSI,開放系統互連參考模型,它的7個層次自頂到下依次為應用層,表示層,會話層,傳輸層,網絡層,數據鏈路層和物理層。各層的功能見文章開始。 -
TCP協議和UDP協議的區別?
TCP協議是傳輸控制協議,UDP協議是用戶數據報協議,兩者都是傳輸層的協議,主要區別在於前者是可靠的,面向連接的協議,后者是不可靠的,無連接的協議。其它的區別還有,TCP協議傳輸速度慢,UDP常用於一次性傳輸比較少量數據的網絡應用。 -
TCP三次握手為什么不能是兩次?
主要是防止兩次握手情況下已經失效的連接請求報文段突然又傳送到服務端而產生錯誤。例如,客戶機A向服務器B發送TCP連接請求,第一個連接請求報文在網絡的某個節點長時間滯留,A超時后認為報文丟失,於是再重傳一次連接請求,B收到后建立連接。數據傳輸完畢后雙方斷開連接,而這時之前滯留的連接請求到達了服務端B,而B認為A又發來連接請求。如果兩次握手建立連接,A並無連接請求,造成B的資源浪費。 -
HTTP請求的GET方法和POST方法的區別?
GET和POST是HTTP請求的兩種方法,主要區別在於GET方法是請求讀取由URL所標志的信息,POST是給服務器添加信息。 -
在瀏覽器中輸入網址到顯示出頁面的整個過程?
(1) 輸出包含域名的網址 (2) 瀏覽器向DNS請求解析域名對應的IP地址 (3) 域名系統DNS解析出域名對應的IP地址 (4) 瀏覽器與該服務器建立TCP連接 (5) 瀏覽器發送HTTP請求 (6) 服務器通過HTTP響應把頁面文件發送給瀏覽器 (7) TCP連接釋放 (8) 瀏覽器解釋文件,並顯示
七、Socket
Socket是網絡驅動層提供給應用程序編程接口和一種機制。我們可以把 Socket 比喻成是一個港口碼頭。應用程序只要把貨物放到港口碼頭上,就算完成了貨物的運送。對於接收方應用程序也要創建一個港口碼頭,只需要等待貨物到達碼頭后將貨物取走。
Socket 是在應用程序中創建的,它是通過一種綁定機制與驅動程序建立關系,告訴自己所對應的 IP 和 Port。在網絡上傳輸的每一個數據幀,必須包含發送者的 IP 地址和端口號。創建完 Socket 以后,應用程序寫入到 Socket 的數據,由 Socket 交給驅動程序向網絡上發送數據,計算機從網絡上收到與某個 Socket 綁定的 IP 和 Port 相關的數據后,由驅動程序再交給 Socket ,應用程序就可以從這個 Socket 中讀取接收到的數據。網絡應用程序就是這樣通過 Socket 發送和接收的。
Socket數據發送過程:
Socket數據接收過程:
常用應用層協議
八、Java網絡編程
Java的網絡編程主要涉及到的內容是Socket編程,那么什么是Socket呢?簡單地說,Socket,套接字,就是兩台主機之間邏輯連接的端點。TPC/IP協議是傳輸層協議,主要解決數據如何在網絡中傳輸,而HTTP是應用層協議,主要解決如何包裝數據。Socket,本質上就是一組接口,是對TCP/IP協議的封裝和應用(程序員層面上)。
整體流程
Socket編程主要涉及到客戶端和服務器端兩個方面,首先是在服務器端創建一個服務器套接字(ServerSocket),並把它附加到一個端口上,服務器從這個端口監聽連接。端口號的范圍是0到65536,但是0到1024是為特權服務保留的端口號,我們可以選擇任意一個當前沒有被其他進程使用的端口。
客戶端請求與服務器進行連接的時候,根據服務器的域名或者IP地址,加上端口號,打開一個套接字。當服務器接受連接后,服務器和客戶端之間的通信就像輸入輸出流一樣進行操作。

九、Java網絡編程常用類
1.InteAddress類
Java中的InetAddress是一個代表IP地址的封裝。IP地址可以由字節數組和字符串來分別表示,InetAddress將IP地址以對象的形式進行封裝,可以更方便的操作和獲取其屬性。InetAddress沒有構造方法,可以通過兩個靜態方法獲得它的對象。
//根據主機名來獲取對應的InetAddress實例
InetAddress ip = InetAddress.getByName("www.baidu.com");
//判斷是否可達
System.out.println("baidu是否可達:" + ip.isReachable(2000));
//獲取該InetAddress實例的IP字符串
System.out.println(ip.getHostAddress());
//根據原始IP地址(字節數組形式)來獲取對應的InetAddress實例
InetAddress local = InetAddress.getByAddress(new byte[]{127,0,0,1});
System.out.println("本機是否可達:" + local.isReachable(5000));
//獲取該InetAddress實例對應的全限定域名
System.out.println(local.getCanonicalHostName());
2.URL和URLConnection類
網絡中的URL(Uniform Resource Locator)是統一資源定位符的簡稱。它表示Internet上某一資源的地址。通過URL我們可以訪問Internet上的各種網絡資源,比如最常見的WWW,FTP站點。 URL可以被認為是指向互聯網資源的“指針”,通過URL可以獲得互聯網資源相關信息,包括獲得URL的InputStream對象獲取資源的信息,以及一個到URL所引用遠程對象的連接URLConnection。 URLConnection對象可以向所代表的URL發送請求和讀取URL的資源。通常,創建一個和URL的連接,需要如下幾個步驟:
- 創建URL對象,並通過調用openConnection方法獲得URLConnection對象;
- 設置URLConnection參數和普通請求屬性;
- 向遠程資源發送請求;
- 遠程資源變為可用,程序可以訪問遠程資源的頭字段和通過輸入流來讀取遠程資源返回的信息。
這里需要重點討論一下第三步:如果只是發送GET方式請求,使用connect方法建立和遠程資源的連接即可;如果是需要發送POST方式的請求,則需要獲取URLConnection對象所對應的輸出流來發送請求。這里需要注意的是,由於GET方法的參數傳遞方式是將參數顯式追加在地址后面,那么在構造URL對象時的參數就應當是包含了參數的完整URL地址,而在獲得了URLConnection對象之后,就直接調用connect方法即可發送請求。而POST方法傳遞參數時僅僅需要頁面URL,而參數通過需要通過輸出流來傳遞。另外還需要設置頭字段。以下是兩種方式的代碼:
//1. 向指定URL發送GET方法的請求
String urlName = url + "?" + param;
URL realUrl = new URL(urlName);
//打開和URL之間的連接
URLConnection conn = realUrl.openConnection();
//設置通用的請求屬性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent","Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
//建立實際的連接
conn.connect();
//2. 向指定URL發送POST方法的請求
URL realUrl = new URL(url);
//打開和URL之間的連接
URLConnection conn = realUrl.openConnection();
//設置通用的請求屬性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
//發送POST請求必須設置如下兩行
conn.setDoOutput(true);
conn.setDoInput(true);
//獲取URLConnection對象對應的輸出流
out = new PrintWriter(conn.getOutputStream());
//發送請求參數
out.print(param);
3.URLDecoder和URLEncoder
這兩個類可以別用於將application/x-www-form-urlencoded MIME類型的字符串轉換為普通字符串,將普通字符串轉換為這類特殊型的字符串。使用URLDecoder類的靜態方法decode()用於解碼,URLEncoder類的靜態方法encode()用於編碼。具體使用方法如下:
//將application/x-www-form-urlencoded字符串轉換成普通字符串
String keyWord = URLDecoder.decode("%E6%9D%8E%E5%88%9A+j2ee", "UTF-8");
System.out.println(keyWord);
//將普通字符串轉換成 application/x-www-form-urlencoded字符串
String urlStr = URLEncoder.encode( "ROR敏捷開發最佳指南" , "GBK");
System.out.println(urlStr);
4.Socket和ServerSocket類
網絡上的兩個程序通過一個雙向的通訊連接實現數據的交換,這個雙向鏈路的一端稱為一個Socket。Socket通常用來實現客戶方和服務方的連接。Socket是TCP/IP協議的一個十分流行的編程界面,一個Socket由一個IP地址和一個端口號唯一確定。 但是,Socket所支持的協議種類也不光TCP/IP一種,因此兩者之間是沒有必然聯系的。在Java環境下,Socket編程主要是指基於TCP/IP協議的網絡編程。 Server端Listen(監聽)某個端口是否有連接請求,Client端向Server端發出Connect(連接)請求,Server端向Client端發回Accept(接受)消息。一個連接就建立起來了。Server端和Client端都可以通過Send,Write等方法與對方通信。 TCP Socket的通信過程如下圖:
5.DatagramSocket類
UDP協議是一種不可靠的網絡協議,它在通訊實例的兩端個建立一個Socket,但這兩個Socket之間並沒有虛擬鏈路,這兩個Socket只是發送和接受數據報的對象。 包java.net中提供了兩個類DatagramSocket和DatagramPacket用來支持數據報通信,DatagramSocket用於在程序之間建立傳送數據報的通信連接, DatagramPacket則用來表示一個數據報。 DatagramSocket的構造方法:
DatagramSocket();
DatagramSocket(int prot);
DatagramSocket(int port, InetAddress laddr);
其中,port指明socket所使用的端口號,如果未指明端口號,則把socket連接到本地主機上一個可用的端口。laddr指明一個可用的本地地址。給出端口號時要保證不發生端口沖突,否則會生成SocketException類例外。注意:上述的兩個構造方法都聲明拋棄非運行時例外SocketException,程序中必須進行處理,或者捕獲、或者聲明拋棄。 用數據報方式編寫client/server程序時,無論在客戶方還是服務方,首先都要建立一個DatagramSocket對象,用來接收或發送數據報,然后使用DatagramPacket類對象作為傳輸數據的載體。
實例一
下面是一個客戶端和服務器端進行數據交互的簡單例子,客戶端輸入正方形的邊長,服務器端接收到后計算面積並返回給客戶端,通過這個例子可以初步對Socket編程有個把握。
- 服務器端
public class SocketServer { public static void main(String[] args) throws IOException { // 端口號 int port = 7000; // 在端口上創建一個服務器套接字 ServerSocket serverSocket = new ServerSocket(port); // 監聽來自客戶端的連接 Socket socket = serverSocket.accept(); DataInputStream dis = new DataInputStream( new BufferedInputStream(socket.getInputStream())); DataOutputStream dos = new DataOutputStream( new BufferedOutputStream(socket.getOutputStream())); do { double length = dis.readDouble(); System.out.println("服務器端收到的邊長數據為:" + length); double result = length * length; dos.writeDouble(result); dos.flush(); } while (dis.readInt() != 0); socket.close(); serverSocket.close(); } }
- 客戶端
1 public class SocketClient { 2 3 public static void main(String[] args) throws UnknownHostException, IOException { 4 5 int port = 7000; 6 7 String host = "localhost"; 8 9 // 創建一個套接字並將其連接到指定端口號 10 Socket socket = new Socket(host, port); 11 12 DataInputStream dis = new DataInputStream( 13 new BufferedInputStream(socket.getInputStream())); 14 15 DataOutputStream dos = new DataOutputStream( 16 new BufferedOutputStream(socket.getOutputStream())); 17 18 Scanner sc = new Scanner(System.in); 19 20 boolean flag = false; 21 22 while (!flag) { 23 24 System.out.println("請輸入正方形的邊長:"); 25 double length = sc.nextDouble(); 26 27 dos.writeDouble(length); 28 dos.flush(); 29 30 double area = dis.readDouble(); 31 32 System.out.println("服務器返回的計算面積為:" + area); 33 34 while (true) { 35 36 System.out.println("繼續計算?(Y/N)"); 37 38 String str = sc.next(); 39 40 if (str.equalsIgnoreCase("N")) { 41 dos.writeInt(0); 42 dos.flush(); 43 flag = true; 44 break; 45 } else if (str.equalsIgnoreCase("Y")) { 46 dos.writeInt(1); 47 dos.flush(); 48 break; 49 } 50 } 51 } 52 53 socket.close(); 54 } 55 }
實例二
可以看到上面的服務器端程序和客戶端程序是一對一的關系,為了能讓一個服務器端程序能同時為多個客戶提供服務,可以使用多線程機制,每個客戶端的請求都由一個獨立的線程進行處理。下面是改寫后的服務器端程序。
1 public class SocketServerM { 2 3 public static void main(String[] args) throws IOException { 4 5 int port = 7000; 6 int clientNo = 1; 7 8 ServerSocket serverSocket = new ServerSocket(port); 9 10 // 創建線程池 11 ExecutorService exec = Executors.newCachedThreadPool(); 12 13 try { 14 15 while (true) { 16 Socket socket = serverSocket.accept(); 17 exec.execute(new SingleServer(socket, clientNo)); 18 clientNo++; 19 } 20 21 } finally { 22 serverSocket.close(); 23 } 24 25 } 26 } 27 28 class SingleServer implements Runnable { 29 30 private Socket socket; 31 private int clientNo; 32 33 public SingleServer(Socket socket, int clientNo) { 34 this.socket = socket; 35 this.clientNo = clientNo; 36 } 37 38 @Override 39 public void run() { 40 41 try { 42 43 DataInputStream dis = new DataInputStream( 44 new BufferedInputStream(socket.getInputStream())); 45 46 DataOutputStream dos = new DataOutputStream( 47 new BufferedOutputStream(socket.getOutputStream())); 48 49 do { 50 51 double length = dis.readDouble(); 52 System.out.println("從客戶端" + clientNo + "接收到的邊長數據為:" + length); 53 double result = length * length; 54 dos.writeDouble(result); 55 dos.flush(); 56 57 } while (dis.readInt() != 0); 58 59 } catch (IOException e) { 60 e.printStackTrace(); 61 } finally { 62 System.out.println("與客戶端" + clientNo + "通信結束"); 63 try { 64 socket.close(); 65 } catch (IOException e) { 66 e.printStackTrace(); 67 } 68 } 69 } 70 }
上面改進后的服務器端代碼可以支持不斷地並發響應網絡中的客戶請求。關鍵的地方在於多線程機制的運用,同時利用線程池可以改善服務器程序的性能。