1. 程式人生 > >Java網路程式設計和NIO詳解開篇:Java網路程式設計基礎

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網路程式設計基礎

轉自併發程式設計網https://ifeve.com/

Java 網路教程: 基礎

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

在網站上還有其他三個與Java網路相關的教程,如下:

儘管Java網路API允許我們通過套接字(Socket)開啟或關閉網路連線,但所有的網路通訊均是基於Java IO類 InputStreamOutputStream實現的。

此外,我們還可以使用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物件。

Java網路教程之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();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();

Java網路程式設計: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獲取資料

從DataframSocket獲取資料時,首先建立DataframPacket ,然後通過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獲取更多詳情)

Java網路教程:URL + URLConnection

  • HTTP GET和POST
  • 從URLs到本地檔案

在java.net包中包含兩個有趣的類:URL類和URLConnection類。這兩個類可以用來建立客戶端到web伺服器(HTTP伺服器)的連線。下面是一個簡單的程式碼例子:

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)方法,如下:

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”。

Java網路教程:JarURLConnection

Java的JarURLConnection類用來連線Java Jar檔案。一旦連線上,你可以獲取Jar檔案的資訊。一個簡單的例子如下:

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...

Java 網路教程: 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網路教程:Protocol Design

如果設計一個客戶端到伺服器的系統,那麼同時也需要設計客戶端和伺服器之間的通訊協議。當然,有時候協議已經為你決定好了,比如HTTP、XML_RPC(http response 的 body 使用xml)、或者SOAP(也是http response 的 body 使用xml)。設計客戶端到服務端協議的時候,一旦協議決定開啟一會兒,來看一些你必須考慮的地方:

1. 客戶端到服務端的往返通訊

2.區分請求結束和響應結束。

3.防火牆穿透

客戶端-服務端往返

當客戶端和服務端通訊,執行操作時,他們在交換資訊。比如,客戶端執行一個服務請求,服務端嘗試完成這個請求,發回響應告訴客戶端結果。這種客戶端和服務端的資訊交換就叫做往返。示意圖如下:

當一個計算機(客戶端或者服務端)在網路中傳送資料到另一個計算機時,從資料傳送到另一端接收資料完會花費一定時間。這就是資料在網路間的傳送的時間花費。這個時間叫做延遲。

協議中含有越多的往返,協議變得越慢,延遲特別高。HTTP協議只包含一個單獨的響應來執行服務。換句話說就是一個單獨的往返。另一方面,在一封郵件傳送前,SMTP協議包含了幾個客戶端和服務端的往返。

在協議中有多個往返的原因是:有大量的資料從客戶端傳送到服務端。這種情況下你有2個選擇:

1.在分開往返中傳送頭資訊;

2.將訊息分成更小的資料塊。

如果服務端能完成頭資訊的一些初始驗證 ,那麼分開發送頭資訊是很明智的。如果頭資訊是空白的,傳送大量資料本身就是浪費資源。

在傳輸大量資料時,如果網路連線失敗了,得從頭開始重新發送資料。資料分割傳送時,只需要在網路連線失敗處重新發送資料塊。已經發送成功的資料塊不需要重新發送。

區分請求結束和響應結束

如果協議容許在同一個連線中傳送多個請求,需要一個讓服務端知道當前請求何時結束、下一個請求何時開始。客戶端也需要知道一個響應何時結束了,下一個響應何時開始。

對於請求有2個方法區分結束:

1.在請求的開始處傳送請求的字長

2.在請求資料的最後傳送一個結束標記。

HTTP用第一個機制。在請求頭中 傳送了“Content-Length”。請求頭會告訴服務端在標頭檔案後有多少位元組是屬於請求的。

這個模型的優勢在於沒有請求結束標誌的開銷。為了避免資料看上去像請求結束標誌,也不需要對資料體進行編碼。

第一個方法的劣勢:在資料傳輸前,傳送者必須知道多少位元組數將被傳輸。如果資料時動態生成的,在傳送前,首先你得快取所有的資料,這樣才能計算出資料的位元組數。

運用請求結束標誌時,不需要知道傳送了多少位元組數。只需要知道請求結束標誌在資料的末尾。當然,必須確認已傳送的資料中不包含會導致請求結束標誌錯誤的資料。可以這樣做:

可以說請求結束標誌是位元組值255。當然資料可能包含值255。因此,對資料中包含值255的每一個位元組新增一個額外的位元組,還有值255。結束請求標誌被從位元組值255到255之後的值為0。如下編碼:

255 in data –>255, 255

end-of-request –> 255, 0

這種255,0的序列永遠不會出現在資料中,因為你把所有的值255變成了255,255。同時,255,255,0也不會被錯認為255,0。255,255被理解成在一起的,0是單獨的。

防火牆穿透

比起HTTP協議,大多數防火牆會攔截所有的其他通訊。因此把協議放在HTTP的上層是個好方法,像XML-RPC,SOAP和REST也可以這樣做。

協議置於HTTP的上層,在客戶端和服務端的HTTP請求和響應中可以來回傳送資料。記住,HTTP請求和響應不止包含text或者HTML。也可以在裡面傳送二進位制資料。

將請求放置在HTTP協議上,唯一有點奇怪的是:HTTP請求必須包含一個“主機”頭欄位。如果你在HTTP協議上設計P2P協議,同樣的人最可能不會執行多個“主機”。在這種情況下需要頭欄位是不必要的開銷(但是個小開銷)。

相關推薦

Java網路程式設計NIO開篇Java網路程式設計基礎

老曹眼中的網路程式設計基礎 轉自:https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA 我們是幸運的,因為我們擁有網路。網路是一個神奇的東西,它改變了你和我的生活方式,改變了整個世界。 然而,網路的無標度和

Java網絡編程NIO開篇Java網絡編程基礎

tcp協議 ack focus 特殊字符 設計模式 ssm 域名 代碼片段 mime Java網絡編程和NIO詳解開篇:Java網絡編程基礎 計算機網絡編程基礎 轉自:https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA 我們是

Java網路程式設計NIO3IO模型與Java網路程式設計模型

微信公眾號【Java技術江湖】一位阿里 Java 工程師的技術小站。(關注公眾號後回覆”Java“即可領取 Java基礎、進階、專案和架構師等免費學習資料,更有資料庫、分散式、微服務等熱門技術學習視訊,內容豐富,兼顧原理和實踐,另外也將贈送作者原創的Java學習指南、Java程式設計師面試指南等乾貨資源)

Java網路程式設計NIO2JAVA NIO 一步步構建I/O多路複用的請求模型

微信公眾號【黃小斜】作者是螞蟻金服 JAVA 工程師,專注於 JAVA 後端技術棧:SpringBoot、SSM全家桶、MySQL、分散式、中介軟體、微服務,同時也懂點投資理財,堅持學習和寫作,相信終身學習的力量!關注公眾號後回覆”架構師“即可領取 Java基礎、進階、專案和架構師等免費學習資料,更有資料

Java網絡編程NIO1JAVA 中原生的 socket 通信機制

圖片 構建 -i tst 所有 ice arr 10個 屬於 Java網絡編程和NIO詳解1:JAVA 中原生的 socket 通信機制 JAVA 中原生的 socket 通信機制 摘要:本文屬於原創,歡迎轉載,轉載請保留出處:https://github.com/jas

Java網絡編程NIO5Java 非阻塞 IO 異步 IO

後來 即使 你會 fault 當我 mina 負責 second cti Java網絡編程和NIO詳解5:Java 非阻塞 IO 和異步 IO Java 非阻塞 IO 和異步 IO 轉自https://www.javadoop.com/post/nio-and-aio 本系

Java網路程式設計NIO4淺析NIO包中的Buffer、Channel Selector

Java NIO:Buffer、Channel 和 Selector轉自https://www.javadoop.com/post/nio-and-aio本文將介紹 Java NIO 中三大元件 Buffer、Channel、Selector 的使用。本來要一起介紹非阻塞 I

Java網路程式設計NIO8淺析mmapDirect Buffer

微信公眾號【黃小斜】作者是螞蟻金服 JAVA 工程師,目前在螞蟻財富負責後端開發工作,專注於 JAVA 後端技術棧,同時也懂點投資理財,堅持學習和寫作,用大廠程式設計師的視角解讀技術與網際網路,我的世界裡不只有 coding!關注公眾號後回覆”架構師“即可領取 Java基礎、進階、專案和架構師等免費學習資

Java網絡編程NIO3IO模型與Java網絡編程模型

用戶 分組 重新 spa 編譯 linux操作 計算 再次 簡化 Java網絡編程和NIO詳解3:IO模型與Java網絡編程模型 基本概念說明 用戶空間與內核空間 現在操作系統都是采用虛擬存儲器,那麽對32位操作系統而言,它的尋址空間(虛擬存儲空間)為4G(2的32次方)。

Java網絡編程NIO8淺析mmapDirect Buffer

temp 行動 訪問 objects swa oca 空閑 long 內存操作 Java網絡編程與NIO詳解8:淺析mmap和Direct Buffer 本系列文章首發於我的個人博客:https://h2pl.github.io/ 歡迎閱覽我的CSDN專欄:Java網絡編程

Java網絡編程NIO7淺談 Linux 中NIO Selector 的實現原理

fdt 重要 文件描述 block tor create size 註冊 comm Java網絡編程和NIO詳解7:淺談 Linux 中NIO Selector 的實現原理 轉自:https://www.jianshu.com/p/2b71ea919d49 本系列文章首發於

java的UDPTCP北京-賽車平臺出租源碼分析

ati 消息 byte ide 一段 pack catch 打包 println 1、需求分析北京-賽車平臺出租Q1446595067 最近在和硬件做網口的傳輸協議,一開始告訴我說用TCP的socket進行傳輸,我說沒問題,就寫了個socket的發送和接收方法。but過了沒

java中BiFunctionFunction

關於Lambda詳見lambda描述。關於函式式介面詳見函式式介面 首先我們來看一下BiFunction類,程式碼如下: package java.util.function; import java.util.Objects; /**  * Represents a fu

Java中ComparableComparator

該文基於JDK1.8。 一、Comparable<T> Comparable<T>原始碼如下: package java.lang; import java.util.*; /** * This interface imposes a total orde

JAVA中ListIteratorIterator與辨析

    在使用java集合的時候,都需要使用Iterator。但是java集合中還有一個迭代器ListIterator,在使用List、ArrayList、LinkedList和Vector的時候可以使用。這兩種迭代器有什麼區別呢?下面我們詳細分析。這裡有一點需要明確的時候

第一次面試(java 中equal==的

        今天去了方正國際軟體有限公司面試,第一次面試,面試之後的唯一想做的就是把基礎打好。來看看筆試的第一題 Long a1=128L; Long a2=128L; Long a3=127L; Long a4=127L; Sy

java拆箱裝箱(轉)

需要注意的是:當 "=="運算子的兩個運算元都是 包裝器型別的引用,則是比較指向的是否是同一個物件,而如果其中有一個運算元是表示式(即包含算術運算)則比較的是數值(即會觸發自動拆箱的過程)。另外,對於包裝器型別,equals方法並不會進行型別轉換。   自動裝箱和拆箱問題是Java

C#Socket教程同步TCP程式設計

服務端重要類: TcpListener類,伺服器監聽類,用於監聽和連線客戶端,該類重要方法如下: 構造方法: public TcpListener(IPEndPoint iep); public TcpListener(IPAddress localAddre

Java集合8Java的集合類細節精講

變長參數 span 兩個 就是 his 類別 類型 基本數據 test Java集合詳解8:Java集合類細節精講 今天我們來探索一下Java集合類中的一些技術細節。主要是對一些比較容易被遺漏和誤解的知識點做一些講解和補充。可能不全面,還請諒解。 本文參考:http://c

Java程式設計師從笨鳥到菜鳥之(四十八)細談struts2(十)ognl概念原理

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!