應用程序經過套接字API對UPD協議和TCP協議所提供的服務進行訪問。java
底層由物理層:基礎的通訊信道構成,如以太網/WIFI或調制解調器撥號鏈接。服務器
網絡層:完成將分組報文(packet)傳輸到它的目的地,即路由功能,通常採用IP協議。IP協議提供了一種數據服務:每組分組報文都有網絡獨立處理和分發,就像信件或包裹經過郵政系統發送同樣。每一個IP報文必須包含一個保存其目的地址的字段。網絡
傳輸層:提供了TCP協議和UDP協議,這兩種協議都創建在IP層提供的服務之上,IP協議只是將分組報文分發到不一樣的主機,而後還須要更細粒度的尋址將報文發送到主機指定的應用程序端口,這個尋址功能是TCP或UDP要完成的,所以他們也成爲端到端的傳輸協議。IP協議只是將數據從一個主機傳到另外一個主機。socket
TCP協議提供一個可信賴的字節流信道(處理報文丟失、重傳及順序混亂問題),一種面向鏈接的協議:在使用它進行通訊以前,兩個應用程序之間首先要創建一個TCP鏈接,這涉及到兩臺機器的TCP部件完成握手消息的交互。ui
UDP協議不嘗試對IP層產生的錯誤進行修復,僅僅是簡單地擴展IP協議,傳輸到端口。編碼
網絡層中的IP協議就像把郵件送到某個街道的某個樓的信箱,而傳輸層則是將信件送到該樓層的具體某個房間裏頭。spa
IP協議實際上是單播協議,還有多播協議,廣播到任意數量的地址。設計
一種抽象層,應用程序經過它來發送和接受數據,就像應用程序打開一個文件句柄,將數據讀寫到穩定的存儲器上同樣。使用socket能夠將應用程序添加到網絡中,並與處於同一個網絡中的其餘應用程序進行通訊。一臺計算機上的應用程序向socket寫入的信息可以被另外一臺計算機上的另外一個應用程序讀取。code
不一樣類型的socket與不一樣類型的底層協議族以及同一個協議族的不一樣協議棧相關聯。orm
TCP/IP協議族中的主要socket類型爲流套接字(stream scoket)和數據報套接字(datagram socket)。
流套接字將TCP做爲其端對端協議,提供了一個可信賴的字節流服務。
數據報套接字使用UDP協議,提供了一個best-effort的數據報服務。
一個套接字抽象層能夠被多個應用程序引用,每一個使用了特定套接字的程序均可以經過那個套接字進行通訊。每一個端口都標識了一臺主機上的一個應用程序。實際上,一個端口肯定了一臺主機上的一個套接字。
任何要交換信息的程序之間在信息的編碼方式上必須達成共識(好比將信息表示爲位序列),以及哪一個程序發送消息,何時和怎麼接受信息都將影響程序的行爲。程序間達成的這種包含了信息交換的形式和意義的共識稱爲協議。用來實現特定應用程序的協議稱爲應用程序協議。
TCP/IP協議的惟一約束是,信息必須在塊(chunk)中發送和接收,而塊的長度必須是8位的倍數,所以咱們能夠認爲TCP/IP協議中傳輸的信息是字節序列。
若是是本身設計和編寫套接字的客戶端和服務器端,則能夠爲所欲爲地定義本身的應用程序協議。
對於須要超過一個字節來表示的數據類型,咱們必須知道這些字節的發送順序。
一種是從整數的右邊開始,由低位到高位發送,即little-endian順序;一種是從左邊開始,由高位到低位發送,即big-endian順序。
對於任何多字節的整數,發送者和接收者必須在使用big-endian順序仍是使用little-endian順序上達成共識。
另一個須要達成的共識是:所傳輸的數值是有符號的仍是無符號的。
對於給定的k位,咱們能夠經過二進制補碼來表示-2的k-1次方到2的k-1次方-1範圍的值,若是使用無符號,則能夠表示0到2的k次方-1之間的數值。
DataOutputStream容許你將基本數據類型按big-endian順序進行編碼,即將整數以適當大小的二進制補碼的形式寫到流中。
在一組符號與一組整數之間的映射稱爲編碼字符集,好比ASCII(將英文字母、數字、標點符號以及一些特殊符號映射爲0-127的整數),Unicode(映射到0~65535之間的的整數)。
編碼方案:發送者和接收者須要對這些整數如何表示成字節序列達成一致。
字符集:charset,由編碼字符集和字符的編碼方法結合起來。
Java裏頭內置了特定的序列化,隱藏了全部繁瑣的參數編碼解碼細節。Serialization處理了將實際的Java對象轉換成字節序列的工做,所以你能夠在不一樣虛擬機之間傳遞Java對象實例。
缺點是:它們比較籠統,在通訊開銷上不能作到最高效,好比一個對象的序列化形式其包含的信息在JVM環境之外是毫無心義的;其次是Serializable和Externalizable接口不能用於已經定義了不一樣傳輸格式的狀況;最後用戶自定義的類必須本身實現序列化接口,容易出錯。
public byte[] toWire(VoteMsg msg) throws IOException { String msgString = MAGIC + DELIMSTR + (msg.isInquiry() ? INQSTR : VOTESTR) + DELIMSTR + (msg.isResponse() ? RESPONSESTR + DELIMSTR : "") + Integer.toString(msg.getCandidateID()) + DELIMSTR + Long.toString(msg.getVoteCount()); byte data[] = msgString.getBytes(CHARSETNAME); return data; }
文本方式一般使用一個魔數來開頭。
二進制格式使用固定大小的消息,每條消息由一個特殊字節開始(魔數)。
/* Wire Format * 1 1 1 1 1 1 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * | Magic |Flags| ZERO | * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * | Candidate ID | * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * | | * | Vote Count (only in response) | * | | * | | * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ */ public byte[] toWire(VoteMsg msg) throws IOException { ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(byteStream); // converts ints short magicAndFlags = MAGIC; if (msg.isInquiry()) { magicAndFlags |= INQUIRE_FLAG; } if (msg.isResponse()) { magicAndFlags |= RESPONSE_FLAG; } out.writeShort(magicAndFlags); // We know the candidate ID will fit in a short: it's > 0 && < 1000 out.writeShort((short) msg.getCandidateID()); if (msg.isResponse()) { out.writeLong(msg.getVoteCount()); } out.flush(); byte[] data = byteStream.toByteArray(); return data; }
TCP協議是一個基於流的服務,於是須要提供字節的幀。
// Create an inquiry request (2nd arg = true) VoteMsg msg = new VoteMsg(false, true, CANDIDATEID, 0); byte[] encodedMsg = coder.toWire(msg); // Send request System.out.println("Sending Inquiry (" + encodedMsg.length + " bytes): "); System.out.println(msg); framer.frameMsg(encodedMsg, out); 這裏採用了基於顯示長度的方式來標識幀大小:LengthFarmer類爲每條消息添加一個長度前綴。 public void frameMsg(byte[] message, OutputStream out) throws IOException { if (message.length > MAXMESSAGELENGTH) { throw new IOException("message too long"); } // write length prefix out.write((message.length >> BYTESHIFT) & BYTEMASK); out.write(message.length & BYTEMASK); // write message out.write(message); out.flush(); }