04.Java網(wǎng)絡(luò)編程(轉(zhuǎn)載)

1.網(wǎng)絡(luò)編程
  1.1計(jì)算機(jī)網(wǎng)絡(luò)概述
  網(wǎng)絡(luò)編程的實(shí)質(zhì)就是兩個(gè)(或多個(gè))設(shè)備(例如計(jì)算機(jī))之間的數(shù)據(jù)傳輸悠汽。
  按照計(jì)算機(jī)網(wǎng)絡(luò)的定義葵擎,通過(guò)一定的物理設(shè)備將處于不同位置的計(jì)算機(jī)連接起來(lái)組成的網(wǎng)絡(luò)探橱,這個(gè)網(wǎng)絡(luò)中包含的設(shè)備有:計(jì)算機(jī)资柔、路由器甘桑、交換機(jī)等等拍皮。
  其實(shí)從軟件編程的角度來(lái)說(shuō)歹叮,對(duì)于物理設(shè)備的理解不需要很深刻,就像你打電話時(shí)不需要很熟悉通信網(wǎng)絡(luò)的底層實(shí)現(xiàn)是一樣的铆帽,但是當(dāng)深入到網(wǎng)絡(luò)編程的底層時(shí)咆耿,這些基礎(chǔ)知識(shí)是必須要補(bǔ)的。
  路由器和交換機(jī)組成了核心的計(jì)算機(jī)網(wǎng)絡(luò)爹橱,計(jì)算機(jī)只是這個(gè)網(wǎng)絡(luò)上的節(jié)點(diǎn)以及控制等萨螺,通過(guò)光纖、網(wǎng)線等連接將設(shè)備連接起來(lái)愧驱,從而形成了一張巨大的計(jì)算機(jī)網(wǎng)絡(luò)慰技。
  網(wǎng)絡(luò)最主要的優(yōu)勢(shì)在于共享:共享設(shè)備和數(shù)據(jù),現(xiàn)在共享設(shè)備最常見的是打印機(jī)冯键,一個(gè)公司一般一個(gè)打印機(jī)即可惹盼,共享數(shù)據(jù)就是將大量的數(shù)據(jù)存儲(chǔ)在一組機(jī)器中,其它的計(jì)算機(jī)通過(guò)網(wǎng)絡(luò)訪問(wèn)這些數(shù)據(jù)惫确,例如網(wǎng)站手报、銀行服務(wù)器等等。
  如果需要了解更多的網(wǎng)絡(luò)硬件基礎(chǔ)知識(shí)改化,可以閱讀《計(jì)算機(jī)網(wǎng)絡(luò)》教材掩蛤,對(duì)于基礎(chǔ)進(jìn)行強(qiáng)化,這個(gè)在基礎(chǔ)學(xué)習(xí)階段不是必須的陈肛,但是如果想在網(wǎng)絡(luò)編程領(lǐng)域有所造詣揍鸟,則是一個(gè)必須的基本功。
  對(duì)于網(wǎng)絡(luò)編程來(lái)說(shuō)句旱,最主要的是計(jì)算機(jī)和計(jì)算機(jī)之間的通信阳藻,這樣首要的問(wèn)題就是如何找到網(wǎng)絡(luò)上的計(jì)算機(jī)呢?這就需要了解IP地址的概念谈撒。
  為了能夠方便的識(shí)別網(wǎng)絡(luò)上的每個(gè)設(shè)備腥泥,網(wǎng)絡(luò)中的每個(gè)設(shè)備都會(huì)有一個(gè)唯一的數(shù)字標(biāo)識(shí),這個(gè)就是IP地址啃匿。在計(jì)算機(jī)網(wǎng)絡(luò)中,現(xiàn)在命名IP地址的規(guī)定是IPv4協(xié)議蛔外,該協(xié)議規(guī)定每個(gè)IP地址由4個(gè)0-255之間的數(shù)字組成,例如10.0.120.34溯乒。每個(gè)接入網(wǎng)絡(luò)的計(jì)算機(jī)都擁有唯一的IP地址夹厌,這個(gè)IP地址可能是固定的,例如網(wǎng)絡(luò)上各種各樣的服務(wù)器裆悄,也可以是動(dòng)態(tài)的矛纹,例如使用ADSL撥號(hào)上網(wǎng)的寬帶用戶,無(wú)論以何種方式獲得或是否是固定的光稼,每個(gè)計(jì)算機(jī)在聯(lián)網(wǎng)以后都擁有一個(gè)唯一的合法IP地址或南,就像每個(gè)手機(jī)號(hào)碼一樣逻住。
  但是由于IP地址不容易記憶,所以為了方便記憶迎献,有創(chuàng)造了另外一個(gè)概念——域名(Domain Name),例如sohu.com等腻贰。一個(gè)IP地址可以對(duì)應(yīng)多個(gè)域名吁恍,一個(gè)域名只能對(duì)應(yīng)一個(gè)IP地址。域名的概念可以類比手機(jī)中的通訊簿播演,由于手機(jī)號(hào)碼不方便記憶冀瓦,所以添加一個(gè)姓名標(biāo)識(shí)號(hào)碼,在實(shí)際撥打電話時(shí)可以選擇該姓名写烤,然后撥打即可翼闽。
  在網(wǎng)絡(luò)中傳輸?shù)臄?shù)據(jù),全部是以IP地址作為地址標(biāo)識(shí)洲炊,所以在實(shí)際傳輸數(shù)據(jù)以前需要將域名轉(zhuǎn)換為IP地址感局,實(shí)現(xiàn)這種功能的服務(wù)器稱之為DNS服務(wù)器,也就是通俗的說(shuō)法叫做域名解析暂衡。例如當(dāng)用戶在瀏覽器輸入域名時(shí)询微,瀏覽器首先請(qǐng)求DNS服務(wù)器,將域名轉(zhuǎn)換為IP地址狂巢,然后將轉(zhuǎn)換后的IP地址反饋給瀏覽器撑毛,然后再進(jìn)行實(shí)際的數(shù)據(jù)傳輸。
  當(dāng)DNS服務(wù)器正常工作時(shí)唧领,使用IP地址或域名都可以很方便的找到計(jì)算機(jī)網(wǎng)絡(luò)中的某個(gè)設(shè)備藻雌,例如服務(wù)器計(jì)算機(jī)。當(dāng)DNS不正常工作時(shí)斩个,只能通過(guò)IP地址訪問(wèn)該設(shè)備胯杭。所以IP地址的使用要比域名通用一些。
  IP地址和域名很好的解決了在網(wǎng)絡(luò)中找到一個(gè)計(jì)算機(jī)的問(wèn)題萨驶,但是為了讓一個(gè)計(jì)算機(jī)可以同時(shí)運(yùn)行多個(gè)網(wǎng)絡(luò)程序歉摧,就引入了另外一個(gè)概念——端口(port)。
  在介紹端口的概念以前腔呜,首先來(lái)看一個(gè)例子叁温,一般一個(gè)公司前臺(tái)會(huì)有一個(gè)電話,每個(gè)員工會(huì)有一個(gè)分機(jī)核畴,這樣如果需要找到這個(gè)員工的話膝但,需要首先撥打前臺(tái)總機(jī),然后轉(zhuǎn)該分機(jī)號(hào)即可谤草。這樣減少了公司的開銷跟束,也方便了每個(gè)員工莺奸。在該示例中前臺(tái)總機(jī)的電話號(hào)碼就相當(dāng)于IP地址,而每個(gè)員工的分機(jī)號(hào)就相當(dāng)于端口冀宴。
  有了端口的概念以后灭贷,在同一個(gè)計(jì)算機(jī)中每個(gè)程序?qū)?yīng)唯一的端口,這樣一個(gè)計(jì)算機(jī)上就可以通過(guò)端口區(qū)分發(fā)送給每個(gè)端口的數(shù)據(jù)了略贮,換句話說(shuō)甚疟,也就是一個(gè)計(jì)算機(jī)上可以并發(fā)運(yùn)行多個(gè)網(wǎng)絡(luò)程序,而不會(huì)在互相之間產(chǎn)生干擾逃延。
  在硬件上規(guī)定览妖,端口的號(hào)碼必須位于0-65535之間,每個(gè)端口唯一的對(duì)應(yīng)一個(gè)網(wǎng)絡(luò)程序揽祥,一個(gè)網(wǎng)絡(luò)程序可以使用多個(gè)端口讽膏。這樣一個(gè)網(wǎng)絡(luò)程序運(yùn)行在一臺(tái)計(jì)算上時(shí),不管是客戶端還是服務(wù)器拄丰,都是至少占用一個(gè)端口進(jìn)行網(wǎng)絡(luò)通訊府树。在接收數(shù)據(jù)時(shí),首先發(fā)送給對(duì)應(yīng)的計(jì)算機(jī)料按,然后計(jì)算機(jī)根據(jù)端口把數(shù)據(jù)轉(zhuǎn)發(fā)給對(duì)應(yīng)的程序挺尾。
  有了IP地址和端口的概念以后,在進(jìn)行網(wǎng)絡(luò)通訊交換時(shí)站绪,就可以通過(guò)IP地址查找到該臺(tái)計(jì)算機(jī)遭铺,然后通過(guò)端口標(biāo)識(shí)這臺(tái)計(jì)算機(jī)上的一個(gè)唯一的程序。這樣就可以進(jìn)行網(wǎng)絡(luò)數(shù)據(jù)的交換了恢准。
  但是魂挂,進(jìn)行網(wǎng)絡(luò)編程時(shí),只有IP地址和端口的概念還是不夠的馁筐,下面就介紹一下基礎(chǔ)的網(wǎng)絡(luò)編程相關(guān)的軟件基礎(chǔ)知識(shí)涂召。

1.2網(wǎng)絡(luò)編程概述
  網(wǎng)絡(luò)編程中有兩個(gè)主要的問(wèn)題,一個(gè)是如何準(zhǔn)確的定位網(wǎng)絡(luò)上一臺(tái)或多臺(tái)主機(jī)敏沉,另一個(gè)就是找到主機(jī)后如何可靠高效的進(jìn)行數(shù)據(jù)傳輸果正。在TCP/IP協(xié)議中IP層主要負(fù)責(zé)網(wǎng)絡(luò)主機(jī)的定位,數(shù)據(jù)傳輸?shù)穆酚擅顺伲蒊P地址可以唯一地確定Internet上的一臺(tái)主機(jī)秋泳。而TCP層則提供面向應(yīng)用的可靠的或非可靠的數(shù)據(jù)傳輸機(jī)制,這是網(wǎng)絡(luò)編程的主要對(duì)象攒菠,一般不需要關(guān)心IP層是如何處理數(shù)據(jù)的迫皱。
  按照前面的介紹,網(wǎng)絡(luò)編程就是兩個(gè)或多個(gè)設(shè)備之間的數(shù)據(jù)交換辖众,其實(shí)更具體的說(shuō)卓起,網(wǎng)絡(luò)編程就是兩個(gè)或多個(gè)程序之間的數(shù)據(jù)交換和敬,和普通的單機(jī)程序相比,網(wǎng)絡(luò)程序最大的不同就是需要交換數(shù)據(jù)的程序運(yùn)行在不同的計(jì)算機(jī)上戏阅,這樣就造成了數(shù)據(jù)好換的復(fù)雜昼弟。雖然通過(guò)IP地址和端口號(hào)可以找到網(wǎng)絡(luò)上運(yùn)行的一個(gè)程序,但是如果需要進(jìn)行網(wǎng)絡(luò)編程奕筐,則需要了解網(wǎng)絡(luò)通訊的過(guò)程私杜。
  網(wǎng)絡(luò)通訊基于“請(qǐng)求—響應(yīng)”模型。在網(wǎng)絡(luò)通訊中救欧,第一次主動(dòng)發(fā)起通訊的程序被稱為客戶端(client)程序,簡(jiǎn)稱客戶端锣光,而第一次通訊中等待鏈接的程序被稱為服務(wù)器端(Server)程序笆怠,簡(jiǎn)稱服務(wù)器。一旦通訊建立誊爹,則客戶端和服務(wù)器端完全一樣蹬刷,沒(méi)有本質(zhì)區(qū)別。
  由此频丘,網(wǎng)絡(luò)編程中的兩種程序就分別是客戶端和服務(wù)器端办成,例如QQ程序,每個(gè)QQ用戶安裝的都是QQ客戶端程序搂漠,而QQ服務(wù)器端程序則在騰訊公司的機(jī)房中迂卢,為大量的QQ用戶提供服務(wù)。這種網(wǎng)絡(luò)編程的結(jié)構(gòu)被稱為客戶端/服務(wù)器結(jié)構(gòu)桐汤,也叫Client/Serverj結(jié)構(gòu)而克,簡(jiǎn)稱C/S結(jié)構(gòu)。
  使用C/S結(jié)構(gòu)的程序怔毛,在開發(fā)時(shí)需要分別開發(fā)客戶端和服務(wù)器端员萍,這種結(jié)構(gòu)的優(yōu)勢(shì)在于客戶端是專門開發(fā)的,所以根據(jù)需要實(shí)現(xiàn)各種效果拣度,專業(yè)點(diǎn)的說(shuō)就是表現(xiàn)力豐富碎绎,而服務(wù)器端也需要專門進(jìn)行開發(fā)。但是這種結(jié)構(gòu)也存在著很多不足抗果,例如通用性差筋帖,幾乎不能通用,也就是說(shuō)一種程序的客戶端只能和對(duì)應(yīng)的服務(wù)器端通訊冤馏,而不能和其他服務(wù)器端通訊幕随,在實(shí)際維護(hù)中,也需要維護(hù)專門的客戶端和服務(wù)器端宿接,維護(hù)的壓力比較大赘淮。
  其實(shí)在運(yùn)行很多程序時(shí)辕录,沒(méi)有必要使用專門的客戶端,而需要使用通用的客戶端梢卸,例如瀏覽器走诞,使用瀏覽器作為客戶端的結(jié)構(gòu)稱為瀏覽器/服務(wù)器結(jié)構(gòu),也叫做Browser/Server結(jié)構(gòu)蛤高,簡(jiǎn)稱B/S結(jié)構(gòu)蚣旱。
  使用B/S結(jié)構(gòu)的程序,在開發(fā)時(shí)只需要開發(fā)服務(wù)器端即可戴陡,這種優(yōu)勢(shì)在于開發(fā)壓力比較小塞绿,不需要維護(hù)客戶端,但是這種結(jié)構(gòu)也存在這很多不足恤批,例如瀏覽器的限制比較大异吻,表現(xiàn)了不強(qiáng),不能進(jìn)行系統(tǒng)級(jí)別的操作等喜庞。
  總之C/S結(jié)構(gòu)和B/S結(jié)構(gòu)是現(xiàn)在網(wǎng)絡(luò)編程中常見的兩種結(jié)構(gòu)诀浪,B/S結(jié)構(gòu)其實(shí)也就是一種特殊的C/S結(jié)構(gòu)。
  另外簡(jiǎn)單的介紹一下P2P(Point to Point)程序延都,常見的如BT雷猪、電驢等。P2P程序是一種特殊的程序晰房,應(yīng)該一個(gè)P2P程序中既包含客戶端程序求摇,也包含服務(wù)器端程序,例如BT殊者,使用客戶端程序部分連接其它的種子(服務(wù)器端)月帝,而使用服務(wù)器端向其它的BT客戶端傳輸數(shù)據(jù)。如果這個(gè)還不是很清楚幽污,其實(shí)P2P程序和手機(jī)是一樣的嚷辅,當(dāng)手機(jī)撥打電話時(shí)就是使用客戶端的作用,而手機(jī)處于待機(jī)狀態(tài)時(shí)距误,可以接收到其它用戶撥打的電話則起的就是服務(wù)器端的功能簸搞,只是一般的手機(jī)不能同時(shí)使用撥打電話和接聽電話的功能,而P2P程序?qū)崿F(xiàn)了該功能准潭。
  最后介紹一下網(wǎng)絡(luò)編程中最重要的趁俊,也是最復(fù)雜的概念——協(xié)議(protocol)。按照前面的介紹刑然,網(wǎng)絡(luò)編程就是運(yùn)行在不同計(jì)算機(jī)中兩個(gè)程序之間的數(shù)據(jù)交換寺擂。在實(shí)際進(jìn)行數(shù)據(jù)交換時(shí),為了讓接收端理解該數(shù)據(jù),計(jì)算機(jī)比較笨怔软,什么都不懂的垦细,那么久需要規(guī)定該數(shù)據(jù)的格式,這個(gè)數(shù)據(jù)的格式就是協(xié)議挡逼。
  如果沒(méi)有理解協(xié)議的概念括改,那么再舉一個(gè)例子,記得有個(gè)電影叫《永不消逝的電波》家坎,講述的是地下黨通過(guò)電臺(tái)發(fā)送情報(bào)的故事嘱能,這里我們不探討電影的劇情,而只關(guān) 心電臺(tái)發(fā)送的數(shù)據(jù)虱疏。在實(shí)際發(fā)報(bào)時(shí)惹骂,需要首先將需要發(fā)送的內(nèi)容轉(zhuǎn)換為電報(bào)編碼,然后將電報(bào)編碼發(fā)送出去做瞪,而接收端接收的是電報(bào)編碼对粪,如果需要理解電報(bào)的內(nèi)容 則需要根據(jù)密碼本翻譯出該電報(bào)的內(nèi)容。這里的密碼本就規(guī)定了一種數(shù)據(jù)格式穿扳,這種對(duì)于網(wǎng)絡(luò)中傳輸?shù)臄?shù)據(jù)格式在網(wǎng)絡(luò)編程中就被稱作協(xié)議。
  那么如何編寫協(xié)議格式呢国旷?答案是隨意矛物。只要按照這種協(xié)議格式能夠生成唯一的編碼,按照該編碼可以唯一的解析出發(fā)送數(shù)據(jù)的內(nèi)容即可跪但。也正因?yàn)楦鱾€(gè)網(wǎng)絡(luò)程序之間協(xié)議格式的不同履羞,所以才導(dǎo)致了客戶端程序都是專用的結(jié)構(gòu)。
  在實(shí)際的網(wǎng)絡(luò)編程中屡久,最麻煩的內(nèi)容不是數(shù)據(jù)的發(fā)送和接受忆首,因?yàn)檫@個(gè)功能在幾乎所有編程語(yǔ)言中都提供了封裝好的API進(jìn)行調(diào)用,最麻煩的內(nèi)容就是協(xié)議的設(shè)計(jì)及協(xié)議的生產(chǎn)和解析被环,這個(gè)才是網(wǎng)絡(luò)編程最核心的內(nèi)容糙及。
  1.3網(wǎng)絡(luò)通訊方式
  在現(xiàn)有的網(wǎng)絡(luò)中筛欢,網(wǎng)絡(luò)通訊的方式主要有兩種:
  1.TCP(傳輸控制協(xié)議)方式浸锨。
  2.UDP(用戶數(shù)據(jù)協(xié)議)方式。
  為了方便理解這兩種方式版姑,還是先來(lái)看個(gè)例子柱搜。大家使用手機(jī)時(shí),向別人傳遞信息時(shí)有兩種方式:撥打電話和發(fā)送短信剥险。使用撥打電話的方式可以保證該信息傳遞給別人聪蘸,因?yàn)閯e人接電話時(shí)本身就確認(rèn)收到了該信息。而發(fā)送短信的方式價(jià)格低廉,使用方便健爬,但是接受人可能收不到控乾。
  在網(wǎng)絡(luò)通訊中,TCP方式就類似于撥打電話浑劳,使用該種方式進(jìn)行網(wǎng)絡(luò)通訊時(shí)阱持,需要建立專門的虛擬連接,然后進(jìn)行可靠的數(shù)據(jù)傳輸魔熏,如果數(shù)據(jù)發(fā)送失敗衷咽,則客戶端會(huì)自動(dòng)重發(fā)該數(shù)據(jù),而UDP方式就類似于發(fā)送短信蒜绽,使用這種方式進(jìn)行網(wǎng)絡(luò)通訊時(shí)镶骗,不需要建立專門的虛擬連接,傳輸也不是很可靠躲雅,如果發(fā)送失敗則客戶端無(wú)法獲得鼎姊。
  這兩種傳輸方式都是實(shí)際的網(wǎng)絡(luò)編程中進(jìn)行使用,重要的數(shù)據(jù)一般使用TCP方式進(jìn)行數(shù)據(jù)傳輸相赁,而大量的非核心數(shù)據(jù)則都通過(guò)UDP方式進(jìn)行傳遞相寇,在一些程序中甚至結(jié)合使用這兩種方式進(jìn)行數(shù)據(jù)的傳遞。
  由于TCP需要建立專用的虛擬連接以及確認(rèn)傳輸是否正確钮科,所以使用TCP方式的速度稍微慢一些唤衫,而且傳輸時(shí)產(chǎn)生的數(shù)據(jù)量要比UDP稍微大一些。
  關(guān)于網(wǎng)絡(luò)編程的基礎(chǔ)知識(shí)就介紹這么多绵脯,如果需要深入了解相關(guān)知識(shí)請(qǐng)閱讀專門的計(jì)算機(jī)網(wǎng)絡(luò)書籍佳励,下面開始介紹Java語(yǔ)言中網(wǎng)絡(luò)編程的相關(guān)技術(shù)。

1.3網(wǎng)絡(luò)編程步驟
  按照前面的基礎(chǔ)知識(shí)介紹蛆挫,無(wú)論使用TCP方式還是UDP方式進(jìn)行網(wǎng)絡(luò)通訊赃承,網(wǎng)絡(luò)編程都是由客戶端和服務(wù)器端組成,所以悴侵,下面介紹網(wǎng)絡(luò)編程的步驟時(shí)瞧剖,均以C/S結(jié)構(gòu)為基礎(chǔ)進(jìn)行介紹。
  1.3.1客戶端網(wǎng)絡(luò)編程步驟
  客戶端是指網(wǎng)絡(luò)編程中首先發(fā)起連接的程序可免,客戶端一般實(shí)現(xiàn)程序界面和基本邏輯實(shí)現(xiàn)筒繁,在進(jìn)行實(shí)際的客戶端編程時(shí),無(wú)論客戶端復(fù)雜還是簡(jiǎn)單巴元,以及客戶端實(shí)現(xiàn)的方式毡咏,客戶端的編程主要由三個(gè)步驟實(shí)現(xiàn):
  1.建立網(wǎng)絡(luò)連接
  客戶端網(wǎng)絡(luò)編程的第一步都是建立網(wǎng)絡(luò)連接。在建立網(wǎng)絡(luò)連接時(shí)需要指定連接的服務(wù)器的IP地址和端口號(hào)逮刨,建立完成以后呕缭,會(huì)形成一條虛擬的連接堵泽,后續(xù)的操作就可以通過(guò)該連接實(shí)現(xiàn)數(shù)據(jù)交換了。
  2.交換數(shù)據(jù)
  連接建立以后恢总,就可以通過(guò)這個(gè)連接交換數(shù)據(jù)了,交換數(shù)據(jù)嚴(yán)格要求按照請(qǐng)求響應(yīng)模型進(jìn)行,由客戶端發(fā)送一個(gè)請(qǐng)求數(shù)據(jù)到服務(wù)器阳距,服務(wù)器反饋一個(gè)響應(yīng)數(shù)據(jù)后給客戶端咖熟,如果客戶端不發(fā)送請(qǐng)求則服務(wù)器就不響應(yīng)。
  根據(jù)邏輯需要躬存,可以多次交換數(shù)據(jù)盾剩,但是還是必須遵循請(qǐng)求響應(yīng)模型驻粟。
  3.關(guān)閉網(wǎng)絡(luò)連接
  在數(shù)據(jù)交換完成后矿卑,關(guān)閉網(wǎng)絡(luò)連接,釋放程序占用的端口椎咧、內(nèi)存等系統(tǒng)資源,結(jié)束網(wǎng)絡(luò)編程诸狭。
  最基本的步驟一般都是這三個(gè)步驟,在實(shí)際實(shí)現(xiàn)時(shí),步驟2會(huì)出現(xiàn)重復(fù),在進(jìn)行代碼組織時(shí)兴溜,由于網(wǎng)絡(luò)編程是比較耗時(shí)的操作已卷,所以一般開啟專門的現(xiàn)場(chǎng)進(jìn)行網(wǎng)絡(luò)通訊。

1.4服務(wù)器端網(wǎng)絡(luò)編程步驟
  服務(wù)器是指網(wǎng)絡(luò)編程中被等待連接的程序骤菠,服務(wù)器端一般實(shí)現(xiàn)程序的核心邏輯以及數(shù)據(jù)存儲(chǔ)等核心功能它改。服務(wù)器端的編程步驟和客戶端不同,是由四個(gè)步驟實(shí)現(xiàn)商乎,依次是:
  1.監(jiān)聽端口
  服務(wù)器端屬于被動(dòng)等待連接央拖,所以服務(wù)器端啟動(dòng)以后,不需要發(fā)起連接鹉戚,而只需要監(jiān)聽本地計(jì)算機(jī)的某個(gè)固定端口即可鲜戒。這個(gè)端口就是服務(wù)器端開放給客戶端的端口,服務(wù)器端程序運(yùn)行的本地計(jì)算機(jī)的IP地址就是服務(wù)器端程序的IP地址抹凳。
  2.獲得連接
  當(dāng)客戶端連接到服務(wù)器端時(shí)遏餐,服務(wù)器端就可以獲得一個(gè)連接,這個(gè)連接包含客戶端信息却桶,例如客戶端IP地址等境输,服務(wù)器端和客戶端通過(guò)該連接進(jìn)行數(shù)據(jù)交換蔗牡。
  一般在服務(wù)器端編程中颖系,當(dāng)獲得連接時(shí),需要開啟專門的線程處理該連接辩越,每個(gè)連接都由獨(dú)立的線程實(shí)現(xiàn)嘁扼。
  3.交換數(shù)據(jù)
  服務(wù)器端通過(guò)獲得的連接進(jìn)行數(shù)據(jù)交換。服務(wù)器端的數(shù)據(jù)交換步驟是首先接收客戶端發(fā)送過(guò)來(lái)的數(shù)據(jù)黔攒,然后進(jìn)行邏輯處理趁啸,再把處理以后的結(jié)果數(shù)據(jù)發(fā)送給客戶端强缘。簡(jiǎn)單來(lái)說(shuō),就是先接收再發(fā)送不傅,這個(gè)和客戶端的數(shù)據(jù)交換順序不同旅掂。
  其實(shí),服務(wù)器端獲得的連接和客戶端的連接是一樣的访娶,只是數(shù)據(jù)交換的步驟不同商虐。當(dāng)然,服務(wù)器端的數(shù)據(jù)交換也是可以多次進(jìn)行的崖疤。在數(shù)據(jù)交換完成以后秘车,關(guān)閉和客戶端的連接。
  4.關(guān)閉連接
  當(dāng)服務(wù)器程序關(guān)閉時(shí)劫哼,需要關(guān)閉服務(wù)器端叮趴,通過(guò)關(guān)閉服務(wù)器端使得服務(wù)器監(jiān)聽的端口以及占用的內(nèi)存可以釋放出來(lái),實(shí)現(xiàn)了連接的關(guān)閉权烧。
  其實(shí)服務(wù)器端編程的模型和呼叫中心的實(shí)現(xiàn)是類似的眯亦,例如移動(dòng)的客服電話10086就是典型的呼叫中心,當(dāng)一個(gè)用戶撥打10086時(shí)般码,轉(zhuǎn)接給一個(gè)專門的客服人員搔驼,由該客服實(shí)現(xiàn)和該用戶的問(wèn)題解決,當(dāng)另外一個(gè)用戶撥打10086時(shí)侈询,則轉(zhuǎn)接給另一個(gè)客服舌涨,實(shí)現(xiàn)問(wèn)題解決,依次類推扔字。
  在服務(wù)器端編程時(shí)囊嘉,10086這個(gè)電話號(hào)碼就類似于服務(wù)器端的端口號(hào)碼,每個(gè)用戶就相當(dāng)于一個(gè)客戶端程序革为,每個(gè)客服人員就相當(dāng)于服務(wù)器端啟動(dòng)的專門和客戶端連接的線程扭粱,每個(gè)線程都是獨(dú)立進(jìn)行交互的。
  這就是服務(wù)器端編程的模型震檩,只是TCP方式是需要建立連接的琢蛤,對(duì)于服務(wù)器端的壓力比較大,而UDP是不需要建立連接的抛虏,對(duì)于服務(wù)器端的壓力比較小罷了博其。
  總之,無(wú)論使用任何語(yǔ)言迂猴,任何方式進(jìn)行基礎(chǔ)的網(wǎng)絡(luò)編程慕淡,都必須遵循固定的步驟進(jìn)行操作,在熟悉了這些步驟以后沸毁,可以根據(jù)需要進(jìn)行邏輯上的處理峰髓,但是還是必須遵循固定的步驟進(jìn)行傻寂。
  其實(shí),基礎(chǔ)的網(wǎng)絡(luò)編程本身不難携兵,也不需要很多的基礎(chǔ)網(wǎng)絡(luò)知識(shí)疾掰,只是由于編程的基礎(chǔ)功能都已經(jīng)由API實(shí)現(xiàn),而且需要按照固定的步驟進(jìn)行徐紧,所以在入門時(shí)有一定的門檻个绍,希望下面的內(nèi)容能夠?qū)⒛憧焖俚膸刖W(wǎng)絡(luò)編程技術(shù)的大門纹份。

2.Java網(wǎng)絡(luò)編程技術(shù)
  和網(wǎng)絡(luò)編程有關(guān)的基本API位于Java.NET包中古掏,該包中包含了基本的網(wǎng)絡(luò)編程實(shí)現(xiàn)糙麦,該包是網(wǎng)絡(luò)編程的基礎(chǔ)嬉橙。該包既包含基本的網(wǎng)絡(luò)編程類硅急,也包含封裝后的專門處理WEB相關(guān)的處理類裙盾。
  首先來(lái)介紹一下基礎(chǔ)的網(wǎng)絡(luò)類-InetAddress類薯鼠。該類的功能是代表一個(gè)IP地址馋没,并且將IP地址和域名相關(guān)的操作方法包含在該類的內(nèi)部呀潭。關(guān)于該類的使用钉迷,下面通過(guò)一個(gè)基礎(chǔ)的代碼演示該類的使用。

import java.net.InetAddress;
import java.net.UnknownHostException;

public class InetAddressDemo {
    public static void main(String[] args) {

        try {
            InetAddress inet1 = InetAddress.getByName("www.163.com");
            System.out.println(inet1);
            InetAddress inet2=InetAddress.getByName("127.0.0.1");
            System.out.println(inet2);
            InetAddress inet3=InetAddress.getLocalHost();
            System.out.println(inet3);
            String host =inet3.getHostName();
            System.out.println("域名:"+host);
            String ip=inet3.getHostAddress();
            System.out.println("IP:"+ip);
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }
}

在該示例代碼中钠署,演示了InetAddress類的基本使用糠聪,并使用了該類的幾個(gè)常用方法,該代碼的執(zhí)行結(jié)果是:
www.163.com/202.201.14.182
/127.0.0.1
DESKTOP-HRHF03J/192.168.1.196
域名:DESKTOP-HRHF03J
IP:192.168.1.196
說(shuō)明:由于該代碼中包含一個(gè)互聯(lián)網(wǎng)的網(wǎng)址谐鼎,所以運(yùn)行該程序時(shí)需要聯(lián)網(wǎng)舰蟆,否則將產(chǎn)生異常。
在后續(xù)的使用中狸棍,經(jīng)常包含需要使用InetAddress對(duì)象代表IP地址的構(gòu)造方法身害,當(dāng)然,該類的使用補(bǔ)水必須的草戈,也可以使用字符串來(lái)代表IP地址塌鸯。

3.TCP編程
  在Java語(yǔ)言中,對(duì)于TCP方式的網(wǎng)絡(luò)編程提供了良好的支持唐片,在實(shí)際實(shí)現(xiàn)時(shí)丙猬,以java.net.socket類代表客戶端連接,以java.net.ServerSocket類作為服務(wù)器端連接费韭。在進(jìn)行網(wǎng)絡(luò)編程時(shí)茧球,底層網(wǎng)絡(luò)通訊的細(xì)節(jié)已經(jīng)實(shí)現(xiàn)了比較高的封裝,所以在程序員實(shí)際編程時(shí)揽思,只需要指定IP地址和端口號(hào)就可以建立連接了袜腥。正是由于這種高度的封裝见擦,一方面钉汗,簡(jiǎn)化了Java語(yǔ)言網(wǎng)絡(luò)編程的難度羹令,另外也使得使用Java語(yǔ)言進(jìn)行網(wǎng)絡(luò)編程無(wú)法深入到網(wǎng)絡(luò)的底層,所以使用Java語(yǔ)言進(jìn)行網(wǎng)絡(luò)底層系統(tǒng)編程很困難损痰,具體點(diǎn)說(shuō)福侈,Java語(yǔ)言無(wú)法事先底層的網(wǎng)絡(luò)嗅探以及獲得IP包結(jié)構(gòu)等消息。但是由于Java語(yǔ)言的網(wǎng)絡(luò)編程比較簡(jiǎn)答卢未,所以還是獲得了廣泛的使用肪凛。
  在使用TCP方式進(jìn)行網(wǎng)絡(luò)編程時(shí),需要按照前面介紹的網(wǎng)絡(luò)編程的步驟進(jìn)行辽社,下面分別介紹一下在Java語(yǔ)言中客戶端和服務(wù)器端的實(shí)現(xiàn)步驟伟墙。在客戶端網(wǎng)絡(luò)編程中,首先需要建立連接滴铅,在Java API中以及java.net.socket類的對(duì)象代表網(wǎng)絡(luò)連接戳葵,所以建立客戶端網(wǎng)絡(luò)連接,也就是創(chuàng)建Socket類型的對(duì)象汉匙,該對(duì)象代表網(wǎng)絡(luò)連接拱烁,示例如下:
  Socket socket1=new Socket(“192.168.1.103”,10000);   Socket socket2=new Socket(“www.sohu.com”,80);
  上面的代碼中,socket1實(shí)現(xiàn)的是連接到IP地址是192.168.1.103的計(jì)算機(jī)的10000號(hào)端口噩翠,而socket2實(shí)現(xiàn)的是連接到域名是www.sohu.com的計(jì)算機(jī)的80號(hào)端口戏自,至于底層網(wǎng)絡(luò)如何實(shí)現(xiàn)建立連接,對(duì)于程序員來(lái)說(shuō)是完全透明的伤锚。如果建立連接時(shí)擅笔,本機(jī)網(wǎng)絡(luò)不通,或服務(wù)器端程序未開啟屯援,則會(huì)拋出異常剂娄。
  連接一旦建立,則完成了客戶端編程的第一步玄呛,緊接著的步驟就是按照“請(qǐng)求-響應(yīng)”模型進(jìn)行網(wǎng)絡(luò)數(shù)據(jù)交換阅懦,在Java語(yǔ)言中,數(shù)據(jù)傳輸功能由Java IO實(shí)現(xiàn)徘铝,也就是說(shuō)只需要從連接中獲得輸入流和輸出流即可耳胎,然后將需要發(fā)送的數(shù)據(jù)寫入連接對(duì)象的輸出流中,在發(fā)送完成后從輸入流中讀取數(shù)據(jù)即可惕它。示例代碼如下:

OutputStream os=socket1.getOutputStream();
InputStream is=socket1,getInputStream();

上面的代碼中怕午,分別從socket1這個(gè)連接對(duì)象獲得了輸出流和輸入流對(duì)象,在整個(gè)網(wǎng)絡(luò)編程中淹魄,后續(xù)的數(shù)據(jù)交換就變成了IO操作郁惜,也就是遵循“請(qǐng)求-響應(yīng)”模式的規(guī)定,先向輸出流中寫入數(shù)據(jù)甲锡,這些數(shù)據(jù)會(huì)被系統(tǒng)發(fā)送出去兆蕉,然后再?gòu)妮斎肓髦凶x取服務(wù)器端的反饋信息羽戒,這樣就完成了一次數(shù)據(jù)交換工作,當(dāng)然這個(gè)數(shù)據(jù)交換可以多次進(jìn)行虎韵。
  這里獲得的只是最基本的輸出流和輸入流對(duì)象易稠,還可以根據(jù)前面學(xué)習(xí)到的IO知識(shí),使用流的嵌套將這些獲得的基本流對(duì)象轉(zhuǎn)換成需要的裝飾流對(duì)象包蓝,從而方便數(shù)據(jù)的操作驶社。
  最后當(dāng)數(shù)據(jù)交換完成以后,關(guān)閉網(wǎng)絡(luò)連接测萎,釋放網(wǎng)絡(luò)連接占用的系統(tǒng)端口和內(nèi)存等資源亡电,完成網(wǎng)絡(luò)操作,示例代碼如下:
  socket1.close();
  這就是最基本的網(wǎng)絡(luò)編程功能介紹硅瞧,下面是一個(gè)簡(jiǎn)單的網(wǎng)絡(luò)客戶端程序示例逊抡,該程序的作用是向服務(wù)器發(fā)送一個(gè)字符串“Hello”,并將服務(wù)器端的反饋顯示到控制臺(tái)零酪,數(shù)據(jù)交換只進(jìn)行一次冒嫡,當(dāng)數(shù)據(jù)交換完成以后關(guān)閉網(wǎng)絡(luò)連接,程序結(jié)束四苇,實(shí)現(xiàn)的代碼如下:

package tcp;
import java.io.*;
import java.net.*;
/**
 * 簡(jiǎn)單的Socket客戶端
 * 功能為:發(fā)送字符串“Hello”到服務(wù)器端孝凌,并打印出服務(wù)器端的反饋
 */
public class SimpleSocketClient {
         public static void main(String[] args) {
                   Socket socket = null;
                   InputStream is = null;
                   OutputStream os = null;
                   //服務(wù)器端IP地址
                   String serverIP = "127.0.0.1";
                   //服務(wù)器端端口號(hào)
                   int port = 10000;
                   //發(fā)送內(nèi)容
                   String data = "Hello";
                   try {
                            //建立連接
                            socket = new Socket(serverIP,port);
                            //發(fā)送數(shù)據(jù)
                            os = socket.getOutputStream();
                            os.write(data.getBytes());
                            //接收數(shù)據(jù)
                            is = socket.getInputStream();
                            byte[] b = new byte[1024];
                            int n = is.read(b);
                            //輸出反饋數(shù)據(jù)
                            System.out.println("服務(wù)器反饋:" + new String(b,0,n));
                   } catch (Exception e) {
                            e.printStackTrace(); //打印異常信息
                   }finally{
                            try {
                                     //關(guān)閉流和連接
                                     is.close();
                                     os.close();
                                     socket.close();
                            } catch (Exception e2) {}
                   }
         }
}

在該示例代碼中建立了一個(gè)連接到IP地址為127.0.0.1,端口號(hào)為10000的TCP類型的網(wǎng)絡(luò)連接月腋,然后獲得連接的輸出流對(duì)象蟀架,將需要發(fā)送的字符串“Hello”轉(zhuǎn)換為波耶特?cái)?shù)組寫入到輸出流中,由系統(tǒng)自動(dòng)完成將輸出流中的數(shù)據(jù)發(fā)送出去榆骚,如果需要強(qiáng)制發(fā)送片拍,可以調(diào)用輸出流對(duì)象中的flush方法實(shí)現(xiàn)。在數(shù)據(jù)發(fā)送出去后妓肢,從連接對(duì)象的輸入流中讀取服務(wù)器端的反饋信息捌省,讀取時(shí)可以使用IO中的各種讀取方法進(jìn)行讀取,這里使用最簡(jiǎn)單的方法進(jìn)行讀取碉钠。從輸入流中讀取到的內(nèi)容就是服務(wù)器端的反饋纲缓,并將讀取到的內(nèi)容在客戶端的控制臺(tái)進(jìn)行輸出,最后依次關(guān)閉打開的流對(duì)象和網(wǎng)絡(luò)連接對(duì)象喊废。
  這是一個(gè)簡(jiǎn)單的功能示例祝高,在該示例中演示了TCP類型的網(wǎng)絡(luò)客戶端基本方法的使用,該代碼只起演示目的污筷,還無(wú)法達(dá)到實(shí)用的級(jí)別工闺。
  如果需要在控制臺(tái)下面編譯和運(yùn)行該代碼,需要首先在控制臺(tái)下切換到源代碼所在的目錄,然后依次輸入編譯和運(yùn)行命令:

 javac –d . SimpleSocketClient.java

 java tcp.SimpleSocketClient

和下面將要介紹的SimpleSocketServer服務(wù)器端組合運(yùn)行時(shí)陆蟆,程序的輸出結(jié)果為:

 服務(wù)器反饋:Hello

介紹完一個(gè)簡(jiǎn)單的客戶端編程的示例雷厂,下面接著介紹一下TCP類型的服務(wù)器端的編寫。首先需要說(shuō)明的是遍搞,客戶端的步驟和服務(wù)器端的編寫步驟不同罗侯,所以在學(xué)習(xí)服務(wù)器端編程時(shí)注意不要和客戶端混淆起來(lái)器腋。
  在服務(wù)器端程序編程中溪猿,由于服務(wù)器端實(shí)現(xiàn)的是被動(dòng)等待連接,所以服務(wù)器端編程的第一個(gè)步驟是監(jiān)聽端口纫塌,也就是監(jiān)聽是否有客戶端連接到達(dá)诊县。實(shí)現(xiàn)服務(wù)器端監(jiān)聽的代碼為:
  ServerSocket ss = new ServerSocket(10000);
  該代碼實(shí)現(xiàn)的功能是監(jiān)聽當(dāng)前計(jì)算機(jī)的10000號(hào)端口,如果在執(zhí)行該代碼時(shí)措左,10000號(hào)端口已經(jīng)被別的程序占用依痊,那么將拋出異常。否則將實(shí)現(xiàn)監(jiān)聽怎披。
  服務(wù)器端編程的第二個(gè)步驟是獲得連接胸嘁。該步驟的作用是當(dāng)有客戶端連接到達(dá)時(shí),建立一個(gè)和客戶端連接對(duì)應(yīng)的Socket連 接對(duì)象凉逛,從而釋放客戶端連接對(duì)于服務(wù)器端端口的占用性宏。實(shí)現(xiàn)功能就像公司的前臺(tái)一樣,當(dāng)一個(gè)客戶到達(dá)公司時(shí)状飞,會(huì)告訴前臺(tái)我找某某某毫胜,然后前臺(tái)就通知某某某, 然后就可以繼續(xù)接待其它客戶了诬辈。通過(guò)獲得連接酵使,使得客戶端的連接在服務(wù)器端獲得了保持,另外使得服務(wù)器端的端口釋放出來(lái)焙糟,可以繼續(xù)等待其它的客戶端連接口渔。 實(shí)現(xiàn)獲得連接的代碼是:

Socket socket = ss.accept();

該代碼實(shí)現(xiàn)的功能是獲得當(dāng)前連接到服務(wù)器端的客戶端連接。需要說(shuō)明的是accept和前面IO部分介紹的read方法一樣穿撮,都是一個(gè)阻塞方法搓劫,也就是當(dāng)無(wú)連接時(shí),該方法將阻塞程序的執(zhí)行混巧,直到連接到達(dá)時(shí)才執(zhí)行該行代碼枪向。另外獲得的連接會(huì)在服務(wù)器端的該端口注冊(cè),這樣以后就可以通過(guò)在服務(wù)器端的注冊(cè)信息直接通信咧党,而注冊(cè)以后服務(wù)器端的端口就被釋放出來(lái)秘蛔,又可以繼續(xù)接受其它的連接了。
   連接獲得以后,后續(xù)的編程就和客戶端的網(wǎng)絡(luò)編程類似了深员,這里獲得的Socket類型的連接就和客戶端的網(wǎng)絡(luò)連接一樣了负蠕,只是服務(wù)器端需要首先讀取發(fā)送過(guò)來(lái)的數(shù)據(jù),然后進(jìn)行邏輯處理以后再發(fā)送給客戶端倦畅,也就是交換數(shù)據(jù)的順序和客戶端交換數(shù)據(jù)的步驟剛好相反遮糖。這部分的內(nèi)容和客戶端很類似,所以就不重復(fù)了叠赐,如果還不熟悉欲账,可以參看下面的示例代碼。
  最后芭概,在服務(wù)器端通信完成以后赛不,關(guān)閉服務(wù)器端連接。實(shí)現(xiàn)的代碼為:ss.close();
  這就是基本的TCP類型的服務(wù)器端編程步驟罢洲。下面以一個(gè)簡(jiǎn)單的echo服務(wù)實(shí)現(xiàn)為例子踢故,介紹綜合使用示例。echo的意思就是“回聲”惹苗,echo服務(wù)器端實(shí)現(xiàn)的功能就是將客戶端發(fā)送的內(nèi)容再原封不動(dòng)的反饋給客戶端殿较。實(shí)現(xiàn)的代碼如下:

package tcp;

import java.io.*;
import java.net.*;
/**
 * echo服務(wù)器
 * 功能:將客戶端發(fā)送的內(nèi)容反饋給客戶端
 */
public class SimpleSocketServer {
         public static void main(String[] args) {
                   ServerSocket serverSocket = null;
                   Socket socket = null;
                   OutputStream os = null;
                   InputStream is = null;
                   //監(jiān)聽端口號(hào)
                   int port = 10000;
                   try {
                            //建立連接
                            serverSocket = new ServerSocket(port);
                            //獲得連接
                            socket = serverSocket.accept();
                            //接收客戶端發(fā)送內(nèi)容
                            is = socket.getInputStream();
                            byte[] b = new byte[1024];
                            int n = is.read(b);
                            //輸出
                            System.out.println("客戶端發(fā)送內(nèi)容為:" + new String(b,0,n));
                            //向客戶端發(fā)送反饋內(nèi)容
                            os = socket.getOutputStream();
                            os.write(b, 0, n);
                   } catch (Exception e) {
                            e.printStackTrace();
                   }finally{
                            try{
                                     //關(guān)閉流和連接
                                     os.close();
                                     is.close();
                                     socket.close();
                                     serverSocket.close();
                            }catch(Exception e){}
                   }
         }
}

在該示例代碼中建立了一個(gè)監(jiān)聽當(dāng)前計(jì)算機(jī)10000號(hào)端口的服務(wù)器端Socket連接,然后獲得客戶端發(fā)送過(guò)來(lái)的連接桩蓉,如果有連接到達(dá)時(shí)淋纲,讀取連接中發(fā)送過(guò)來(lái)的內(nèi)容,并將發(fā)送的內(nèi)容在控制臺(tái)進(jìn)行輸出触机,輸出完成以后將客戶端發(fā)送的內(nèi)容再反饋給客戶端帚戳。最后關(guān)閉流和連接對(duì)象,結(jié)束程序儡首。
  在控制臺(tái)下面編譯和運(yùn)行該程序的命令和客戶端部分的類似片任。
  這樣,就以一個(gè)很簡(jiǎn)單的示例演示了TCP類型的網(wǎng)絡(luò)編程在Java語(yǔ)言中的基本實(shí)現(xiàn)蔬胯,這個(gè)示例只是演示了網(wǎng)絡(luò)編程的基本步驟以及各個(gè)功能方法的基本使用对供,只是為網(wǎng)絡(luò)編程打下了一個(gè)基礎(chǔ),下面將就幾個(gè)問(wèn)題來(lái)深入介紹網(wǎng)絡(luò)編程深層次的一些知識(shí)氛濒。
  為了一步一步的掌握網(wǎng)絡(luò)編程产场,下面再研究網(wǎng)絡(luò)編程中的兩個(gè)基本問(wèn)題,通過(guò)解決這兩個(gè)問(wèn)題將對(duì)網(wǎng)絡(luò)編程的認(rèn)識(shí)深入一層舞竿。
  1京景、如何復(fù)用Socket連接?
  在前面的示例中骗奖,客戶端中建立了一次連接确徙,只發(fā)送一次數(shù)據(jù)就關(guān)閉了醒串,這就相當(dāng)于撥打電話時(shí),電話打通了只對(duì)話一次就關(guān)閉了鄙皇,其實(shí)更加常用的應(yīng)該是撥通一次電話以后多次對(duì)話芜赌,這就是復(fù)用客戶端連接。
  那么如何實(shí)現(xiàn)建立一次連接伴逸,進(jìn)行多次數(shù)據(jù)交換呢缠沈?其實(shí)很簡(jiǎn)單,建立連接以后错蝴,將數(shù)據(jù)交換的邏輯寫到一個(gè)循環(huán)中就可以了洲愤。這樣只要循環(huán)不結(jié)束則連接就不會(huì)被關(guān) 閉。按照這種思路漱竖,可以改造一下上面的代碼禽篱,讓該程序可以在建立連接一次以后畜伐,發(fā)送三次數(shù)據(jù)馍惹,當(dāng)然這里的次數(shù)也可以是多次,示例代碼如下:

package tcp;
import java.io.*;
import java.net.*;
/**
 * 復(fù)用連接的Socket客戶端
 * 功能為:發(fā)送字符串“Hello”到服務(wù)器端玛界,并打印出服務(wù)器端的反饋
 */
public class MulSocketClient {
         public static void main(String[] args) {
                   Socket socket = null;
                   InputStream is = null;
                   OutputStream os = null;
                   //服務(wù)器端IP地址
                   String serverIP = "127.0.0.1";
                   //服務(wù)器端端口號(hào)
                   int port = 10000;
                   //發(fā)送內(nèi)容
                   String data[] ={"First","Second","Third"};
                   try {
                            //建立連接
                            socket = new Socket(serverIP,port);
                            //初始化流
                            os = socket.getOutputStream();
                            is = socket.getInputStream();
                            byte[] b = new byte[1024];
                            for(int i = 0;i < data.length;i++){
                                     //發(fā)送數(shù)據(jù)
                                     os.write(data[i].getBytes());
                                     //接收數(shù)據(jù)
                                     int n = is.read(b);
                                     //輸出反饋數(shù)據(jù)
                                     System.out.println("服務(wù)器反饋:" + new String(b,0,n));
                            }
                   } catch (Exception e) {
                            e.printStackTrace(); //打印異常信息
                   }finally{
                            try {
                                     //關(guān)閉流和連接
                                     is.close();
                                     os.close();
                                     socket.close();
                            } catch (Exception e2) {}
                   }
         }
}

該示例程序和前面的代碼相比万矾,將數(shù)據(jù)交換部分的邏輯寫在一個(gè)for循環(huán)的內(nèi)容,這樣就可以建立一次連接慎框,依次將data數(shù)組中的數(shù)據(jù)按照順序發(fā)送給服務(wù)器端了良狈。
  如果還是使用前面示例代碼中的服務(wù)器端程序運(yùn)行該程序,則該程序的結(jié)果是:

java.net.SocketException: Software caused connection abort: recv failed

                                 at java.net.SocketInputStream.socketRead0(Native Method)

                                 at java.net.SocketInputStream.read(SocketInputStream.java:129)

                                 at java.net.SocketInputStream.read(SocketInputStream.java:90)

                                 at tcp.MulSocketClient.main(MulSocketClient.java:30)

服務(wù)器反饋:First

顯然笨枯,客戶端在實(shí)際運(yùn)行時(shí)出現(xiàn)了異常薪丁,出現(xiàn)異常的原因是什么呢?如果仔細(xì)閱讀前面的代碼馅精,應(yīng)該還記得前面示例代碼中的服務(wù)器端是對(duì)話一次數(shù)據(jù)以后就關(guān)閉了連接严嗜,如果服務(wù)器端程序關(guān)閉了,客戶端繼續(xù)發(fā)送數(shù)據(jù)肯定會(huì)出現(xiàn)異常洲敢,這就是出現(xiàn)該問(wèn)題的原因漫玄。
  按照客戶端實(shí)現(xiàn)的邏輯,也可以復(fù)用服務(wù)器端的連接压彭,實(shí)現(xiàn)的原理也是將服務(wù)器端的數(shù)據(jù)交換邏輯寫在循環(huán)中即可睦优,按照該種思路改造以后的服務(wù)器端代碼為:

package tcp;
import java.io.*;
import java.net.*;
/**
 * 復(fù)用連接的echo服務(wù)器
 * 功能:將客戶端發(fā)送的內(nèi)容反饋給客戶端
 */
public class MulSocketServer {
         public static void main(String[] args) {
                   ServerSocket serverSocket = null;
                   Socket socket = null;
                   OutputStream os = null;
                   InputStream is = null;
                   //監(jiān)聽端口號(hào)
                   int port = 10000;
                   try {
                            //建立連接
                            serverSocket = new ServerSocket(port);
                            System.out.println("服務(wù)器已啟動(dòng):");
                            //獲得連接
                            socket = serverSocket.accept();
                            //初始化流
                            is = socket.getInputStream();
                            os = socket.getOutputStream();
                            byte[] b = new byte[1024];
                            for(int i = 0;i < 3;i++){
                                     int n = is.read(b);
                                     //輸出
                                     System.out.println("客戶端發(fā)送內(nèi)容為:" + new String(b,0,n));
                                     //向客戶端發(fā)送反饋內(nèi)容
                                     os.write(b, 0, n);
                            }
                   } catch (Exception e) {
                            e.printStackTrace();
                   }finally{
                            try{
                                     //關(guān)閉流和連接
                                     os.close();
                                     is.close();
                                     socket.close();
                                     serverSocket.close();
                            }catch(Exception e){}
                   }
         }
}

在該示例代碼中,也將數(shù)據(jù)發(fā)送和接收的邏輯寫在了一個(gè)for循環(huán)內(nèi)部壮不,只是在實(shí)現(xiàn)時(shí)硬性的將循環(huán)次數(shù)規(guī)定成了3次汗盘,這樣代碼雖然比較簡(jiǎn)單,但是通用性比較差询一。
  以該服務(wù)器端代碼實(shí)現(xiàn)為基礎(chǔ)運(yùn)行前面的客戶端程序時(shí)隐孽,客戶端的輸出為:
服務(wù)器反饋:First
服務(wù)器反饋:Second
服務(wù)器反饋:Third
服務(wù)器端程序的輸出結(jié)果為:
服務(wù)器已啟動(dòng):
客戶端發(fā)送內(nèi)容為:First
客戶端發(fā)送內(nèi)容為:Second
客戶端發(fā)送內(nèi)容為:Third
  在該程序中尸执,比較明顯的體現(xiàn)出了“請(qǐng)求-響應(yīng)”模型,也就是在客戶端發(fā)起連接以后缓醋,首先發(fā)送字符串“First”給服務(wù)器端如失,服務(wù)器端輸出客戶端發(fā)送的內(nèi)容“First”,然后將客戶端發(fā)送的內(nèi)容再反饋給客戶端送粱,這樣客戶端也輸出服務(wù)器反饋“First”褪贵,這樣就完成了客戶端和服務(wù)器端的一次對(duì)話,緊接著客戶端發(fā)送“Second”給服務(wù)器端抗俄,服務(wù)端輸出“Second”脆丁,然后將“Second”再反饋給客戶端,客戶端再輸出“Second”动雹,從而完成第二次會(huì)話槽卫,第三次會(huì)話的過(guò)程和這個(gè)一樣。在這個(gè)過(guò)程中胰蝠,每次都是客戶端程序首先發(fā)送數(shù)據(jù)給服務(wù)器端歼培,服務(wù)器接收數(shù)據(jù)以后,將結(jié)果反饋給客戶端茸塞,客戶端接收到服務(wù)器端的反饋躲庄,從而完成一次通訊過(guò)程。
在該示例中钾虐,雖然解決了多次發(fā)送的問(wèn)題噪窘,但是客戶端和服務(wù)器端的次數(shù)控制還不夠靈活,如果客戶端的次數(shù)不固定怎么辦呢效扫?是否可以使用某個(gè)特殊的字符串倔监,例如quit,表示客戶端退出呢,這就涉及到網(wǎng)絡(luò)協(xié)議的內(nèi)容了菌仁,會(huì)在后續(xù)的網(wǎng)絡(luò)應(yīng)用示例部分詳細(xì)介紹浩习。下面開始介紹另外一個(gè)網(wǎng)絡(luò)編程的突出問(wèn)題。
2掘托、如何使服務(wù)器端支持多個(gè)客戶端同時(shí)工作瘦锹?
前面介紹的服務(wù)器端程序,只是實(shí)現(xiàn)了概念上的服務(wù)器端闪盔,離實(shí)際的服務(wù)器端程序結(jié)構(gòu)距離還很遙遠(yuǎn)弯院,如果需要讓服務(wù)器端能夠?qū)嶋H使用,那么最需要解決的問(wèn)題就是——如何支持多個(gè)客戶端同時(shí)工作泪掀。
一個(gè)服務(wù)器端一般都需要同時(shí)為多個(gè)客戶端提供通訊听绳,如果需要同時(shí)支持多個(gè)客戶端,則必須使用前面介紹的線程的概念异赫。簡(jiǎn)單來(lái)說(shuō)椅挣,也就是當(dāng)服務(wù)器端接收到一個(gè)連接時(shí)头岔,啟動(dòng)一個(gè)專門的線程處理和該客戶端的通訊。
按照這個(gè)思路改寫的服務(wù)端示例程序?qū)⒂蓛蓚€(gè)部分組成鼠证,MulThreadSocketServer類實(shí)現(xiàn)服務(wù)器端控制峡竣,實(shí)現(xiàn)接收客戶端連接,然后開啟專門的邏輯線程處理該連接量九,LogicThread類實(shí)現(xiàn)對(duì)于一個(gè)客戶端連接的邏輯處理适掰,將處理的邏輯放置在該類的run方法中。該示例的代碼實(shí)現(xiàn)為:

package tcp;

import java.net.ServerSocket;
import java.net.Socket;
/**
 * 支持多客戶端的服務(wù)器端實(shí)現(xiàn)
 */
public class MulThreadSocketServer {
         public static void main(String[] args) {
                   ServerSocket serverSocket = null;
                   Socket socket = null;
                   //監(jiān)聽端口號(hào)
                   int port = 10000;
                   try {
                            //建立連接
                            serverSocket = new ServerSocket(port);
                            System.out.println("服務(wù)器已啟動(dòng):");
                            while(true){
                                     //獲得連接
                                     socket = serverSocket.accept();
                                     //啟動(dòng)線程
                                     new LogicThread(socket);
                            }
                   } catch (Exception e) {
                            e.printStackTrace();
                   }finally{
                            try{
                                     //關(guān)閉連接
                                     serverSocket.close();
                            }catch(Exception e){}
                   }
         }
}

在該示例代碼中荠列,實(shí)現(xiàn)了一個(gè)while形式的死循環(huán)类浪,由于accept方法是阻塞方法,所以當(dāng)客戶端連接未到達(dá)時(shí)肌似,將阻塞該程序的執(zhí)行费就,當(dāng)客戶端到達(dá)時(shí)接收該連接,并啟動(dòng)一個(gè)新的LogicThread線程處理該連接川队,然后按照循環(huán)的執(zhí)行流程力细,繼續(xù)等待下一個(gè)客戶端連接。這樣當(dāng)任何一個(gè)客戶端連接到達(dá)時(shí)呼寸,都開啟一個(gè)專門的線程處理艳汽,通過(guò)多個(gè)線程支持多個(gè)客戶端同時(shí)處理猴贰。
  下面再看一下LogicThread線程類的源代碼實(shí)現(xiàn):

package tcp;

import java.io.*;
import java.net.*;
/**
 * 服務(wù)器端邏輯線程
 */
public class LogicThread extends Thread {
         Socket socket;
         InputStream is;
         OutputStream os;
         public LogicThread(Socket socket){
                   this.socket = socket;
                   start(); //啟動(dòng)線程
         }

         public void run(){
                   byte[] b = new byte[1024];
                   try{
                            //初始化流
                            os = socket.getOutputStream();
                            is = socket.getInputStream();
                            for(int i = 0;i < 3;i++){
                                     //讀取數(shù)據(jù)
                                     int n = is.read(b);
                                     //邏輯處理
                                     byte[] response = logic(b,0,n);
                                     //反饋數(shù)據(jù)
                                     os.write(response);
                            }
                   }catch(Exception e){
                            e.printStackTrace();
                   }finally{
                            close();
                   }
         }

         /**
          * 關(guān)閉流和連接
          */
         private void close(){
                   try{
                            //關(guān)閉流和連接
                            os.close();
                            is.close();
                            socket.close();
                   }catch(Exception e){}
         }

         /**
          * 邏輯處理方法,實(shí)現(xiàn)echo邏輯
          * @param b 客戶端發(fā)送數(shù)據(jù)緩沖區(qū)
          * @param off 起始下標(biāo)
          * @param len 有效數(shù)據(jù)長(zhǎng)度
          * @return
          */
         private byte[] logic(byte[] b,int off,int len){
                   byte[] response = new byte[len];
                   //將有效數(shù)據(jù)拷貝到數(shù)組response中
                   System.arraycopy(b, 0, response, 0, len);
                   return response;
         }
}

在該示例代碼中对雪,每次使用一個(gè)連接對(duì)象構(gòu)造該線程,該連接對(duì)象就是該線程需要處理的連接米绕,在線程構(gòu)造完成以后瑟捣,該線程就被啟動(dòng)起來(lái)了,然后在run方法內(nèi)部對(duì)客戶端連接進(jìn)行處理栅干,數(shù)據(jù)交換的邏輯和前面的示例代碼一致迈套,只是這里將接收到客戶端發(fā)送過(guò)來(lái)的數(shù)據(jù)并進(jìn)行處理的邏輯封裝成了logic方法,按照前面介紹的IO編程的內(nèi)容碱鳞,客戶端發(fā)送過(guò)來(lái)的內(nèi)容存儲(chǔ)在數(shù)組b的起始下標(biāo)為0桑李,長(zhǎng)度為n個(gè)中,這些數(shù)據(jù)是客戶端發(fā)送過(guò)來(lái)的有效數(shù)據(jù)窿给,將有效的數(shù)據(jù)傳遞給logic方法贵白,logic方法實(shí)現(xiàn)的是echo服務(wù)的邏輯,也就是將客戶端發(fā)送的有效數(shù)據(jù)形成以后新的response數(shù)組崩泡,并作為返回值反饋禁荒。
在線程中將logic方法的返回值反饋給客戶端,這樣就完成了服務(wù)器端的邏輯處理模擬角撞,其他的實(shí)現(xiàn)和前面的介紹類似呛伴,這里就不在重復(fù)了勃痴。
這里的示例還只是基礎(chǔ)的服務(wù)器端實(shí)現(xiàn),在實(shí)際的服務(wù)器端實(shí)現(xiàn)中热康,由于硬件和端口數(shù)的限制沛申,所以不能無(wú)限制的創(chuàng)建線程對(duì)象,而且頻繁的創(chuàng)建線程對(duì)象效率也比較低姐军,所以程序中都實(shí)現(xiàn)了線程池來(lái)提高程序的執(zhí)行效率污它。
這里簡(jiǎn)單介紹一下線程池的概念,線程池(Thread pool)是池技術(shù)的一種庶弃,就是在程序啟動(dòng)時(shí)首先把需要個(gè)數(shù)的線程對(duì)象創(chuàng)建好衫贬,例如創(chuàng)建5000個(gè)線程對(duì)象,然后當(dāng)客戶端連接到達(dá)時(shí)從池中取出一個(gè)已經(jīng)創(chuàng)建完成的線程對(duì)象使用即可歇攻。當(dāng)客戶端連接關(guān)閉以后固惯,將該線程對(duì)象重新放入到線程池中供其它的客戶端重復(fù)使用,這樣可以提高程序的執(zhí)行速度缴守,優(yōu)化程序?qū)τ趦?nèi)存的占用等葬毫。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市屡穗,隨后出現(xiàn)的幾起案子贴捡,更是在濱河造成了極大的恐慌,老刑警劉巖村砂,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烂斋,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡础废,警方通過(guò)查閱死者的電腦和手機(jī)汛骂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)评腺,“玉大人帘瞭,你說(shuō)我怎么就攤上這事≥锛ィ” “怎么了蝶念?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)芋绸。 經(jīng)常有香客問(wèn)我媒殉,道長(zhǎng),這世上最難降的妖魔是什么侥钳? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任适袜,我火速辦了婚禮,結(jié)果婚禮上舷夺,老公的妹妹穿的比我還像新娘苦酱。我一直安慰自己售貌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布疫萤。 她就那樣靜靜地躺著颂跨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扯饶。 梳的紋絲不亂的頭發(fā)上恒削,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音尾序,去河邊找鬼钓丰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛每币,可吹牛的內(nèi)容都是我干的携丁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼兰怠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼梦鉴!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起揭保,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤肥橙,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后秸侣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體存筏,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年塔次,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了方篮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡励负,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出匕得,到底是詐尸還是另有隱情继榆,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布汁掠,位于F島的核電站略吨,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏考阱。R本人自食惡果不足惜翠忠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望乞榨。 院中可真熱鬧秽之,春花似錦当娱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至河质,卻和暖如春冀惭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背掀鹅。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工散休, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人乐尊。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓溃槐,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親科吭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子昏滴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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

  • 網(wǎng)絡(luò)編程 網(wǎng)絡(luò)編程對(duì)于很多的初學(xué)者來(lái)說(shuō),都是很向往的一種編程技能对人,但是很多的初學(xué)者卻因?yàn)楹荛L(zhǎng)一段時(shí)間無(wú)法進(jìn)入網(wǎng)絡(luò)編...
    程序員歐陽(yáng)閱讀 2,006評(píng)論 1 37
  • 計(jì)算機(jī)網(wǎng)絡(luò)概述 網(wǎng)絡(luò)編程的實(shí)質(zhì)就是兩個(gè)(或多個(gè))設(shè)備(例如計(jì)算機(jī))之間的數(shù)據(jù)傳輸谣殊。 按照計(jì)算機(jī)網(wǎng)絡(luò)的定義,通過(guò)一定...
    蛋炒飯_By閱讀 1,210評(píng)論 0 10
  • Socket編程 1基礎(chǔ)知識(shí) 協(xié)議 端口號(hào)(辨別不同應(yīng)用) TCP/IP協(xié)議 是目前世界上應(yīng)用最廣泛的協(xié)議是以TC...
    __豆約翰__閱讀 1,088評(píng)論 0 3
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理牺弄,服務(wù)發(fā)現(xiàn)姻几,斷路器,智...
    卡卡羅2017閱讀 134,601評(píng)論 18 139
  • 姓名:張亞妮 公司:慈星股份 【知~學(xué)習(xí)】 《六項(xiàng)精進(jìn)》大綱背誦1遍 共44遍 《六項(xiàng)精進(jìn)》通篇誦讀1遍 共...
    宇嘟嘟閱讀 112評(píng)論 0 0