1. 程式人生 > >Java網絡編程和NIO詳解開篇:Java網絡編程基礎

Java網絡編程和NIO詳解開篇:Java網絡編程基礎

tcp協議 ack focus 特殊字符 設計模式 ssm 域名 代碼片段 mime

Java網絡編程和NIO詳解開篇:Java網絡編程基礎

計算機網絡編程基礎

轉自:https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA

我們是幸運的,因為我們擁有網絡。網絡是一個神奇的東西,它改變了你和我的生活方式,改變了整個世界。 然而,網絡的無標度和小世界特性使得它又是復雜的,無所不在,無所不能,以致於我們無法區分甚至無法描述。

對於一個碼農而言,了解網絡的基礎知識可能還是從了解定義開始,認識OSI的七層協議模型,深入Socket內部,進而熟練地進行網絡編程。

關於網絡

關於網絡,在詞典中的定義是這樣的:

在電的系統中,由若幹元件組成的用來使電信號按一定要求傳輸的電路或這種電路的部分,叫網絡。

作為一名從事過TMN開發的通信專業畢業生,固執地認為網絡是從通信系統中誕生的。通信是人與人之間通過某種媒介進行的信息交流與傳遞。傳統的通信網絡(即電話網絡)是由傳輸、交換和終端三大部分組成,通信網絡是指將各個孤立的設備進行物理連接,實現信息交換的鏈路,從而達到資源共享和通信的目的。通信網絡可以從覆蓋範圍,拓撲結構,交換方式等諸多視角進行分類…… 滿滿的回憶,還是留在書架上吧。

網絡的概念外延被不斷的放大著,抽象的思維能力是人們創新乃至創造的根源。網絡用來表示諸多對象及其相互聯系,數學上的圖,物理學上的模型,交通網絡,人際網絡,城市網絡等等,總之,網絡被總結成從同類問題中抽象出來用數學中的圖論科學來表達並研究的一種模型。

很多夥伴認為,了解這些之後呢,然並卵。我們關心的只是計算機網絡,算機網絡是用通信線路和設備將分布在不同地點的多臺計算機系統互相連接起來,按照網絡協議,分享軟硬件功能,最終實現資源共享的系統。特別的,我們談到的網絡只是互聯網——Internet,或者移動互聯網,需要的是寫互連網應用程序。但是,一位工作了五六年的編程高手曾對我說,現在終於了解到基礎知識有多重要,技術在不斷演進,而相對不變的就是那些原理和編程模型了。

老碼農深以為然,編程實踐就是從具體到抽象,再到具體,循環往復,螺旋式上升的過程。了解前世今生,只是為了可能觸摸到“勢”。基礎越紮實,建築就會越有想象的空間。 對於網絡編程的基礎,大概要從OSI的七層協議模型開始了。

七層模型

七層模型(OSI,Open System Interconnection參考模型),是參考是國際標準化組織制定的一個用於計算機或通信系統間互聯的標準體系。它是一個七層抽象的模型,不僅包括一系列抽象的術語和概念,也包括具體的協議。 經典的描述如下:

簡述每一層的含義:

  1. 物理層(Physical Layer):建立、維護、斷開物理連接。

  2. 數據鏈路層 (Link):邏輯連接、進行硬件地址尋址、差錯校驗等。

  3. 網絡層 (Network):進行邏輯尋址,實現不同網絡之間的路徑選擇。

  4. 傳輸層 (Transport):定義傳輸數據的協議端口號,及流控和差錯校驗。

  5. 會話層(Session Layer):建立、管理、終止會話。

  6. 表示層(Presentation Layer):數據的表示、安全、壓縮。

  7. 應用層 (Application):網絡服務與最終用戶的一個接口

每一層利用下一層提供的服務與對等層通信,每一層使用自己的協議。了解了這些,然並卵。但是,這一模型確實是絕大多數網絡編程的基礎,作為抽象類存在的,而TCP/IP協議棧只是這一模型的一個具體實現。

TCP/IP 協議模型

TCP/IP是Internet的基礎,是一組協議的代名詞,包括許多協議,組成了TCP/IP協議棧。TCP/IP 有四層模型和五層模型之說,區別在於數據鏈路層是否作為獨立的一層存在。個人傾向於5層模型,這樣2層和3層的交換設備更容易弄明白。當談到網絡的2層或3層交換機的時候,就知道指的是那些協議。

數據是如何傳遞呢?這就要了解網絡層和傳輸層的協議,我們熟知的IP包結構是這樣的:

技術分享圖片

IP協議和IP地址是兩個不同的概念,這裏沒有涉及IPV6的。不關註網絡安全的話,對這些結構不必耳熟能詳的。傳輸層使用這樣的數據包進行傳輸,傳輸層又分為面向連接的可靠傳輸TCP和數據報UDP。TCP的包結構:

技術分享圖片

TCP 連接建立的三次握手肯定是必知必會,在系統調優的時候,內核中關於網絡的相關參數與這個圖息息相關。UDP是一種無連接的傳輸層協議,提供的是簡單不可靠的信息傳輸。協議結構相對簡單,包括源和目標的端口號,長度以及校驗和。基於TCP和UDP的數據封裝及解析示例如下:

技術分享圖片

還是然並卵麽?一個數據包的大小了解了,會發現什麽呢?PayLoad到底是多少?在設計協議通信的時候,這些都為我們提供了粒度定義的依據。進一步,通過一個例子看看吧。

模型解讀示例

FTP是一個比較好的例子。為了方便起見,假設兩條計算機分別是A 和 B,將使用FTP 將A上的一個文件X傳輸到B上。

首先,計算機A和B之間要有物理層的連接,可以是有線比如同軸電纜或者雙絞線通過RJ-45的電路接口連接,也可以是無線連接例如WIFI。先簡化一下,考慮局域網,暫不討論路由器和交換機以及WIFI熱點。這些物理層的連接建立了比特流的原始傳輸通路。

接下來,數據鏈路層登場,建立兩臺計算機的數據鏈路。如果A和B所在的網絡上同時連接著計算機C,D,E等等,A和B之間如何建立的數據鏈路呢?這一過程就是物理尋址,A要在眾多的物理連接中找到B,依賴的是計算機的物理地址即MAC地址,對就是網卡上的MAC地址。以太網采用CSMA/CD方式來傳輸數據,數據在以太網的局域網中都是以廣播方式傳輸的,整個局域網中的所有節點都會收到該幀,只有目標MAC地址與自己的MAC地址相同的幀才會被接收。A通過差錯控制和接入控制找到了B的網卡,建立可靠的數據通路。

那IP地址呢? 數據鏈路建立起來了,還需要IP地址麽?我們FTP 命令中制定的是IP地址而不是MAC地址呀?IP地址是邏輯地址,包括網絡地址和主機地址。如果A和B在不同的局域網中,中間有著多個路由器,A需要對B進行邏輯尋址才可以的。物理地址用於底層的硬件的通信,邏輯地址用於上層的協議間的通信。在以太網中:邏輯地址就是IP地址,物理地址就是MAC 地址。在使用中,兩種地址是用一定的算法將他們兩個聯系起來的。所以,IP是用來在網絡上選擇路由的,在FTP的命令中,IP中的原地址就是A的IP地址,目標地址就是B的IP地址。這應該就是網絡層,負責將分組數據從源端傳輸到目的端。

A向B傳輸一個文件時,如果文件中有部分數據丟失,就可能會造成在B上無法正常閱讀或使用。所以需要一個可靠的連接,能夠確保傳輸過程的完整性,這就是傳輸層的TCP協議,FTP就是建立在TCP之上的。TCP的三次握手確定了雙方數據包的序號、最大接受數據的大小(window)以及MSS(Maximum Segment Size)。TCP利用IP完成尋址,TCP中的提供了端口號,FTP中目的端口號一般是21。傳輸層的端口號對應主機進程,指本地主機與遠程主機正在進行的會話。

會話層用來建立、維護、管理應用程序之間的會話,主要功能是對話控制和同步,編程中所涉及的session是會話層的具體體現。表示層完成數據的解編碼,加解密,壓縮解壓縮等,例如FTP中bin命令,代表了二進制傳輸,即所傳輸層數據的格式。 HTTP協議裏body中的Json,XML等都可以認為是表示層。應用層就是具體應用的本身了,FTP中的PUT,GET等命令都是應用的具體功能特性。

簡單地,物理層到電纜連接,數據鏈路層到網卡,網絡層路由到主機,傳輸層到端口,會話層維持會話,表示層表達數據格式,應用層就是具體FTP中的各種命令功能了。

Socket

了解了7層模型就可以編程了麽,拿起編程語言就可以耍了麽?剛開始上手嘗試還是可以的,如果要進一步,老碼農覺得還是看看底層實現的好,因為一切歸根到底都會歸結為系統調用。到了操作系統層面如何看網絡呢?Socket登場了。

在Linux世界,“一切皆文件”,操作系統把網絡讀寫作為IO操作,就像讀寫文件那樣,對外提供出來的編程接口就是Socket。所以,socket(套接字)是通信的基石,是支持TCP/IP協議網絡通信的基本操作單元。socket實質上提供了進程通信的端點。進程通信之前,雙方首先必須各自創建一個端點,否則是沒有辦法建立聯系並相互通信的。一個完整的socket有一個本地唯一的socket號,這是由操作系統分配的。

從設計模式的角度看, Socket其實是一個外觀模式,它把復雜的TCP/IP協議棧隱藏在Socket接口後面,對用戶來說,一組簡單的Socket接口就是全部。當應用程序創建一個socket時,操作系統就返回一個整數作為描述符(descriptor)來標識這個套接字。然後,應用程序以該描述符為傳遞參數,通過調用函數來完成某種操作(例如通過網絡傳送數據或接收輸入的數據)。

在許多操作系統中,Socket描述符和其他I/O描述符是集成在一起的,操作系統把socket描述符實現為一個指針數組,這些指針指向內部數據結構。進一步看,操作系統為每個運行的進程維護一張單獨的文件描述符表。當進程打開一個文件時,系統把一個指向此文件內部數據結構的指針寫入文件描述符表,並把該表的索引值返回給調用者 。

既然Socket和操作系統的IO操作相關,那麽各操作系統IO實現上的差異會導致Socket編程上的些許不同。看看我Mac上的Socket.so 會發現和CentOS上的還是些不同的。

進程進行Socket操作時,也有著多種處理方式,如阻塞式IO,非阻塞式IO,多路復用(select/poll/epoll),AIO等等。

多路復用往往在提升性能方面有著重要的作用。select系統調用的功能是對多個文件描述符進行監視,當有文件描述符的文件讀寫操作完成以及發生異常或者超時,該調用會返回這些文件描述符。select 需要遍歷所有的文件描述符,就遍歷操作而言,復雜度是 O(N)。

epoll相關系統調用是在Linux 2.5 後的某個版本開始引入的。該系統調用針對傳統的select/poll不足,設計上作了很大的改動。select/poll 的缺點在於:

  1. 每次調用時要重復地從用戶模式讀入參數,並重復地掃描文件描述符。

  2. 每次在調用開始時,要把當前進程放入各個文件描述符的等待隊列。在調用結束後,又把進程從各個等待隊列中刪除。

epoll 是把 select/poll 單個的操作拆分為 1 個 epollcreate,多個 epollctrl和一個 wait。此外,操作系統內核針對 epoll 操作添加了一個文件系統,每一個或者多個要監視的文件描述符都有一個對應的inode 節點,主要信息保存在 eventpoll 結構中。而被監視的文件的重要信息則保存在 epitem 結構中,是一對多的關系。由於在執行 epollcreate 和 epollctrl 時,已經把用戶模式的信息保存到內核了, 所以之後即便反復地調用 epoll_wait,也不會重復地拷貝參數,不會重復掃描文件描述符,也不反復地把當前進程放入/拿出等待隊列。

所以,當前主流的Server側Socket實現大都采用了epoll的方式,例如Nginx, 在配置文件可以顯式地看到 use epoll

網絡編程

了解了7層協議模型和操作系統層面的Socket實現,可以方便我們理解網絡編程。

在系統架構的時候,有重要的一環就是拓撲架構,這裏涉及了網絡等基礎設施,那麽7層協議下四層就會有助於我們對業務系統網絡結構的觀察和判斷。在系統設計的時候,往往采用面向接口的設計,而接口也往往是基於HTTP協議的Restful API。 那接口的粒度就可以將data segment作為一個約束了,同時可以關註到移動互聯網中的弱網環境。

不同的編程語言,有著不同的框架和庫,真正的編寫網絡程序代碼並不復雜,例如,用Erlang 中 gen_tcp 用於編寫一個簡單的Echo服務器:

Start_echo_server()->
{ok,Listen}= gen_tcp:listen(1234,[binary,{packet,4},{reuseaddr,true},{active,true}]),
{ok,socket}=get_tcp:accept(Listen),
gen_tcp:close(Listen),
loop(Socket).
?
loop(Socket) ->
receive
{tcp,Socket,Bin} ->
io:format(“serverreceived binary = ~p~n”,[Bin])
Str= binary_to_term(Bin),
io:format(“server (unpacked) ~p~n”,[Str]),
Reply= lib_misc:string2value(Str),
io:format(“serverreplying = ~p~n”,[Reply]),
gen_tcp:send(Socket,term_to_binary(Reply)),
loop(Socket);
{tcp_closed,Socket} ->
Io:format(“ServerSocket closed ~n”)
end.

然而,寫出漂亮的服務器程序仍然是一件非常吃功夫的事情,例如,個人非常喜歡的python Tornado 代碼, 在ioloop.py 中有對多路復用的選擇:

@classmethod
def configurable_default(cls):
if hasattr(select, "epoll"):
from tornado.platform.epoll import EPollIOLoop
return EPollIOLoop
if hasattr(select, "kqueue"):
# Python 2.6+ on BSD or Mac
from tornado.platform.kqueue import KQueueIOLoop
return KQueueIOLoop
from tornado.platform.select import SelectIOLoop
return SelectIOLoop

在HTTPServer.py 中同樣繼承了TCPServer,進而實現了HTTP協議,代碼片段如下:

class HTTPServer(TCPServer, Configurable,
httputil.HTTPServerConnectionDelegate):
...
def initialize(self, request_callback, no_keep_alive=False, io_loop=None,
xheaders=False, ssl_options=None, protocol=None,
decompress_request=False,
chunk_size=None, max_header_size=None,
idle_connection_timeout=None, body_timeout=None,
max_body_size=None, max_buffer_size=None):
self.request_callback = request_callback
self.no_keep_alive = no_keep_alive
self.xheaders = xheaders
self.protocol = protocol
self.conn_params = HTTP1ConnectionParameters(
decompress=decompress_request,
chunk_size=chunk_size,
max_header_size=max_header_size,
header_timeout=idle_connection_timeout or 3600,
max_body_size=max_body_size,
body_timeout=body_timeout)
TCPServer.__init__(self, io_loop=io_loop, ssl_options=ssl_options,
max_buffer_size=max_buffer_size,
read_chunk_size=chunk_size)
self._connections = set()
...

Java網絡編程基礎

Java網絡編程基礎 轉自並發編程網https://ifeve.com/

Java 網絡教程: 基礎

Java提供了非常易用的網絡API,調用這些API我們可以很方便的通過建立TCP/IP或UDP套接字,在網絡之間進行相互通信,其中TCP要比UDP更加常用,但在本教程中我們對這兩種方式都有說明。

盡管Java網絡API允許我們通過套接字(Socket)打開或關閉網絡連接,但所有的網絡通信均是基於Java IO類 InputStream和OutputStream實現的。

此外,我們還可以使用Java NIO API中相關的網絡類,用法與Java網絡API基本類似,Java NIO API可以以非阻塞模式工作,在某些特定的場景中使用非阻塞模式可以獲得較大的性能提升。

Java TCP網絡基礎

通常情況下,客戶端打開一個連接到服務器端的TCP/IP連接,然後客戶端開始與服務器之間通信,當通信結束後客戶端關閉連接,過程如下圖所示:

ClientServerOpen ConnectionSend RequestReceive ResponseClose Connection

客戶端通過一個已打開的連接可以發送不止一個請求。事實上在服務器處於接收狀態下,客戶端可以發送盡可能多的數據,服務器也可以主動關閉連接。

Java中Socket類和ServerSocket類

當客戶端想要打開一個連接到服務器的TCP/IP連接時,就要使用到Java Socket類。socket類只需要被告知連接的IP地址和TCP端口,其余的都有Java實現。

假如我們想要打開一個監聽服務,來監聽客戶端連接某些指定TCP端口的連接,那就需要使用Java ServerSocket類。當客戶端通過Socket連接服務器端的ServerSocket監聽時,服務器端會指定這個連接的一個Socket,此時客戶端與服務器端間的通信就變成Socket與Socket之間的通信。

關於Socket類和ServerSocket類會在後面的文章中有詳細的介紹。

Java UDP網絡基礎

UDP的工作方式與TCP相比略有不同。使用UDP通信時,在客戶端與服務器之間並沒有建立連接的概念,客戶端發送到服務器的數據,服務器可能(也可能並沒有)收到這些數據,而且客戶端也並不知道這些數據是否被服務器成功接收。當服務器向客戶端發送數據時也是如此。

正因為是不可靠的數據傳輸,UDP相比與TCP來說少了很多的協議開銷。

在某些場景中,使用無連接的UDP要優於TCP,這些在文章Java UDP DatagramSocket類介紹中會有更多介紹。

當我們想要在Java中使用TCP/IP通過網絡連接到服務器時,就需要創建java.net.Socket對象並連接到服務器。假如希望使用Java NIO,也可以創建Java NIO中的SocketChannel對象。

Socket

創建Socket

下面的示例代碼是連接到IP地址為78.64.84.171服務器上的80端口,這臺服務器就是我們的Web服務器(www.jenkov.com),而80端口就是Web服務端口。

Socket socket = new Socket(“78.46.84.171”, 80); 我們也可以像如下示例中使用域名代替IP地址:

Socket socket = new Socket(“jenkov.com”, 80);

Socket發送數據

要通過Socket發送數據,我們需要獲取Socket的輸出流(OutputStream),示例代碼如下:

Socket socket = new Socket(“jenkov.com”, 80); OutputStream out = socket.getOutputStream();

out.write(“some data”.getBytes()); out.flush(); out.close();

socket.close(); 代碼非常簡單,但是想要通過網絡將數據發送到服務器端,一定不要忘記調用flush()方法。操作系統底層的TCP/IP實現會先將數據放入一個更大的數據緩存塊中,而緩存塊的大小是與TCP/IP的數據包大小相適應的。(譯者註:調用flush()方法只是將數據寫入操作系統緩存中,並不保證數據會立即發送)

Socket讀取數據

從Socket中讀取數據,我們就需要獲取Socket的輸入流(InputStream),代碼如下:

Socket socket = new Socket(“jenkov.com”, 80); InputStream in = socket.getInputStream();

int data = in.read(); //… read more data…

in.close(); socket.close(); 代碼也並不復雜,但需要註意的是,從Socket的輸入流中讀取數據並不能讀取文件那樣,一直調用read()方法直到返回-1為止,因為對Socket而言,只有當服務端關閉連接時,Socket的輸入流才會返回-1,而是事實上服務器並不會不停地關閉連接。假設我們想要通過一個連接發送多個請求,那麽在這種情況下關閉連接就顯得非常愚蠢。

因此,從Socket的輸入流中讀取數據時我們必須要知道需要讀取的字節數,這可以通過讓服務器在數據中告知發送了多少字節來實現,也可以采用在數據末尾設置特殊字符標記的方式連實現。

關閉Socket

當使用完Socket後我們必須將Socket關閉,斷開與服務器之間的連接。關閉Socket只需要調用Socket.close()方法即可,代碼如下:

Socket socket = new Socket(“jenkov.com”, 80);

socket.close(); Java 網絡教程: ServerSocket 用java.net.ServerSocket實現java服務通過TCP/IP監聽客戶端連接,你也可以用Java NIO 來代替java網絡標準API,這時候需要用到 ServerSocketChannel。

創建一個 ServerSocket連接 以下是一個創建ServerSocket類來監聽9000端口的一個簡單的代碼

ServerSocket serverSocket = new ServerSocket(9000);

監聽請求的連接 要獲取請求的連接需要用ServerSocket.accept()方法。該方法返回一個Socket類,該類具有普通java Socket類的所有特性。代碼如下:

ServerSocket serverSocket = new ServerSocket(9000); boolean isStopped = false;while(!isStopped){ Socket clientSocket = serverSocket.accept(); //do something with clientSocket}

對每個調用了accept()方法的類都只獲得一個請求的連接。

另外,請求的連接也只能在線程運行的server中調用了accept()方法之後才能夠接受請求。線程運行在server中其它所有的方法上的時候都不能接受客戶端的連接請求。所以”接受”請求的線程通常都會把Socket的請求連接放入一個工作線程池中,然後再和客戶端連接。更多關於多線程服務端設計的文檔請參考 java多線程服務

關閉客戶端Socket 客戶端請求執行完畢,並且不會再有該客戶端的其它請求發送過來的時候,就需要關閉Socket連接,這和關閉一個普通的客戶端Socket連接一樣。如下代碼來執行關閉:

socket.close();

關閉服務端Sockets 要關閉服務的時候需要關掉 ServerSocket連接。通過執行如下代碼:

serverSocket.close();

UDP DatagramSocket

DatagramSocket類是java通過UDP通信的途徑。UDP仍位於IP層的上面。 你可以用DatagramSocket類發送和接收UDP數據包。

UDP 和TCP

UDP工作方式和TCP有點不同。當你通過TCP發送數據時,你先要創建連接。一旦TCP連接建立了,TCP會保證你的數據傳遞到對端,否則它將告訴你已發生的錯誤。

僅僅用UDP來發送數據包(datagrams)到網絡間的某個IP地址。你不能保證數據會不會到達。你也不能保證UDP數據包到達接收方的指令。這意味著UDP比TCP有更少的協議開銷(無完整檢查流)。

當數據傳輸過程中不在乎數據包是否丟失時,UDP就比較適合這樣的數據傳輸。比如,網上的電視信號的傳輸。你希望信號到達客戶端時盡可能地接近直播。因此,如果丟失一兩個畫面,你一點都不在乎。你不希望直播延遲,值想確保所有的畫面顯示在客戶端。你寧可跳過丟失的畫面,希望一直看到最新的畫面。

這種情況也會發生在網上攝像機直播節目中。誰會關心過去發生的什麽,你只想顯示當前的畫面。你不希望比實際情況慢30s結束,只因為你想看到攝像機顯示給觀眾的所有畫面。這跟攝像機錄像有點不同。從攝像機錄制畫面到磁盤,你不希望丟失一個畫面。你可能還希望有點延遲,如果有重大的情況發生,就不需要倒回去檢查畫面。

通過DatagramSocket發送數據 通過Java的DatagramSocket類發送數據,首先需要創建DatagramPacket。如下:

1 buffer = new byte[65508]; 2 3 InetAddress address = new DatagramPacket(buffer, buffer.length, address,9000); 字節緩沖塊(字節數組)就是UDP數據包中用來發送的數據。緩沖塊上限長度為65508字節,是單一UDP數據包發送的最大的數據量。

數據包構造函數的長度就是緩存塊中用於發送的數據的長度。所有多於最大容量的數據都會被忽略。

包含節點(例如服務器)地址的InetAddress實例攜帶節點(如服務器)的地址發送的UDP數據包。InetAddress類表示一個ip地址(網絡地址)。getByName()方法返回帶有一個InetAddress實例,該實例帶有匹配主機名的ip地址。

端口參數是UDP端口服務器用來接收正在監聽的數據。UDP端口和TCP端口是不一樣的。一臺電腦同時有不同的進程監聽UDP和TCP 80端口。

為了發送數據包,你需要創建DatagramSocket來發送數據。如下:

1    DatagramSocketdatagramSocket = new DatagramSocket();

調用send()方法發送數據,像這樣:

1    datagramSocket.send(packet);
完整示例:
?
1 DatagramSocket datagramSocket = new DatagramSocket();
2
3 byte[] buffer = "0123456789".getBytes();
4
5 InetAddress receiverAddress = InetAddress.getLocalHost();
6
7 DataframPacket packet = new DatagramPacket( buffer, buffer.length, receiverAddress,80);
8 datagramSocket.send(packet);

從DatagramSocket獲取數據 從DatagramSocket獲取數據時,首先創建DatagramPacket ,然後通過DatagramSocket類的receive()方法接收數據。例如:

1    DatagramSocket datagramSocket = new DatagramSocket(80);
2
3 byte[] buffer = new byte[10];
4
5 DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
6
7 datagramSocket.receive(packet);

註意DatagramSocket是如何通過傳遞參數80到它的構造器初始化的。這個參數是UDP端口的DatagramSocket用來接收UDP數據包的。像之前提到的,TCP和UDP端口是不一樣的,也不重疊。你可以有倆個不同的進程同時在端口80監聽TCP和UDP,沒有任何沖突。

第二,字節緩存塊和DatagramPacket創建了。註意DatagramPacket是沒有關於節點如何發送數據的信息的,當創建一個方數據的DatagramPacket時,它會直到這個信息。這就是為什麽我們會用DatagramPacket接收數據而不是發送數據。因此沒有目標地址是必須的。

最後,調用DatagramSocket的receive()方法。直到數據包接收到為止,這個方法都是阻塞的。

接收的數據位於DatagramPacket的字節緩沖塊。緩沖塊可以通過調用getData()獲得:

1 byte[] buffer = packet.getData(); 緩沖塊接收了多少的數據需要你去找出來。你用的協議應該定義每個UDP包發多少數據,活著定義一個你能找到的數據結束標記。 一個真正的服務端程序可能會在一個loop中調用receive()方法,傳送所有接收到的DatagramPacket到工作的線程池中,就像TCP服務器處理請求連接一樣(查看Java Multithreaded Servers獲取更多詳情)

URL + URLConnection

HTTP GET和POST 從URLs到本地文件 在java.net包中包含兩個有趣的類:URL類和URLConnection類。這兩個類可以用來創建客戶端到web服務器(HTTP服務器)的連接。下面是一個簡單的代碼例子:

1    URL url = new URL("http://jenkov.com");
2 URLConnection urlConnection = url.openConnection();
3 InputStream input = urlConnection.getInputStream();
4 int data = input.read();
5 while(data != -1){
6 System.out.print((char) data);
7 data = input.read();
8 }
9 input.close();

HTTP GET和POST 默認情況下URLConnection發送一個HTTP GET請求到web服務器。如果你想發送一個HTTP POST請求,要調用URLConnection.setDoOutput(true)方法,如下:

1    URL url = new URL("http://jenkov.com");
2 URLConnection urlConnection = url.openConnection();
3 urlConnection.setDoOutput(true);

一旦你調用了setDoOutput(true),你就可以打開URLConnection的OutputStream,如下:

1    OutputStream output = urlConnection.getOutputStream();

你可以使用這個OutputStream向相應的HTTP請求中寫任何數據,但你要記得將其轉換成URL編碼(關於URL編碼的解釋,自行Google)(譯者註:具體名字是:application/x-www-form-urlencoded MIME 格式編碼)。 當你寫完數據的時候要記得關閉OutputStream。

從URLs到本地文件 URL也被叫做統一資源定位符。如果你的代碼不關心文件是來自網絡還是來自本地文件系統,URL類是另外一種打開文件的方式。 下面是一個如何使用URL類打開一個本地文件系統文件的例子:

1    URL url = new URL("file:/c:/data/test.txt");
2    URLConnection urlConnection = url.openConnection();
3    InputStream input = urlConnection.getInputStream();
4    int data = input.read();
5    while(data != -1){
6    System.out.print((char) data);
7    data = input.read();
8    }
9    input.close();

註意:這和通過HTTP訪問一個web服務器上的文件的唯一不同處就是URL:”file:/c:/data/test.txt”。

JarURLConnection

Java的JarURLConnection類用來連接Java Jar文件。一旦連接上,你可以獲取Jar文件的信息。一個簡單的例子如下:

01    String urlString = "http://butterfly.jenkov.com/"
02                     + "container/download/"
03                     + "jenkov-butterfly-container-2.9.9-beta.jar";
04     
05    URL jarUrl = new URL(urlString);
06    JarURLConnection connection = new JarURLConnection(jarUrl);
07     
08    Manifest manifest = connection.getManifest();
09     
10    JarFile jarFile = connection.getJarFile();
11    //do something with Jar file...

InetAddress

創建一個 InetAddress 實例 InetAddress 的內部方法 InetAddress 是 Java 對 IP 地址的封裝。這個類的實例經常和 UDP DatagramSockets 和 Socket,ServerSocket 類一起使用。

創建一個 InetAddress 實例 InetAddress 沒有公開的構造方法,因此你必須通過一系列靜態方法中的某一個來獲取它的實例。 <!–more–>

下面是為一個域名實例化 InetAddres 類的例子:

InetAddress address = InetAddress.getByName("jenkov.com");

當然也會有為匹配某個 IP 地址來實例化一個 InetAddress:

InetAddress address = InetAddress.getByName("78.46.84.171");

另外,它還有通過獲取本地 IP 地址的來獲取 InetAddress 的方法(正在運行程序的那臺機器)

InetAddress address = InetAddress.getLocalHost();

InetAddress 內部方法 InetAddress 類還擁有大量你可以調用的其它方法。例如:你可以通過調用getAddress()方法來獲取 IP 地址的 byte 數組。如果要了解更多的方法,最簡單的方式就是讀 JavaDoc 文檔中關於 InetAddress 類的部分。

Java網絡編程和NIO詳解開篇:Java網絡編程基礎