3 發(fā)送和接收數(shù)據(jù)

3.1信息和編碼

通過(guò)套接字進(jìn)行發(fā)送和接收時(shí)篷牌,只能處理字節(jié)和字節(jié)數(shù)組恶迈。作為一種強(qiáng)類型語(yǔ)言臣咖,Java需要把其他數(shù)據(jù)類型(int跃捣,String等)顯式轉(zhuǎn)換成字節(jié)數(shù)組。比如String類的getBytes()方法夺蛇,是將一個(gè)Sring實(shí)例中的字符轉(zhuǎn)換成字節(jié)的標(biāo)準(zhǔn)方式疚漆。
注:語(yǔ)言有無(wú)類型,弱類型和強(qiáng)類型三種刁赦。其中娶聘,無(wú)類型不檢查,甚至不區(qū)分指令和數(shù)據(jù)甚脉;弱類型的檢查很弱丸升,僅能嚴(yán)格的區(qū)分指令和數(shù)據(jù);強(qiáng)類型的則嚴(yán)格的在編譯期進(jìn)行檢查牺氨。強(qiáng)類型語(yǔ)言在沒(méi)有強(qiáng)制類型轉(zhuǎn)化前发钝,不允許兩種不同類型的變量相互操作顿涣。 如:double類型變量a,不經(jīng)過(guò)強(qiáng)制類型轉(zhuǎn)換那么程序int b = a是無(wú)法通過(guò)編譯酝豪。常用的強(qiáng)類型語(yǔ)言有Java涛碑、C# 、Apex和Python等孵淘。

3.1.1基本類型

計(jì)算機(jī)組成原理原碼補(bǔ)碼知識(shí)

3.1.2字符串和文本

我們可以將數(shù)字和boolean類型的數(shù)據(jù)表示成String類型蒲障,如“123478962”,“6.02e23”瘫证,“true”揉阎,“false”等。也可以通過(guò)調(diào)用getBytes()方法背捌,將一個(gè)字符串轉(zhuǎn)換成字節(jié)數(shù)組毙籽。

3.2組合輸入輸出流

Java中與流相關(guān)的類可以組合起來(lái)從而提供強(qiáng)大的功能。例如毡庆,我們可以將一個(gè)Socket實(shí)例的OutputStream包裝在一個(gè)BufferedOutputStream實(shí)例中坑赡,這樣可以先將字節(jié)暫時(shí)緩存在一起,然后再一次全部發(fā)送到底層的通信信道中么抗,以提高程序的性能毅否。我們還能再將這個(gè)BufferedOutputStream實(shí)例包裹在一個(gè)DataOutputStream實(shí)例中,以實(shí)現(xiàn)發(fā)送基本數(shù)據(jù)類型的功能蝇刀。


組合輸入輸出流圖解.png

在這個(gè)例子中螟加,我們先將基本數(shù)據(jù)的值,一個(gè)一個(gè)寫入Data OutputStream中吞琐,DataOutputStream再將這些數(shù)據(jù)以二進(jìn)制的形式寫入BufferedOutput-Stream并將三次寫入的數(shù)據(jù)緩存起來(lái)捆探,然后再由BufferedOutputStream一次性地將這些數(shù)據(jù)寫入套接字的OutputStream,最后由OutputStream將數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)站粟。在另一個(gè)終端徐许,我們創(chuàng)建了相應(yīng)的組合InputStream,以有效地接收基本數(shù)據(jù)類型卒蘸。


Java中的相關(guān)類.png

成幀與解析

這部分主要講的是流傳輸中對(duì)數(shù)據(jù)開始和結(jié)束邊界的處理雌隅,這也是為什么我們使用read()方法讀取-1,進(jìn)行判定讀到流結(jié)束的原因缸沃。(SOGaG∑稹)
成幀(framing)技術(shù)則解決了接收端如何定位消息的首尾位置的問(wèn)題。無(wú)論信息是編碼成了文本趾牧、多字節(jié)二進(jìn)制數(shù)检盼、或是兩者的結(jié)合,應(yīng)用程序協(xié)議必須指定消息的接收者如何確定何時(shí)消息已完整接收翘单。
由于UDP套接字保留了消息的邊界信息吨枉,因此不需要進(jìn)行成幀處理(實(shí)際上蹦渣,主要是DatagramPacket負(fù)載的數(shù)據(jù)有一個(gè)確定的長(zhǎng)度,接收者能夠準(zhǔn)確地知道消息的結(jié)束位置)貌亭,而TCP協(xié)議中沒(méi)有消息邊界的概念柬唯,因此,在使用TCP套接字時(shí)圃庭,成幀就是一個(gè)非常重要的考慮因素(在TCP連接中锄奢,接收者讀取完最后一條消息的最后一個(gè)字節(jié)后,將受到一個(gè)流結(jié)束標(biāo)記剧腻,即read()返回-1拘央,該標(biāo)記指示出已經(jīng)讀取到了消息的末尾,非嚴(yán)格意義上來(lái)講书在,這也算是基于定界符方法的一種特殊情況)灰伟。
主要有兩種技術(shù)使接收者能夠準(zhǔn)確地找到消息的結(jié)束位置:

  • 1、基于定界符:消息的結(jié)束由一個(gè)唯一的標(biāo)記指出儒旬,即發(fā)送者在傳輸完數(shù)據(jù)后顯式添加的一個(gè)特定字節(jié)序列栏账,這個(gè)特殊標(biāo)記不能在傳輸?shù)臄?shù)據(jù)中出現(xiàn)(這也不是絕對(duì)的,應(yīng)用填充技術(shù)能夠?qū)ο⒅谐霈F(xiàn)的定界符進(jìn)行修改义矛,從而使接收者不將其識(shí)別為定界符)。該方法通常用在以文本方式編碼的消息中盟萨。
  • 2凉翻、顯式長(zhǎng)度:在變長(zhǎng)字段或消息前附加一個(gè)固定大小的字段,用來(lái)指示該字段或消息中包含了多少字節(jié)捻激。該方法主要用在以二進(jìn)制字節(jié)方式編碼的消息中制轰。
  • 基于定界符的方法
    通常用在以文本方式編碼的消息中:定義一個(gè)特殊的字符或字符串來(lái)標(biāo)識(shí)消息的結(jié)束。接收者只需要簡(jiǎn)單地掃描輸入信息(以字節(jié)的方式)來(lái)查找定界序列胞谭,并將定界符前面的字符串返回垃杖。這種方法的缺點(diǎn)是消息本身不能包含有定界字符,否則接收者將提前認(rèn)為消息已經(jīng)結(jié)束丈屹。在基于定界符的成幀方法中调俘,發(fā)送者要保證滿足這個(gè)先決條件。幸運(yùn)的是旺垒,填充(stuffing)技術(shù)能夠?qū)ο⒅谐霈F(xiàn)的定界符進(jìn)行修改彩库,從而使接收者不將其識(shí)別為定界符。在接收者掃描定界符時(shí)先蒋,還能識(shí)別出修改過(guò)的數(shù)據(jù)骇钦,并在輸出消息中對(duì)其進(jìn)行還原,從而使其與原始消息一致竞漾。這個(gè)技術(shù)的缺點(diǎn)是發(fā)送者和接收者雙方都必須掃描消息眯搭。
  • 基于長(zhǎng)度的方法
    更簡(jiǎn)單一些窥翩,不過(guò)要使用這種方法必須知道消息長(zhǎng)度的上限。發(fā)送者先要確定消息的長(zhǎng)度鳞仙,將長(zhǎng)度信息存入一個(gè)整數(shù)寇蚊,作為消息的前綴。消息的長(zhǎng)度上限定義了用來(lái)編碼消息長(zhǎng)度所需要的字節(jié)數(shù):如果消息的長(zhǎng)度小于256字節(jié)繁扎,則需要1個(gè)字節(jié)幔荒;如果消息的長(zhǎng)度小于65 536字節(jié),則需要2個(gè)字節(jié)等梳玫。
    代碼實(shí)現(xiàn):
    定義的Framer接口爹梁。它有兩個(gè)方法:frameMsg()方法用來(lái)添加成幀信息并將指定消息輸出到指定流,nextMsg()方法則掃描指定的流提澎,從中抽取出下一條消息姚垃。
    Frame.java
import java.io.IOException;
import java.io.OutputStream;
 
public interface Framer {
  void frameMsg(byte[] message, OutputStream out) throws IOException;
  byte[] nextMsg() throws IOException;
}

DelimFramer.java類實(shí)現(xiàn)了基于定界符的成幀方法,其定界符為“換行”符(“\n”盼忌,字節(jié)值為10)积糯。frameMethod()方法并沒(méi)有實(shí)現(xiàn)填充,當(dāng)成幀的字節(jié)序列中包含有定界符時(shí)谦纱,它只是簡(jiǎn)單地拋出異常看成。nextMsg()方法掃描流,直到讀取到了定界符跨嘉,并返回定界符前面的所有字符川慌,如果流為空則返回null。如果累積了一個(gè)消息的不少字符祠乃,但直到流結(jié)束也沒(méi)有找到定界符梦重,程序?qū)伋鲆粋€(gè)異常來(lái)指示成幀錯(cuò)誤。
DelimFramer.java

import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
 
public class DelimFramer implements Framer {
 
  private InputStream in;        // 數(shù)據(jù)來(lái)源
  private static final byte DELIMITER = '\n'; // 定界符
 
  public DelimFramer(InputStream in) {
    this.in = in;
  }
 
  public void frameMsg(byte[] message, OutputStream out) throws IOException {
    for (byte b : message) {
      if (b == DELIMITER) {
        //如果在消息中檢查到界定符亮瓷,則拋出異常
        throw new IOException("Message contains delimiter");
      }
    }
    out.write(message);
    out.write(DELIMITER);
    out.flush();
  }
 
  public byte[] nextMsg() throws IOException {
    ByteArrayOutputStream messageBuffer = new ByteArrayOutputStream();
    int nextByte;
 
    while ((nextByte = in.read()) != DELIMITER) {
      //如果流已經(jīng)結(jié)束還沒(méi)有讀取到定界符
      if (nextByte == -1) { 
        //如果讀取到的流為空琴拧,則返回null
        if (messageBuffer.size() == 0) { 
          return null;
        } else { 
          //如果讀取到的流不為空,則拋出異常
          throw new EOFException("Non-empty message without delimiter");
        }
      }
      messageBuffer.write(nextByte); 
    }
 
    return messageBuffer.toByteArray();
  }
}

LengthFramer.java類實(shí)現(xiàn)了基于長(zhǎng)度的成幀方法嘱支,適用于長(zhǎng)度小于65 535(216-1)字節(jié)的消息蚓胸。發(fā)送者首先給出指定消息的長(zhǎng)度,并將長(zhǎng)度信息以big-endian順序存入兩個(gè)字節(jié)的整數(shù)中除师,再將這兩個(gè)字節(jié)放在完整的消息內(nèi)容前赢织,連同消息一起寫入輸出流。在接收端馍盟,我們使用DataInputStream以讀取整型的長(zhǎng)度信息于置;readFully()方法將阻塞等待,直到給定的數(shù)組完全填滿,這正是我們需要的八毯。值得注意的是搓侄,使用這種成幀方法,發(fā)送者不需要檢查要成幀的消息內(nèi)容话速,而只需要檢查消息的長(zhǎng)度是否超出了限制讶踪。
LengthFramer.java

import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
 
public class LengthFramer implements Framer {
  public static final int MAXMESSAGELENGTH = 65535;
  public static final int BYTEMASK = 0xff;
  public static final int SHORTMASK = 0xffff;
  public static final int BYTESHIFT = 8;
 
  private DataInputStream in;
 
  public LengthFramer(InputStream in) throws IOException {
    this.in = new DataInputStream(in);    //數(shù)據(jù)來(lái)源
  }
 
  //對(duì)字節(jié)流message添加成幀信息,并輸出到指定流 
  public void frameMsg(byte[] message, OutputStream out) throws IOException {
    //消息的長(zhǎng)度不能超過(guò)65535
    if (message.length > MAXMESSAGELENGTH) {
      throw new IOException("message too long");
    }
    out.write((message.length >> BYTESHIFT) & BYTEMASK);
    out.write(message.length & BYTEMASK);
    out.write(message);
    out.flush();
  }
 
  public byte[] nextMsg() throws IOException {
    int length;
    try { 
      //該方法讀取2個(gè)字節(jié)泊交,將它們作為big-endian整數(shù)進(jìn)行解釋乳讥,并以int型整數(shù)返回它們的值
      length = in.readUnsignedShort(); 
    } catch (EOFException e) { // no (or 1 byte) message
      return null;
    }
    // 0 <= length <= 65535
    byte[] msg = new byte[length];
    //該方法處阻塞等待,直到接收到足夠的字節(jié)來(lái)填滿指定的數(shù)組
    in.readFully(msg); //
    return msg;
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末廓俭,一起剝皮案震驚了整個(gè)濱河市云石,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌研乒,老刑警劉巖汹忠,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異雹熬,居然都是意外死亡宽菜,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門竿报,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)铅乡,“玉大人,你說(shuō)我怎么就攤上這事烈菌≌笮遥” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵僧界,是天一觀的道長(zhǎng)侨嘀。 經(jīng)常有香客問(wèn)我臭挽,道長(zhǎng)捂襟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任欢峰,我火速辦了婚禮葬荷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘纽帖。我一直安慰自己宠漩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布懊直。 她就那樣靜靜地躺著扒吁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪室囊。 梳的紋絲不亂的頭發(fā)上雕崩,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天魁索,我揣著相機(jī)與錄音,去河邊找鬼盼铁。 笑死粗蔚,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的饶火。 我是一名探鬼主播鹏控,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼肤寝!你這毒婦竟也來(lái)了当辐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤醒陆,失蹤者是張志新(化名)和其女友劉穎瀑构,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刨摩,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡寺晌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了澡刹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呻征。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖罢浇,靈堂內(nèi)的尸體忽然破棺而出陆赋,到底是詐尸還是另有隱情,我是刑警寧澤嚷闭,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布攒岛,位于F島的核電站,受9級(jí)特大地震影響胞锰,放射性物質(zhì)發(fā)生泄漏灾锯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一嗅榕、第九天 我趴在偏房一處隱蔽的房頂上張望顺饮。 院中可真熱鬧,春花似錦凌那、人聲如沸兼雄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)赦肋。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間佃乘,已是汗流浹背局蚀。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留恕稠,地道東北人琅绅。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像鹅巍,于是被迫代替她去往敵國(guó)和親千扶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容