日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JAVA的网络编程【转】

發(fā)布時(shí)間:2024/1/17 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JAVA的网络编程【转】 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

轉(zhuǎn)自?http://www.cnblogs.com/springcsc/archive/2009/12/03/1616413.html

網(wǎng)絡(luò)編程

?????????網(wǎng)絡(luò)編程對于很多的初學(xué)者來說,都是很向往的一種編程技能,但是很多的初學(xué)者卻因?yàn)楹荛L一段時(shí)間無法進(jìn)入網(wǎng)絡(luò)編程的大門而放棄了對于該部分技術(shù)的學(xué)習(xí)。

?????????在 學(xué)習(xí)網(wǎng)絡(luò)編程以前,很多初學(xué)者可能覺得網(wǎng)絡(luò)編程是比較復(fù)雜的系統(tǒng)工程,需要了解很多和網(wǎng)絡(luò)相關(guān)的基礎(chǔ)知識,其實(shí)這些都不是很必需的。首先來問一個(gè)問題:你 會打手機(jī)嗎?很多人可能說肯定會啊,不就是按按電話號碼,撥打電話嘛,很簡單的事情啊!其實(shí)初學(xué)者如果入門網(wǎng)絡(luò)編程的話也可以做到這么簡單!

?????????網(wǎng)絡(luò)編程就是在兩個(gè)或兩個(gè)以上的設(shè)備(例如計(jì)算機(jī))之間傳輸數(shù)據(jù)。程序員所作的事情就是把數(shù)據(jù)發(fā)送到指定的位置,或者接收到指定的數(shù)據(jù),這個(gè)就是狹義的網(wǎng)絡(luò)編程范疇。在發(fā)送和接收數(shù)據(jù)時(shí),大部分的程序設(shè)計(jì)語言都設(shè)計(jì)了專門的API實(shí)現(xiàn)這些功能,程序員只需要調(diào)用即可。所以,基礎(chǔ)的網(wǎng)絡(luò)編程可以和打電話一樣簡單。

?????????下面就開始Java語言的網(wǎng)絡(luò)編程技術(shù)學(xué)習(xí)吧。

13.1?網(wǎng)絡(luò)概述

?????????網(wǎng) 絡(luò)編程技術(shù)是當(dāng)前一種主流的編程技術(shù),隨著聯(lián)網(wǎng)趨勢的逐步增強(qiáng)以及網(wǎng)絡(luò)應(yīng)用程序的大量出現(xiàn),所以在實(shí)際的開發(fā)中網(wǎng)絡(luò)編程技術(shù)獲得了大量的使用。本章中以淺 顯的基礎(chǔ)知識說明和實(shí)際的案例使廣大初學(xué)者能夠進(jìn)入網(wǎng)絡(luò)編程技術(shù)的大門,至于以后的實(shí)際修行就要閱讀進(jìn)階的書籍以及進(jìn)行大量的實(shí)際練習(xí)。

13.1.1?計(jì)算機(jī)網(wǎng)絡(luò)概述

?????????網(wǎng)絡(luò)編程的實(shí)質(zhì)就是兩個(gè)(或多個(gè))設(shè)備(例如計(jì)算機(jī))之間的數(shù)據(jù)傳輸。

?????????按照計(jì)算機(jī)網(wǎng)絡(luò)的定義,通過一定的物理設(shè)備將處于不同位置的計(jì)算機(jī)連接起來組成的網(wǎng)絡(luò),這個(gè)網(wǎng)絡(luò)中包含的設(shè)備有:計(jì)算機(jī)、路由器、交換機(jī)等等。

?????????其實(shí)從軟件編程的角度來說,對于物理設(shè)備的理解不需要很深刻,就像你打電話時(shí)不需要很熟悉通信網(wǎng)絡(luò)的底層實(shí)現(xiàn)是一樣的,但是當(dāng)深入到網(wǎng)絡(luò)編程的底層時(shí),這些基礎(chǔ)知識是必須要補(bǔ)的。

?????????路由器和交換機(jī)組成了核心的計(jì)算機(jī)網(wǎng)絡(luò),計(jì)算機(jī)只是這個(gè)網(wǎng)絡(luò)上的節(jié)點(diǎn)以及控制等,通過光纖、網(wǎng)線等連接將設(shè)備連接起來,從而形成了一張巨大的計(jì)算機(jī)網(wǎng)絡(luò)。

?????????網(wǎng)絡(luò)最主要的優(yōu)勢在于共享:共享設(shè)備和數(shù)據(jù),現(xiàn)在共享設(shè)備最常見的是打印機(jī),一個(gè)公司一般一個(gè)打印機(jī)即可,共享數(shù)據(jù)就是將大量的數(shù)據(jù)存儲在一組機(jī)器中,其它的計(jì)算機(jī)通過網(wǎng)絡(luò)訪問這些數(shù)據(jù),例如網(wǎng)站、銀行服務(wù)器等等。

?????????如果需要了解更多的網(wǎng)絡(luò)硬件基礎(chǔ)知識,可以閱讀《計(jì)算機(jī)網(wǎng)絡(luò)》教材,對于基礎(chǔ)進(jìn)行強(qiáng)化,這個(gè)在基礎(chǔ)學(xué)習(xí)階段不是必須的,但是如果想在網(wǎng)絡(luò)編程領(lǐng)域有所造詣,則是一個(gè)必須的基本功。

?????????對于網(wǎng)絡(luò)編程來說,最主要的是計(jì)算機(jī)和計(jì)算機(jī)之間的通信,這樣首要的問題就是如何找到網(wǎng)絡(luò)上的計(jì)算機(jī)呢?這就需要了解IP地址的概念。

?????????為了能夠方便的識別網(wǎng)絡(luò)上的每個(gè)設(shè)備,網(wǎng)絡(luò)中的每個(gè)設(shè)備都會有一個(gè)唯一的數(shù)字標(biāo)識,這個(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撥號上網(wǎng)的寬帶用戶,無論以何種方式獲得或是否是固定的,每個(gè)計(jì)算機(jī)在聯(lián)網(wǎng)以后都擁有一個(gè)唯一的合法IP地址,就像每個(gè)手機(jī)號碼一樣。

?????????但是由于IP地址不容易記憶,所以為了方便記憶,有創(chuàng)造了另外一個(gè)概念——域名(Domain Name),例如sohu.com等。一個(gè)IP地址可以對應(yīng)多個(gè)域名,一個(gè)域名只能對應(yīng)一個(gè)IP地址。域名的概念可以類比手機(jī)中的通訊簿,由于手機(jī)號碼不方便記憶,所以添加一個(gè)姓名標(biāo)識號碼,在實(shí)際撥打電話時(shí)可以選擇該姓名,然后撥打即可。

?????????在網(wǎng)絡(luò)中傳輸?shù)臄?shù)據(jù),全部是以IP地址作為地址標(biāo)識,所以在實(shí)際傳輸數(shù)據(jù)以前需要將域名轉(zhuǎn)換為IP地址,實(shí)現(xiàn)這種功能的服務(wù)器稱之為DNS服務(wù)器,也就是通俗的說法叫做域名解析。例如當(dāng)用戶在瀏覽器輸入域名時(shí),瀏覽器首先請求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í),只能通過IP地址訪問該設(shè)備。所以IP地址的使用要比域名通用一些。

???????? IP地址和域名很好的解決了在網(wǎng)絡(luò)中找到一個(gè)計(jì)算機(jī)的問題,但是為了讓一個(gè)計(jì)算機(jī)可以同時(shí)運(yùn)行多個(gè)網(wǎng)絡(luò)程序,就引入了另外一個(gè)概念——端口(port)。

?????????在介紹端口的概念以前,首先來看一個(gè)例子,一般一個(gè)公司前臺會有一個(gè)電話,每個(gè)員工會有一個(gè)分機(jī),這樣如果需要找到這個(gè)員工的話,需要首先撥打前臺總機(jī),然后轉(zhuǎn)該分機(jī)號即可。這樣減少了公司的開銷,也方便了每個(gè)員工。在該示例中前臺總機(jī)的電話號碼就相當(dāng)于IP地址,而每個(gè)員工的分機(jī)號就相當(dāng)于端口。

?????????有了端口的概念以后,在同一個(gè)計(jì)算機(jī)中每個(gè)程序?qū)?yīng)唯一的端口,這樣一個(gè)計(jì)算機(jī)上就可以通過端口區(qū)分發(fā)送給每個(gè)端口的數(shù)據(jù)了,換句話說,也就是一個(gè)計(jì)算機(jī)上可以并發(fā)運(yùn)行多個(gè)網(wǎng)絡(luò)程序,而不會在互相之間產(chǎn)生干擾。

?????????在硬件上規(guī)定,端口的號碼必須位于0-65535之間,每個(gè)端口唯一的對應(yīng)一個(gè)網(wǎng)絡(luò)程序,一個(gè)網(wǎng)絡(luò)程序可以使用多個(gè)端口。這樣一個(gè)網(wǎng)絡(luò)程序運(yùn)行在一臺計(jì)算上時(shí),不管是客戶端還是服務(wù)器,都是至少占用一個(gè)端口進(jìn)行網(wǎng)絡(luò)通訊。在接收數(shù)據(jù)時(shí),首先發(fā)送給對應(yīng)的計(jì)算機(jī),然后計(jì)算機(jī)根據(jù)端口把數(shù)據(jù)轉(zhuǎn)發(fā)給對應(yīng)的程序。

?????????有了IP地址和端口的概念以后,在進(jìn)行網(wǎng)絡(luò)通訊交換時(shí),就可以通過IP地址查找到該臺計(jì)算機(jī),然后通過端口標(biāo)識這臺計(jì)算機(jī)上的一個(gè)唯一的程序。這樣就可以進(jìn)行網(wǎng)絡(luò)數(shù)據(jù)的交換了。

?????????但是,進(jìn)行網(wǎng)絡(luò)編程時(shí),只有IP地址和端口的概念還是不夠的,下面就介紹一下基礎(chǔ)的網(wǎng)絡(luò)編程相關(guān)的軟件基礎(chǔ)知識。

13.1. 2?網(wǎng)絡(luò)編程概述

?????????按照前面的介紹,網(wǎng)絡(luò)編程就是兩個(gè)或多個(gè)設(shè)備之間的數(shù)據(jù)交換,其實(shí)更具體的說,網(wǎng)絡(luò)編程就是兩個(gè)或多個(gè)程序之間的數(shù)據(jù)交換,和普通的單機(jī)程序相比,網(wǎng)絡(luò)程序最大的不同就是需要交換數(shù)據(jù)的程序運(yùn)行在不同的計(jì)算機(jī)上,這樣就造成了數(shù)據(jù)交換的復(fù)雜。雖然通過IP地址和端口可以找到網(wǎng)絡(luò)上運(yùn)行的一個(gè)程序,但是如果需要進(jìn)行網(wǎng)絡(luò)編程,則還需要了解網(wǎng)絡(luò)通訊的過程。

?????????網(wǎng)絡(luò)通訊基于“請求-響應(yīng)”模型。為了理解這個(gè)模型,先來看一個(gè)例子,經(jīng)常看電視的人肯定見過審訊的場面吧,一般是這樣的:

???????????????????警察:姓名

???????????????????嫌疑犯:XXX

???????????????????警察:性別

???????????????????嫌疑犯:男

???????????????????警察:年齡

???????????????????嫌疑犯:29

???????????????????……

?????????在這個(gè)例子中,警察問一句,嫌疑犯回答一句,如果警察不問,則嫌疑犯保持沉默。這種一問一答的形式就是網(wǎng)絡(luò)中的“請求-響應(yīng)”模型。也就是通訊的一端發(fā)送數(shù)據(jù),另外一端反饋數(shù)據(jù),網(wǎng)絡(luò)通訊都基于該模型。

?????????在網(wǎng)絡(luò)通訊中,第一次主動(dòng)發(fā)起通訊的程序被稱作客戶端(Client)程序,簡稱客戶端,而在第一次通訊中等待連接的程序被稱作服務(wù)器端(Server)程序,簡稱服務(wù)器。一旦通訊建立,則客戶端和服務(wù)器端完全一樣,沒有本質(zhì)的區(qū)別。

?????????由此,網(wǎng)絡(luò)編程中的兩種程序就分別是客戶端和服務(wù)器端,例如QQ程序,每個(gè)QQ用戶安裝的都是QQ客戶端程序,而QQ服務(wù)器端程序則運(yùn)行在騰訊公司的機(jī)房中,為大量的QQ用戶提供服務(wù)。這種網(wǎng)絡(luò)編程的結(jié)構(gòu)被稱作客戶端/服務(wù)器結(jié)構(gòu),也叫做Client/Server結(jié)構(gòu),簡稱C/S結(jié)構(gòu)。

?????????使用C/S結(jié) 構(gòu)的程序,在開發(fā)時(shí)需要分別開發(fā)客戶端和服務(wù)器端,這種結(jié)構(gòu)的優(yōu)勢在于由于客戶端是專門開發(fā)的,所以根據(jù)需要實(shí)現(xiàn)各種效果,專業(yè)點(diǎn)說就是表現(xiàn)力豐富,而服 務(wù)器端也需要專門進(jìn)行開發(fā)。但是這種結(jié)構(gòu)也存在著很多不足,例如通用性差,幾乎不能通用等,也就是說一種程序的客戶端只能和對應(yīng)的服務(wù)器端通訊,而不能和 其它服務(wù)器端通訊,在實(shí)際維護(hù)時(shí),也需要維護(hù)專門的客戶端和服務(wù)器端,維護(hù)的壓力比較大。

?????????其實(shí)在運(yùn)行很多程序時(shí),沒有必要使用專用的客戶端,而需要使用通用的客戶端,例如瀏覽器,使用瀏覽器作為客戶端的結(jié)構(gòu)被稱作瀏覽器/服務(wù)器結(jié)構(gòu),也叫做Browser/Server結(jié)構(gòu),簡稱為B/S結(jié)構(gòu)。

?????????使用B/S結(jié)構(gòu)的程序,在開發(fā)時(shí)只需要開發(fā)服務(wù)器端即可,這種結(jié)構(gòu)的優(yōu)勢在于開發(fā)的壓力比較小,不需要維護(hù)客戶端。但是這種結(jié)構(gòu)也存在著很多不足,例如瀏覽器的限制比較大,表現(xiàn)力不強(qiáng),無法進(jìn)行系統(tǒng)級操作等。

?????????總之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)。

?????????另外簡單的介紹一下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)了該功能。

?????????最后再介紹一個(gè)網(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é)議。

?????????如 果沒有理解協(xié)議的概念,那么再舉一個(gè)例子,記得有個(gè)電影叫《永不消逝的電波》,講述的是地下黨通過電臺發(fā)送情報(bào)的故事,這里我們不探討電影的劇情,而只關(guān) 心電臺發(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ù)格式,這種對于網(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è)功能在幾乎所有的程序語言中都提供了封裝好的API進(jìn)行調(diào)用,最麻煩的內(nèi)容就是協(xié)議的設(shè)計(jì)以及協(xié)議的生產(chǎn)和解析,這個(gè)才是網(wǎng)絡(luò)編程中最核心的內(nèi)容。

?????????關(guān)于網(wǎng)絡(luò)編程的基礎(chǔ)知識,就介紹這里,深刻理解IP地址、端口和協(xié)議等概念,將會極大的有助于后續(xù)知識的學(xué)習(xí)。

13.1.3?網(wǎng)絡(luò)通訊方式

?????????在現(xiàn)有的網(wǎng)絡(luò)中,網(wǎng)絡(luò)通訊的方式主要有兩種:

1、?TCP(傳輸控制協(xié)議)方式

2、?UDP(用戶數(shù)據(jù)報(bào)協(xié)議)方式

為 了方便理解這兩種方式,還是先來看一個(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ā)送失敗,則客戶端會自動(dòng)重發(fā)該數(shù)據(jù)。而UDP方式就類似于發(fā)送短信,使用這種方式進(jìn)行網(wǎng)絡(luò)通訊時(shí),不需要建立專門的虛擬連接,傳輸也不是很可靠,如果發(fā)送失敗則客戶端無法獲得。

這兩種傳輸方式都是實(shí)際的網(wǎng)絡(luò)編程中進(jìn)行使用,重要的數(shù)據(jù)一般使用TCP方式進(jìn)行數(shù)據(jù)傳輸,而大量的非核心數(shù)據(jù)則都通過UDP方式進(jìn)行傳遞,在一些程序中甚至結(jié)合使用這兩種方式進(jìn)行數(shù)據(jù)的傳遞。

由于TCP需要建立專用的虛擬連接以及確認(rèn)傳輸是否正確,所以使用TCP方式的速度稍微慢一些,而且傳輸時(shí)產(chǎn)生的數(shù)據(jù)量要比UDP稍微大一些。

?????????關(guān)于網(wǎng)絡(luò)編程的基礎(chǔ)知識就介紹這么多,如果需要深入了解相關(guān)知識請閱讀專門的計(jì)算機(jī)網(wǎng)絡(luò)書籍,下面開始介紹Java語言中網(wǎng)絡(luò)編程的相關(guān)技術(shù)。

?

?

13.2?網(wǎng)絡(luò)編程技術(shù)

?????????前面介紹了網(wǎng)絡(luò)編程的相關(guān)基礎(chǔ)知識,初步建立了網(wǎng)絡(luò)編程的概念,但是實(shí)際學(xué)習(xí)網(wǎng)絡(luò)編程還必須使用某種程序設(shè)計(jì)語言進(jìn)行代碼實(shí)現(xiàn),下面就介紹一下網(wǎng)絡(luò)編程的代碼實(shí)現(xiàn)。

13.2.1?網(wǎng)絡(luò)編程步驟

?????????按照前面的基礎(chǔ)知識介紹,無論使用TCP方式還是UDP方式進(jìn)行網(wǎng)絡(luò)通訊,網(wǎng)絡(luò)編程都是由客戶端和服務(wù)器端組成。當(dāng)然,B/S結(jié)構(gòu)的編程中只需要實(shí)現(xiàn)服務(wù)器端即可。所以,下面介紹網(wǎng)絡(luò)編程的步驟時(shí),均以C/S結(jié)構(gòu)為基礎(chǔ)進(jìn)行介紹。

?????????說明:這里的步驟實(shí)現(xiàn)和語言無關(guān),也就是說,這個(gè)步驟適用于各種語言實(shí)現(xiàn),不局限于Java語言。

13.2.1.1?客戶端網(wǎng)絡(luò)編程步驟

?????????客戶端(Client)是指網(wǎng)絡(luò)編程中首先發(fā)起連接的程序,客戶端一般實(shí)現(xiàn)程序界面和基本邏輯實(shí)現(xiàn),在進(jìn)行實(shí)際的客戶端編程時(shí),無論客戶端復(fù)雜還是簡單,以及客戶端實(shí)現(xiàn)的方式,客戶端的編程主要由三個(gè)步驟實(shí)現(xiàn):

1、?建立網(wǎng)絡(luò)連接

客戶端網(wǎng)絡(luò)編程的第一步都是建立網(wǎng)絡(luò)連接。在建立網(wǎng)絡(luò)連接時(shí)需要指定連接到的服務(wù)器的IP地址和端口號,建立完成以后,會形成一條虛擬的連接,后續(xù)的操作就可以通過該連接實(shí)現(xiàn)數(shù)據(jù)交換了。

2、?交換數(shù)據(jù)

連接建立以后,就可以通過這個(gè)連接交換數(shù)據(jù)了。交換數(shù)據(jù)嚴(yán)格按照請求響應(yīng)模型進(jìn)行,由客戶端發(fā)送一個(gè)請求數(shù)據(jù)到服務(wù)器,服務(wù)器反饋一個(gè)響應(yīng)數(shù)據(jù)給客戶端,如果客戶端不發(fā)送請求則服務(wù)器端就不響應(yīng)。

根據(jù)邏輯需要,可以多次交換數(shù)據(jù),但是還是必須遵循請求響應(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會出現(xiàn)重復(fù),在進(jìn)行代碼組織時(shí),由于網(wǎng)絡(luò)編程是比較耗時(shí)的操作,所以一般開啟專門的現(xiàn)場進(jìn)行網(wǎng)絡(luò)通訊。

13.2.1.2?服務(wù)器端網(wǎng)絡(luò)編程步驟

?????????服務(wù)器端(Server)是指在網(wǎng)絡(luò)編程中被動(dòng)等待連接的程序,服務(wù)器端一般實(shí)現(xiàn)程序的核心邏輯以及數(shù)據(jù)存儲等核心功能。服務(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ù)器端和客戶端也通過該連接進(jìn)行數(shù)據(jù)交換。

一般在服務(wù)器端編程中,當(dāng)獲得連接時(shí),需要開啟專門的線程處理該連接,每個(gè)連接都由獨(dú)立的線程實(shí)現(xiàn)。

3、?交換數(shù)據(jù)

服務(wù)器端通過獲得的連接進(jìn)行數(shù)據(jù)交換。服務(wù)器端的數(shù)據(jù)交換步驟是首先接收客戶端發(fā)送過來的數(shù)據(jù),然后進(jìn)行邏輯處理,再把處理以后的結(jié)果數(shù)據(jù)發(fā)送給客戶端。簡單來說,就是先接收再發(fā)送,這個(gè)和客戶端的數(shù)據(jù)交換數(shù)序不同。

其實(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ān)閉服務(wù)器端使得服務(wù)器監(jiān)聽的端口以及占用的內(nè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)和該用戶的問題解決,當(dāng)另外一個(gè)用戶撥打10086時(shí),則轉(zhuǎn)接給另一個(gè)客服,實(shí)現(xiàn)問題解決,依次類推。

?????????在服務(wù)器端編程時(shí),10086這個(gè)電話號碼就類似于服務(wù)器端的端口號碼,每個(gè)用戶就相當(dāng)于一個(gè)客戶端程序,每個(gè)客服人員就相當(dāng)于服務(wù)器端啟動(dòng)的專門和客戶端連接的線程,每個(gè)線程都是獨(dú)立進(jìn)行交互的。

?????????這就是服務(wù)器端編程的模型,只是TCP方式是需要建立連接的,對于服務(wù)器端的壓力比較大,而UDP是不需要建立連接的,對于服務(wù)器端的壓力比較小罷了。

13.2.1.3?小結(jié)

?????????總之,無論使用任何語言,任何方式進(jìn)行基礎(chǔ)的網(wǎng)絡(luò)編程,都必須遵循固定的步驟進(jìn)行操作,在熟悉了這些步驟以后,可以根據(jù)需要進(jìn)行邏輯上的處理,但是還是必須遵循固定的步驟進(jìn)行。

?????????其實(shí),基礎(chǔ)的網(wǎng)絡(luò)編程本身不難,也不需要很多的基礎(chǔ)網(wǎng)絡(luò)知識,只是由于編程的基礎(chǔ)功能都已經(jīng)由API實(shí)現(xiàn),而且需要按照固定的步驟進(jìn)行,所以在入門時(shí)有一定的門檻,希望下面的內(nèi)容能夠?qū)⒛憧焖俚膸刖W(wǎng)絡(luò)編程技術(shù)的大門。

13.2.2 Java網(wǎng)絡(luò)編程技術(shù)

???????? Java語言是在網(wǎng)絡(luò)環(huán)境下誕生的,所以Java語言雖然不能說是對于網(wǎng)絡(luò)編程的支持最好的語言,但是必須說是一種對于網(wǎng)絡(luò)編程提供良好支持的語言,使用Java語言進(jìn)行網(wǎng)絡(luò)編程將是一件比較輕松的工作。

?????????和網(wǎng)絡(luò)編程有關(guān)的基本API位于java.net包中,該包中包含了基本的網(wǎng)絡(luò)編程實(shí)現(xiàn),該包是網(wǎng)絡(luò)編程的基礎(chǔ)。該包中既包含基礎(chǔ)的網(wǎng)絡(luò)編程類,也包含封裝后的專門處理WEB相關(guān)的處理類。在本章中,將只介紹基礎(chǔ)的網(wǎng)絡(luò)編程類。

?????????首先來介紹一個(gè)基礎(chǔ)的網(wǎng)絡(luò)類——InetAddress類。該類的功能是代表一個(gè)IP地址,并且將IP地址和域名相關(guān)的操作方法包含在該類的內(nèi)部。

?????????關(guān)于該類的使用,下面通過一個(gè)基礎(chǔ)的代碼示例演示該類的使用,代碼如下:

?????????????????? package inetaddressdemo;

import java.net.*;

/**

?*?演示InetAddress類的基本使用

?*/

public class InetAddressDemo {

???????? public static void main(String[] args) {

?????????????????? try{

??????????????????????????? //使用域名創(chuàng)建對象

??????????????????????????? InetAddress inet1 = InetAddress.getByName("www.163.com");

??????????????????????????? System.out.println(inet1);

??????????????????????????? //使用IP創(chuàng)建對象

??????????????????????????? InetAddress inet2 = InetAddress.getByName("127.0.0.1");

??????????????????????????? System.out.println(inet2);

??????????????????????????? //獲得本機(jī)地址對象

??????????????????????????? InetAddress inet3 = InetAddress.getLocalHost();

??????????????????????????? System.out.println(inet3);

??????????????????????????? //獲得對象中存儲的域名

??????????????????????????? String host = inet3.getHostName();

??????????????????????????? System.out.println("域名:" + host);

??????????????????????????? //獲得對象中存儲的IP

??????????????????????????? String ip = inet3.getHostAddress();

??????????????????????????? System.out.println("IP:" + ip);

?????????????????? }catch(Exception e){}

???????? }

}

?????????在該示例代碼中,演示了InetAddress類的基本使用,并使用了該類中的幾個(gè)常用方法,該代碼的執(zhí)行結(jié)果是:

???????????????????www.163.com/220.181.28.50

/127.0.0.1

chen/192.168.1.100

域名:chen

IP:192.168.1.100

?????????說明:由于該代碼中包含一個(gè)互聯(lián)網(wǎng)的網(wǎng)址,所以運(yùn)行該程序時(shí)需要聯(lián)網(wǎng),否則將產(chǎn)生異常。

?????????在后續(xù)的使用中,經(jīng)常包含需要使用InetAddress對象代表IP地址的構(gòu)造方法,當(dāng)然,該類的使用不是必須的,也可以使用字符串來代表IP地址進(jìn)行實(shí)現(xiàn)。

13.2.3 TCP編程

?????????按照前面的介紹,網(wǎng)絡(luò)通訊的方式有TCP和UDP兩種,其中TCP方式的網(wǎng)絡(luò)通訊是指在通訊的過程中保持連接,有點(diǎn)類似于打電話,只需要撥打一次號碼(建立一次網(wǎng)絡(luò)連接),就可以多次通話(多次傳輸數(shù)據(jù))。這樣方式在實(shí)際的網(wǎng)絡(luò)編程中,由于傳輸可靠,類似于打電話,如果甲給乙打電話,乙說沒有聽清楚讓甲重復(fù)一遍,直到乙聽清楚為止,實(shí)際的網(wǎng)絡(luò)傳輸也是這樣,如果發(fā)送的一方發(fā)送的數(shù)據(jù)接收方覺得有問題,則網(wǎng)絡(luò)底層會自動(dòng)要求發(fā)送方重發(fā),直到接收方收到為止。

?????????在Java語言中,對于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地址和端口號碼就可以建立連接了。正是由于這種高度的封裝,一方面簡化了Java語言網(wǎng)絡(luò)編程的難度,另外也使得使用Java語言進(jìn)行網(wǎng)絡(luò)編程時(shí)無法深入到網(wǎng)絡(luò)的底層,所以使用Java語言進(jìn)行網(wǎng)絡(luò)底層系統(tǒng)編程很困難,具體點(diǎn)說,Java語言無法實(shí)現(xiàn)底層的網(wǎng)絡(luò)嗅探以及獲得IP包結(jié)構(gòu)等信息。但是由于Java語言的網(wǎng)絡(luò)編程比較簡單,所以還是獲得了廣泛的使用。

?????????在使用TCP方式進(jìn)行網(wǎng)絡(luò)編程時(shí),需要按照前面介紹的網(wǎng)絡(luò)編程的步驟進(jìn)行,下面分別介紹一下在Java語言中客戶端和服務(wù)器端的實(shí)現(xiàn)步驟。

在客戶端網(wǎng)絡(luò)編程中,首先需要建立連接,在Java API中以java.net.Socket類的對象代表網(wǎng)絡(luò)連接,所以建立客戶端網(wǎng)絡(luò)連接,也就是創(chuàng)建Socket類型的對象,該對象代表網(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號端口,而socket2實(shí)現(xiàn)的是連接到域名是www.sohu.com的計(jì)算機(jī)的80號端口,至于底層網(wǎng)絡(luò)如何實(shí)現(xiàn)建立連接,對于程序員來說是完全透明的。如果建立連接時(shí),本機(jī)網(wǎng)絡(luò)不通,或服務(wù)器端程序未開啟,則會拋出異常。

連接一旦建立,則完成了客戶端編程的第一步,緊接著的步驟就是按照“請求-響應(yīng)”模型進(jìn)行網(wǎng)絡(luò)數(shù)據(jù)交換,在Java語言中,數(shù)據(jù)傳輸功能由Java IO實(shí)現(xiàn),也就是說只需要從連接中獲得輸入流和輸出流即可,然后將需要發(fā)送的數(shù)據(jù)寫入連接對象的輸出流中,在發(fā)送完成以后從輸入流中讀取數(shù)據(jù)即可。示例代碼如下:

???????? OutputStream os = socket1.getOutputStream();?//獲得輸出流

???????? InputStream is = socket1.getInputStream();???? //獲得輸入流

上面的代碼中,分別從socket1這個(gè)連接對象獲得了輸出流和輸入流對象,在整個(gè)網(wǎng)絡(luò)編程中,后續(xù)的數(shù)據(jù)交換就變成了IO操作,也就是遵循“請求-響應(yīng)”模型的規(guī)定,先向輸出流中寫入數(shù)據(jù),這些數(shù)據(jù)會被系統(tǒng)發(fā)送出去,然后在從輸入流中讀取服務(wù)器端的反饋信息,這樣就完成了一次數(shù)據(jù)交換過程,當(dāng)然這個(gè)數(shù)據(jù)交換過程可以多次進(jìn)行。

這里獲得的只是最基本的輸出流和輸入流對象,還可以根據(jù)前面學(xué)習(xí)到的IO知識,使用流的嵌套將這些獲得到的基本流對象轉(zhuǎn)換成需要的裝飾流對象,從而方便數(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è)簡單的網(wǎng)絡(luò)客戶端程序示例,該程序的作用是向服務(wù)器端發(fā)送一個(gè)字符串“Hello”,并將服務(wù)器端的反饋顯示到控制臺,數(shù)據(jù)交換只進(jìn)行一次,當(dāng)數(shù)據(jù)交換進(jìn)行完成以后關(guān)閉網(wǎng)絡(luò)連接,程序結(jié)束。實(shí)現(xiàn)的代碼如下:

package tcp;

import java.io.*;

import java.net.*;

/**

?*?簡單的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ù)器端端口號

?????????????????? 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,端口號碼為10000的TCP類型的網(wǎng)絡(luò)連接,然后獲得連接的輸出流對象,將需要發(fā)送的字符串“Hello”轉(zhuǎn)換為byte數(shù)組寫入到輸出流中,由系統(tǒng)自動(dòng)完成將輸出流中的數(shù)據(jù)發(fā)送出去,如果需要強(qiáng)制發(fā)送,可以調(diào)用輸出流對象中的flush方法實(shí)現(xiàn)。在數(shù)據(jù)發(fā)送出去以后,從連接對象的輸入流中讀取服務(wù)器端的反饋信息,讀取時(shí)可以使用IO中的各種讀取方法進(jìn)行讀取,這里使用最簡單的方法進(jìn)行讀取,從輸入流中讀取到的內(nèi)容就是服務(wù)器端的反饋,并將讀取到的內(nèi)容在客戶端的控制臺進(jìn)行輸出,最后依次關(guān)閉打開的流對象和網(wǎng)絡(luò)連接對象。

這是一個(gè)簡單的功能示例,在該示例中演示了TCP類型的網(wǎng)絡(luò)客戶端基本方法的使用,該代碼只起演示目的,還無法達(dá)到實(shí)用的級別。

如果需要在控制臺下面編譯和運(yùn)行該代碼,需要首先在控制臺下切換到源代碼所在的目錄,然后依次輸入編譯和運(yùn)行命令:

???????? javac –d . SimpleSocketClient.java

???????? java tcp.SimpleSocketClient

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

?????????服務(wù)器反饋:Hello

介紹完一個(gè)簡單的客戶端編程的示例,下面接著介紹一下TCP類型的服務(wù)器端的編寫。首先需要說明的是,客戶端的步驟和服務(wù)器端的編寫步驟不同,所以在學(xué)習(xí)服務(wù)器端編程時(shí)注意不要和客戶端混淆起來。

在服務(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號端口,如果在執(zhí)行該代碼時(shí),10000號端口已經(jīng)被別的程序占用,那么將拋出異常。否則將實(shí)現(xiàn)監(jiān)聽。

服務(wù)器端編程的第二個(gè)步驟是獲得連接。該步驟的作用是當(dāng)有客戶端連接到達(dá)時(shí),建立一個(gè)和客戶端連接對應(yīng)的Socket連 接對象,從而釋放客戶端連接對于服務(wù)器端端口的占用。實(shí)現(xiàn)功能就像公司的前臺一樣,當(dāng)一個(gè)客戶到達(dá)公司時(shí),會告訴前臺我找某某某,然后前臺就通知某某某, 然后就可以繼續(xù)接待其它客戶了。通過獲得連接,使得客戶端的連接在服務(wù)器端獲得了保持,另外使得服務(wù)器端的端口釋放出來,可以繼續(xù)等待其它的客戶端連接。 實(shí)現(xiàn)獲得連接的代碼是:

???????? Socket socket = ss.accept();

該代碼實(shí)現(xiàn)的功能是獲得當(dāng)前連接到服務(wù)器端的客戶端連接。需要說明的是accept和前面IO部分介紹的read方法一樣,都是一個(gè)阻塞方法,也就是當(dāng)無連接時(shí),該方法將阻塞程序的執(zhí)行,直到連接到達(dá)時(shí)才執(zhí)行該行代碼。另外獲得的連接會在服務(wù)器端的該端口注冊,這樣以后就可以通過在服務(wù)器端的注冊信息直接通信,而注冊以后服務(wù)器端的端口就被釋放出來,又可以繼續(xù)接受其它的連接了。

連接獲得以后,后續(xù)的編程就和客戶端的網(wǎng)絡(luò)編程類似了,這里獲得的Socket類型的連接就和客戶端的網(wǎng)絡(luò)連接一樣了,只是服務(wù)器端需要首先讀取發(fā)送過來的數(shù)據(jù),然后進(jìn)行邏輯處理以后再發(fā)送給客戶端,也就是交換數(shù)據(jù)的順序和客戶端交換數(shù)據(jù)的步驟剛好相反。這部分的內(nèi)容和客戶端很類似,所以就不重復(fù)了,如果還不熟悉,可以參看下面的示例代碼。

最后,在服務(wù)器端通信完成以后,關(guān)閉服務(wù)器端連接。實(shí)現(xiàn)的代碼為:

???????? ss.close();

這就是基本的TCP類型的服務(wù)器端編程步驟。下面以一個(gè)簡單的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)聽端口號

?????????????????? 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號端口的服務(wù)器端Socket連接,然后獲得客戶端發(fā)送過來的連接,如果有連接到達(dá)時(shí),讀取連接中發(fā)送過來的內(nèi)容,并將發(fā)送的內(nèi)容在控制臺進(jìn)行輸出,輸出完成以后將客戶端發(fā)送的內(nèi)容再反饋給客戶端。最后關(guān)閉流和連接對象,結(jié)束程序。

在控制臺下面編譯和運(yùn)行該程序的命令和客戶端部分的類似。

這樣,就以一個(gè)很簡單的示例演示了TCP類型的網(wǎng)絡(luò)編程在Java語言中的基本實(shí)現(xiàn),這個(gè)示例只是演示了網(wǎng)絡(luò)編程的基本步驟以及各個(gè)功能方法的基本使用,只是為網(wǎng)絡(luò)編程打下了一個(gè)基礎(chǔ),下面將就幾個(gè)問題來深入介紹網(wǎng)絡(luò)編程深層次的一些知識。

?為了一步一步的掌握網(wǎng)絡(luò)編程,下面再研究網(wǎng)絡(luò)編程中的兩個(gè)基本問題,通過解決這兩個(gè)問題將對網(wǎng)絡(luò)編程的認(rèn)識深入一層。

1、如何復(fù)用Socket連接?

在前面的示例中,客戶端中建立了一次連接,只發(fā)送一次數(shù)據(jù)就關(guān)閉了,這就相當(dāng)于撥打電話時(shí),電話打通了只對話一次就關(guān)閉了,其實(shí)更加常用的應(yīng)該是撥通一次電話以后多次對話,這就是復(fù)用客戶端連接。

那 么如何實(shí)現(xiàn)建立一次連接,進(jìn)行多次數(shù)據(jù)交換呢?其實(shí)很簡單,建立連接以后,將數(shù)據(jù)交換的邏輯寫到一個(gè)循環(huán)中就可以了。這樣只要循環(huán)不結(jié)束則連接就不會被關(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ù)器端端口號

?????????????????? 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ù)器端是對話一次數(shù)據(jù)以后就關(guān)閉了連接,如果服務(wù)器端程序關(guān)閉了,客戶端繼續(xù)發(fā)送數(shù)據(jù)肯定會出現(xiàn)異常,這就是出現(xià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)聽端口號

?????????????????? 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次,這樣代碼雖然比較簡單,但是通用性比較差。

???????????????????以該服務(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)出了“請求-響應(yīng)”模型,也就是在客戶端發(fā)起連接以后,首先發(fā)送字符串“First”給服務(wù)器端,服務(wù)器端輸出客戶端發(fā)送的內(nèi)容“First”,然后將客戶端發(fā)送的內(nèi)容再反饋給客戶端,這樣客戶端也輸出服務(wù)器反饋“First”,這樣就完成了客戶端和服務(wù)器端的一次對話,緊接著客戶端發(fā)送“Second”給服務(wù)器端,服務(wù)端輸出“Second”,然后將“Second”再反饋給客戶端,客戶端再輸出“Second”,從而完成第二次會話,第三次會話的過程和這個(gè)一樣。在這個(gè)過程中,每次都是客戶端程序首先發(fā)送數(shù)據(jù)給服務(wù)器端,服務(wù)器接收數(shù)據(jù)以后,將結(jié)果反饋給客戶端,客戶端接收到服務(wù)器端的反饋,從而完成一次通訊過程。

在該示例中,雖然解決了多次發(fā)送的問題,但是客戶端和服務(wù)器端的次數(shù)控制還不夠靈活,如果客戶端的次數(shù)不固定怎么辦呢?是否可以使用某個(gè)特殊的字符串,例如quit,表示客戶端退出呢,這就涉及到網(wǎng)絡(luò)協(xié)議的內(nèi)容了,會在后續(xù)的網(wǎng)絡(luò)應(yīng)用示例部分詳細(xì)介紹。下面開始介紹另外一個(gè)網(wǎng)絡(luò)編程的突出問題。

2、如何使服務(wù)器端支持多個(gè)客戶端同時(shí)工作?

?????????前面介紹的服務(wù)器端程序,只是實(shí)現(xiàn)了概念上的服務(wù)器端,離實(shí)際的服務(wù)器端程序結(jié)構(gòu)距離還很遙遠(yuǎn),如果需要讓服務(wù)器端能夠?qū)嶋H使用,那么最需要解決的問題就是——如何支持多個(gè)客戶端同時(shí)工作。

?????????一個(gè)服務(wù)器端一般都需要同時(shí)為多個(gè)客戶端提供通訊,如果需要同時(shí)支持多個(gè)客戶端,則必須使用前面介紹的線程的概念。簡單來說,也就是當(dāng)服務(wù)器端接收到一個(gè)連接時(shí),啟動(dòng)一個(gè)專門的線程處理和該客戶端的通訊。

?????????按照這個(gè)思路改寫的服務(wù)端示例程序?qū)⒂蓛蓚€(gè)部分組成,MulThreadSocketServer類實(shí)現(xiàn)服務(wù)器端控制,實(shí)現(xiàn)接收客戶端連接,然后開啟專門的邏輯線程處理該連接,LogicThread類實(shí)現(xiàn)對于一個(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)聽端口號

?????????????????? 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è)專門的線程處理,通過多個(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ù)長度

???????? ?* @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è)連接對象構(gòu)造該線程,該連接對象就是該線程需要處理的連接,在線程構(gòu)造完成以后,該線程就被啟動(dòng)起來了,然后在run方法內(nèi)部對客戶端連接進(jìn)行處理,數(shù)據(jù)交換的邏輯和前面的示例代碼一致,只是這里將接收到客戶端發(fā)送過來的數(shù)據(jù)并進(jìn)行處理的邏輯封裝成了logic方法,按照前面介紹的IO編程的內(nèi)容,客戶端發(fā)送過來的內(nèi)容存儲在數(shù)組b的起始下標(biāo)為0,長度為n個(gè)中,這些數(shù)據(jù)是客戶端發(fā)送過來的有效數(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ù)的限制,所以不能無限制的創(chuàng)建線程對象,而且頻繁的創(chuàng)建線程對象效率也比較低,所以程序中都實(shí)現(xiàn)了線程池來提高程序的執(zhí)行效率。

?????????這里簡單介紹一下線程池的概念,線程池(Thread pool)是池技術(shù)的一種,就是在程序啟動(dòng)時(shí)首先把需要個(gè)數(shù)的線程對象創(chuàng)建好,例如創(chuàng)建5000個(gè)線程對象,然后當(dāng)客戶端連接到達(dá)時(shí)從池中取出一個(gè)已經(jīng)創(chuàng)建完成的線程對象使用即可。當(dāng)客戶端連接關(guān)閉以后,將該線程對象重新放入到線程池中供其它的客戶端重復(fù)使用,這樣可以提高程序的執(zhí)行速度,優(yōu)化程序?qū)τ趦?nèi)存的占用等。

?????????關(guān)于基礎(chǔ)的TCP方式的網(wǎng)絡(luò)編程就介紹這么多,下面介紹UDP方式的網(wǎng)絡(luò)編程在Java語言中的實(shí)現(xiàn)。

?

?

?

???????網(wǎng)絡(luò)通訊的方式除了TCP方式以外,還有一種實(shí)現(xiàn)的方式就是UDP方式。UDP(User Datagram Protocol),中文意思是用戶數(shù)據(jù)報(bào)協(xié)議,方式類似于發(fā)短信息,是一種物美價(jià)廉的通訊方式,使用該種方式無需建立專用的虛擬連接,由于無需建立專用的連接,所以對于服務(wù)器的壓力要比TCP小很多,所以也是一種常見的網(wǎng)絡(luò)編程方式。但是使用該種方式最大的不足是傳輸不可靠,當(dāng)然也不是說經(jīng)常丟失,就像大家發(fā)短信息一樣,理論上存在收不到的可能,這種可能性可能是1%,反正比較小,但是由于這種可能的存在,所以平時(shí)我們都覺得重要的事情還是打個(gè)電話吧(類似TCP方式),一般的事情才發(fā)短信息(類似UDP方式)。網(wǎng)絡(luò)編程中也是這樣,必須要求可靠傳輸?shù)男畔⒁话闶褂肨CP方式實(shí)現(xiàn),一般的數(shù)據(jù)才使用UDP方式實(shí)現(xiàn)。

???????? UDP方式的網(wǎng)絡(luò)編程也在Java語言中獲得了良好的支持,由于其在傳輸數(shù)據(jù)的過程中不需要建立專用的連接等特點(diǎn),所以在Java API中設(shè)計(jì)的實(shí)現(xiàn)結(jié)構(gòu)和TCP方式不太一樣。當(dāng)然,需要使用的類還是包含在java.net包中。

?????????在Java API中,實(shí)現(xiàn)UDP方式的編程,包含客戶端網(wǎng)絡(luò)編程和服務(wù)器端網(wǎng)絡(luò)編程,主要由兩個(gè)類實(shí)現(xiàn),分別是:

l?DatagramSocket

DatagramSocket類實(shí)現(xiàn)“網(wǎng)絡(luò)連接”,包括客戶端網(wǎng)絡(luò)連接和服務(wù)器端網(wǎng)絡(luò)連接。雖然UDP方式的網(wǎng)絡(luò)通訊不需要建立專用的網(wǎng)絡(luò)連接,但是畢竟還是需要發(fā)送和接收數(shù)據(jù),DatagramSocket實(shí)現(xiàn)的就是發(fā)送數(shù)據(jù)時(shí)的發(fā)射器,以及接收數(shù)據(jù)時(shí)的監(jiān)聽器的角色。類比于TCP中的網(wǎng)絡(luò)連接,該類既可以用于實(shí)現(xiàn)客戶端連接,也可以用于實(shí)現(xiàn)服務(wù)器端連接。

l?DatagramPacket

DatagramPacket類實(shí)現(xiàn)對于網(wǎng)絡(luò)中傳輸?shù)臄?shù)據(jù)封裝,也就是說,該類的對象代表網(wǎng)絡(luò)中交換的數(shù)據(jù)。在UDP方式的網(wǎng)絡(luò)編程中,無論是需要發(fā)送的數(shù)據(jù)還是需要接收的數(shù)據(jù),都必須被處理成DatagramPacket類型的對象,該對象中包含發(fā)送到的地址、發(fā)送到的端口號以及發(fā)送的內(nèi)容等。其實(shí)DatagramPacket類的作用類似于現(xiàn)實(shí)中的信件,在信件中包含信件發(fā)送到的地址以及接收人,還有發(fā)送的內(nèi)容等,郵局只需要按照地址傳遞即可。在接收數(shù)據(jù)時(shí),接收到的數(shù)據(jù)也必須被處理成DatagramPacket類型的對象,在該對象中包含發(fā)送方的地址、端口號等信息,也包含數(shù)據(jù)的內(nèi)容。和TCP方式的網(wǎng)絡(luò)傳輸相比,IO編程在UDP方式的網(wǎng)絡(luò)編程中變得不是必須的內(nèi)容,結(jié)構(gòu)也要比TCP方式的網(wǎng)絡(luò)編程簡單一些。

?????????下面介紹一下UDP方式的網(wǎng)絡(luò)編程中,客戶端和服務(wù)器端的實(shí)現(xiàn)步驟,以及通過基礎(chǔ)的示例演示UDP方式的網(wǎng)絡(luò)編程在Java語言中的實(shí)現(xiàn)方式。

???????? UDP方式的網(wǎng)絡(luò)編程,編程的步驟和TCP方式類似,只是使用的類和方法存在比較大的區(qū)別,下面首先介紹一下UDP方式的網(wǎng)絡(luò)編程客戶端實(shí)現(xiàn)過程。

UDP客戶端編程涉及的步驟也是4個(gè)部分:建立連接、發(fā)送數(shù)據(jù)、接收數(shù)據(jù)和關(guān)閉連接。

首先介紹UDP方式的網(wǎng)絡(luò)編程中建立連接的實(shí)現(xiàn)。其中UDP方式的建立連接和TCP方式不同,只需要建立一個(gè)連接對象即可,不需要指定服務(wù)器的IP和端口號碼。實(shí)現(xiàn)的代碼為:

?????????????????? DatagramSocket ds = new DatagramSocket();

?????????這樣就建立了一個(gè)客戶端連接,該客戶端連接使用系統(tǒng)隨機(jī)分配的一個(gè)本地計(jì)算機(jī)的未用端口號。在該連接中,不指定服務(wù)器端的IP和端口,所以UDP方式的網(wǎng)絡(luò)連接更像一個(gè)發(fā)射器,而不是一個(gè)具體的連接。

?????????當(dāng)然,可以通過制定連接使用的端口號來創(chuàng)建客戶端連接。

?????????????????? DatagramSocket ds = new DatagramSocket(5000);

?????????這樣就是使用本地計(jì)算機(jī)的5000號端口建立了一個(gè)連接。一般在建立客戶端連接時(shí)沒有必要指定端口號碼。

?????????接著,介紹一下UDP客戶端編程中發(fā)送數(shù)據(jù)的實(shí)現(xiàn)。在UDP方式的網(wǎng)絡(luò)編程中,IO技術(shù)不是必須的,在發(fā)送數(shù)據(jù)時(shí),需要將需要發(fā)送的數(shù)據(jù)內(nèi)容首先轉(zhuǎn)換為byte數(shù)組,然后將數(shù)據(jù)內(nèi)容、服務(wù)器IP和服務(wù)器端口號一起構(gòu)造成一個(gè)DatagramPacket類型的對象,這樣數(shù)據(jù)的準(zhǔn)備就完成了,發(fā)送時(shí)調(diào)用網(wǎng)絡(luò)連接對象中的send方法發(fā)送該對象即可。例如將字符串“Hello”發(fā)送到IP是127.0.0.1,端口號是10001的服務(wù)器,則實(shí)現(xiàn)發(fā)送數(shù)據(jù)的代碼如下:

???????? ???????? String s = “Hello”;

?????????????????? String host = “127.0.0.1”;

?????????????????? int port = 10001;

???????? ???????? //將發(fā)送的內(nèi)容轉(zhuǎn)換為byte數(shù)組

?????????????????? byte[] b = s.getBytes();

?????????????????? //將服務(wù)器IP轉(zhuǎn)換為InetAddress對象

?????????????????? InetAddress server = InetAddress.getByName(host);

?????????????????? //構(gòu)造發(fā)送的數(shù)據(jù)包對象

?????????????????? DatagramPacket sendDp = new DatagramPacket(b,b.length,server,port);

?????????????????? //發(fā)送數(shù)據(jù)

?????????????????? ds.send(sendDp);

?????????在該示例代碼中,不管發(fā)送的數(shù)據(jù)內(nèi)容是什么,都需要轉(zhuǎn)換為byte數(shù)組,然后將服務(wù)器端的IP地址構(gòu)造成InetAddress類型的對象,在準(zhǔn)備完成以后,將這些信息構(gòu)造成一個(gè)DatagramPacket類型的對象,在UDP編程中,發(fā)送的數(shù)據(jù)內(nèi)容、服務(wù)器端的IP和端口號,都包含在DatagramPacket對象中。在準(zhǔn)備完成以后,調(diào)用連接對象ds的send方法把DatagramPacket對象發(fā)送出去即可。

?????????按照UDP協(xié)議的約定,在進(jìn)行數(shù)據(jù)傳輸時(shí),系統(tǒng)只是盡全力傳輸數(shù)據(jù),但是并不保證數(shù)據(jù)一定被正確傳輸,如果數(shù)據(jù)在傳輸過程中丟失,那就丟失了。

???????? UDP方式在進(jìn)行網(wǎng)絡(luò)通訊時(shí),也遵循“請求-響應(yīng)”模型,在發(fā)送數(shù)據(jù)完成以后,就可以接收服務(wù)器端的反饋數(shù)據(jù)了。

?????????下面介紹一下UDP客戶端編程中接收數(shù)據(jù)的實(shí)現(xiàn)。當(dāng)數(shù)據(jù)發(fā)送出去以后,就可以接收服務(wù)器端的反饋信息了。接收數(shù)據(jù)在Java語言中的實(shí)現(xiàn)是這樣的:首先構(gòu)造一個(gè)數(shù)據(jù)緩沖數(shù)組,該數(shù)組用于存儲接收的服務(wù)器端反饋數(shù)據(jù),該數(shù)組的長度必須大于或等于服務(wù)器端反饋的實(shí)際有效數(shù)據(jù)的長度。然后以該緩沖數(shù)組為基礎(chǔ)構(gòu)造一個(gè)DatagramPacket數(shù)據(jù)包對象,最后調(diào)用連接對象的receive方法接收數(shù)據(jù)即可。接收到的服務(wù)器端反饋數(shù)據(jù)存儲在DatagramPacket類型的對象內(nèi)部。實(shí)現(xiàn)接收數(shù)據(jù)以及顯示服務(wù)器端反饋內(nèi)容的示例代碼如下:

?????????????????? //構(gòu)造緩沖數(shù)組

?????????????????? byte[] data = new byte[1024];

?????????????????? //構(gòu)造數(shù)據(jù)包對象

?????????????????? DatagramPacket received = new DatagramPacket(data,data.length);

?????????????????? //接收數(shù)據(jù)

?????????????????? ds.receive(receiveDp);

?????????????????? //輸出數(shù)據(jù)內(nèi)容

?????????????????? byte[] b = receiveDp.getData();?//獲得緩沖數(shù)組

?????????????????? int len = receiveDp.getLength(); //獲得有效數(shù)據(jù)長度

?????????????????? String s = new String(b,0,len);

?????????????????? System.out.println(s);

?????????在該代碼中,首先構(gòu)造緩沖數(shù)組data,這里設(shè)置的長度1024是預(yù)估的接收到的數(shù)據(jù)長度,要求該長度必須大于或等于接收到的數(shù)據(jù)長度,然后以該緩沖數(shù)組為基礎(chǔ),構(gòu)造數(shù)據(jù)包對象,使用連接對象ds的receive方法接收反饋數(shù)據(jù),由于在Java語言中,除String以外的其它對象都是按照地址傳遞,所以在receive方法內(nèi)部可以改變數(shù)據(jù)包對象receiveDp的內(nèi)容,這里的receiveDp的功能和返回值類似。數(shù)據(jù)接收到以后,只需要從數(shù)據(jù)包對象中讀取出來就可以了,使用DatagramPacket對象中的getData方法可以獲得數(shù)據(jù)包對象的緩沖區(qū)數(shù)組,但是緩沖區(qū)數(shù)組的長度一般大于有效數(shù)據(jù)的長度,換句話說,也就是緩沖區(qū)數(shù)組中只有一部分?jǐn)?shù)據(jù)是反饋數(shù)據(jù),所以需要使用DatagramPacket對象中的getLength方法獲得有效數(shù)據(jù)的長度,則有效數(shù)據(jù)就是緩沖數(shù)組中的前有效數(shù)據(jù)長度個(gè)內(nèi)容,這些才是真正的服務(wù)器端反饋的數(shù)據(jù)的內(nèi)容。

???????? UDP方式客戶端網(wǎng)絡(luò)編程的最后一個(gè)步驟就是關(guān)閉連接。雖然UDP方式不建立專用的虛擬連接,但是連接對象還是需要占用系統(tǒng)資源,所以在使用完成以后必須關(guān)閉連接。關(guān)閉連接使用連接對象中的close方法即可,實(shí)現(xiàn)的代碼如下:

?????????????????? ds.close();

?????????需要說明的是,和TCP建立連接的方式不同,UDP方式的同一個(gè)網(wǎng)絡(luò)連接對象,可以發(fā)送到達(dá)不同服務(wù)器端IP或端口的數(shù)據(jù)包,這點(diǎn)是TCP方式無法做到的。

?????????介紹完了UDP方式客戶端網(wǎng)絡(luò)編程的基礎(chǔ)知識以后,下面再來介紹一下UDP方式服務(wù)器端網(wǎng)絡(luò)編程的基礎(chǔ)知識。

???????? UDP方式網(wǎng)絡(luò)編程的服務(wù)器端實(shí)現(xiàn)和TCP方式的服務(wù)器端實(shí)現(xiàn)類似,也是服務(wù)器端監(jiān)聽某個(gè)端口,然后獲得數(shù)據(jù)包,進(jìn)行邏輯處理以后將處理以后的結(jié)果反饋給客戶端,最后關(guān)閉網(wǎng)絡(luò)連接,下面依次進(jìn)行介紹。

?????????首先UDP方式服務(wù)器端網(wǎng)絡(luò)編程需要建立一個(gè)連接,該連接監(jiān)聽某個(gè)端口,實(shí)現(xiàn)的代碼為:

???? ????? DatagramSocket ds = new DatagramSocket(10010);

由于服務(wù)器端的端口需要固定,所以一般在建立服務(wù)器端連接時(shí),都指定端口號。例如該示例代碼中指定10010端口為服務(wù)器端使用的端口號,客戶端端在連接服務(wù)器端時(shí)連接該端口號即可。

接著服務(wù)器端就開始接收客戶端發(fā)送過來的數(shù)據(jù),其接收的方法和客戶端接收的方法一直,其中receive方法的作用類似于TCP方式中accept方法的作用,該方法也是一個(gè)阻塞方法,其作用是接收數(shù)據(jù)。

接收到客戶端發(fā)送過來的數(shù)據(jù)以后,服務(wù)器端對該數(shù)據(jù)進(jìn)行邏輯處理,然后將處理以后的結(jié)果再發(fā)送給客戶端,在這里發(fā)送時(shí)就比客戶端要麻煩一些,因?yàn)榉?wù)器端需要獲得客戶端的IP和客戶端使用的端口號,這個(gè)都可以從接收到的數(shù)據(jù)包中獲得。示例代碼如下:

???? //獲得客戶端的IP

???? InetAddress clientIP = receiveDp.getAddress();

???????? //獲得客戶端的端口號

???????? Int clientPort = receiveDp.getPort();

?????使用以上代碼,就可以從接收到的數(shù)據(jù)包對象receiveDp中獲得客戶端的IP地址和客戶端的端口號,這樣就可以在服務(wù)器端中將處理以后的數(shù)據(jù)構(gòu)造成數(shù)據(jù)包對象,然后將處理以后的數(shù)據(jù)內(nèi)容反饋給客戶端了。

?????最后,當(dāng)服務(wù)器端實(shí)現(xiàn)完成以后,關(guān)閉服務(wù)器端連接,實(shí)現(xiàn)的方式為調(diào)用連接對象的close方法,示例代碼如下:

???????? ds.close();

?????介紹完了UDP方式下的客戶端編程和服務(wù)器端編程的基礎(chǔ)知識以后,下面通過一個(gè)簡單的示例演示UDP網(wǎng)絡(luò)編程的基本使用。

?

該示例的功能是實(shí)現(xiàn)將客戶端程序的系統(tǒng)時(shí)間發(fā)送給服務(wù)器端,服務(wù)器端接收到時(shí)間以后,向客戶端反饋?zhàn)址癘K”。實(shí)現(xiàn)該功能的客戶端代碼如下所示:

??????? package udp;

import java.net.*;

import java.util.*;

/**

?*?簡單的UDP客戶端,實(shí)現(xiàn)向服務(wù)器端發(fā)生系統(tǒng)時(shí)間功能

?*/

public class SimpleUDPClient {

??????????? public static void main(String[] args) {

???????? ??????? ????DatagramSocket ds = null;?//連接對象

???????? ??????? ????DatagramPacket sendDp; //發(fā)送數(shù)據(jù)包對象

??????????? ????DatagramPacket receiveDp; //接收數(shù)據(jù)包對象

???????? ??????? ????String serverHost = "127.0.0.1"; //服務(wù)器IP

??????????? ????int serverPort = 10010;?//服務(wù)器端口號

???????? ??????? ????try{

???????? ?????????? ????//建立連接

???????? ?????????? ????ds = new DatagramSocket();

???????? ?????????? ????//初始化發(fā)送數(shù)據(jù)

???????? ?????????? ????Date d = new Date(); //當(dāng)前時(shí)間

???????? ????? ?????????String content = d.toString(); //轉(zhuǎn)換為字符串

???????? ?????????? ????byte[] data = content.getBytes();

???????? ?????????? ????//初始化發(fā)送包對象

???????? ?????????? ????InetAddress address = InetAddress.getByName(serverHost);

???????? ?????????? ????sendDp = new DatagramPacket(data,data.length,address,serverPort);

???????? ?????????? ????//發(fā)送

???????? ?????????? ????ds.send(sendDp);

??????????????????????????????????????????????????????????????????????????

???????? ?????????? ????//初始化接收數(shù)據(jù)

???????? ?????????? ????byte[] b = new byte[1024];

???????? ?????????? ????receiveDp = new DatagramPacket(b,b.length);

???????? ?????????? ????//接收

???????? ?????????? ????ds.receive(receiveDp);

???????? ?????????? ????//讀取反饋內(nèi)容,并輸出

???????? ?????????? ????byte[] response = receiveDp.getData();

???????? ?????????? ????int len = receiveDp.getLength();

???????? ?????????? ????String s = new String(response,0,len);

???????? ?????????? ????System.out.println("服務(wù)器端反饋為:" + s);

??????????? ????}catch(Exception e){

???????? ?????????????? e.printStackTrace();

??????????????? }finally{

???????? ?????????????? try{

???????? ????????????????? //關(guān)閉連接

???????? ????????????????? ds.close();

???????? ?????????????? }catch(Exception e){}

??????????????? }

??????????? }

?????? ?}

在該示例代碼中,首先建立UDP方式的網(wǎng)絡(luò)連接,然后獲得當(dāng)前系統(tǒng)時(shí)間,這里獲得的系統(tǒng)時(shí)間是客戶端程序運(yùn)行的本地計(jì)算機(jī)的時(shí)間,然后將時(shí)間字符串以及服務(wù)器端的IP和端口,構(gòu)造成發(fā)送數(shù)據(jù)包對象,調(diào)用連接對象ds的send方法發(fā)送出去。在數(shù)據(jù)發(fā)送出去以后,構(gòu)造接收數(shù)據(jù)的數(shù)據(jù)包對象,調(diào)用連接對象ds的receive方法接收服務(wù)器端的反饋,并輸出在控制臺。最后在finally語句塊中關(guān)閉客戶端網(wǎng)絡(luò)連接。

和下面將要介紹的服務(wù)器端一起運(yùn)行時(shí),客戶端程序的輸出結(jié)果為:

????服務(wù)器端反饋為:OK

下面是該示例程序的服務(wù)器端代碼實(shí)現(xiàn):

???????package udp;

??????? import java.net.*;

??????? /**

???????? *?簡單UDP服務(wù)器端,實(shí)現(xiàn)功能是輸出客戶端發(fā)送數(shù)據(jù),

???????????并反饋?zhàn)址癘K"給客戶端

???????? */

??????? public class SimpleUDPServer {

??????????? public static void main(String[] args) {

???????? ??????????? DatagramSocket ds = null;?//連接對象

???????? ??????????? DatagramPacket sendDp; //發(fā)送數(shù)據(jù)包對象

???????? ??????????? DatagramPacket receiveDp; //接收數(shù)據(jù)包對象

???????? ??????????? final int PORT = 10010; //端口

?????????????????????????????????????????????? try{

???????? ?????????????? //建立連接,監(jiān)聽端口

???????? ?????????????? ds = new DatagramSocket(PORT);

???????? ??????????????System.out.println("服務(wù)器端已啟動(dòng):");

???????? ?????????????? //初始化接收數(shù)據(jù)

???????? ?????????????? byte[] b = new byte[1024];

???????? ?????????????? receiveDp = new DatagramPacket(b,b.length);

???????? ?????????????? //接收

???????? ?????????????? ds.receive(receiveDp);

???????? ?????????????? //讀取反饋內(nèi)容,并輸出

???????? ?????????????? InetAddress clientIP = receiveDp.getAddress();

???????? ?????????????? int clientPort = receiveDp.getPort();

???????? ?????????????? byte[] data = receiveDp.getData();

???????? ?????????????? int len = receiveDp.getLength();

???????? ?????????????? System.out.println("客戶端IP:" + clientIP.getHostAddress());

???????? ?????????????? System.out.println("客戶端端口:" + clientPort);

???????? ?????????????? System.out.println("客戶端發(fā)送內(nèi)容:" + new String(data,0,len));

??????????????????????????????????????????????????????????????????????????

???????? ?????????????? //發(fā)送反饋

???????? ?????????????? String response = "OK";

???????? ????????????? ?byte[] bData = response.getBytes();

???????? ?????????????? sendDp = new DatagramPacket(bData,bData.length,clientIP,clientPort);

???????? ?????????????? //發(fā)送

???????? ?????????????? ds.send(sendDp);

?????????????????????????????????????????????? }catch(Exception e){

???????? ?????????????? e.printStackTrace();

?????????????????????????????????????????????? }finally{

???????? ?????? ????????try{

???????? ????????????????? //關(guān)閉連接

???????? ????????????????? ds.close();

???????? ?????????????? }catch(Exception e){}

?????????????????????????????????????????????? }

??????????? }

??????? }

在該服務(wù)器端實(shí)現(xiàn)中,首先監(jiān)聽10010號端口,和TCP方式的網(wǎng)絡(luò)編程類似,服務(wù)器端的receive方法是阻塞方法,如果客戶端不發(fā)送數(shù)據(jù),則程序會在該方法處阻塞。當(dāng)客戶端發(fā)送數(shù)據(jù)到達(dá)服務(wù)器端時(shí),則接收客戶端發(fā)送過來的數(shù)據(jù),然后將客戶端發(fā)送的數(shù)據(jù)內(nèi)容讀取出來,并在服務(wù)器端程序中打印客戶端的相關(guān)信息,從客戶端發(fā)送過來的數(shù)據(jù)包中可以讀取出客戶端的IP以及客戶端端口號,將反饋數(shù)據(jù)字符串“OK”發(fā)送給客戶端,最后關(guān)閉服務(wù)器端連接,釋放占用的系統(tǒng)資源,完成程序功能示例。

和前面TCP方式中的網(wǎng)絡(luò)編程類似,這個(gè)示例也僅僅是網(wǎng)絡(luò)編程的功能示例,也存在前面介紹的客戶端無法進(jìn)行多次數(shù)據(jù)交換,以及服務(wù)器端不支持多個(gè)客戶端的問題,這兩個(gè)問題也需要對于代碼進(jìn)行處理才可以很方便的進(jìn)行解決。

在解決該問題以前,需要特別指出的是UDP方式的網(wǎng)絡(luò)編程由于不建立虛擬的連接,所以在實(shí)際使用時(shí)和TCP方式存在很多的不同,最大的一個(gè)不同就是“無狀態(tài)”。該特點(diǎn)指每次服務(wù)器端都收到信息,但是這些信息和連接無關(guān),換句話說,也就是服務(wù)器端只是從信息是無法識別出是誰發(fā)送的,這樣就要求發(fā)送信息時(shí)的內(nèi)容需要多一些,這個(gè)在后續(xù)的示例中可以看到。

下面是實(shí)現(xiàn)客戶端多次發(fā)送以及服務(wù)器端支持多個(gè)數(shù)據(jù)包同時(shí)處理的程序結(jié)構(gòu),實(shí)現(xiàn)的原理和TCP方式類似,在客戶端將數(shù)據(jù)的發(fā)送和接收放入循環(huán)中,而服務(wù)器端則將接收到的每個(gè)數(shù)據(jù)包啟動(dòng)一個(gè)專門的線程進(jìn)行處理。實(shí)現(xiàn)的代碼如下:

??? package udp;

??? import java.net.*;

??? import java.util.*;

??? /**

??? ?*?簡單的UDP客戶端,實(shí)現(xiàn)向服務(wù)器端發(fā)生系統(tǒng)時(shí)間功能

??? ?*?該程序發(fā)送3次數(shù)據(jù)到服務(wù)器端

??? ?*/

??? public class MulUDPClient {

????????????????? public static void main(String[] args) {

???????? ??????? DatagramSocket ds = null;?//連接對象

???????????????????????????????????? DatagramPacket sendDp; //發(fā)送數(shù)據(jù)包對象

???????????????????????????????????? DatagramPacket receiveDp; //接收數(shù)據(jù)包對象

???????????????????????????????????? String serverHost = "127.0.0.1"; //服務(wù)器IP

???????????????????????????????????? int serverPort = 10012;?//服務(wù)器端口號

???????????????????????????????????? try{

???????? ?????????? //建立連接

???????? ?????????? ds = new DatagramSocket();

???????? ?????????? //初始化

??????????????????????????? ??InetAddress address = InetAddress.getByName(serverHost);

???????? ?????????? byte[] b = new byte[1024];

???????? ?????????? receiveDp = new DatagramPacket(b,b.length);

???????? ?????????? System.out.println("客戶端準(zhǔn)備完成");

???????? ?????????? //循環(huán)30次,每次間隔0.01秒

???????? ?????????? for(int i = 0;i < 30;i++){

??????????????????????????????????????????????????????? //初始化發(fā)送數(shù)據(jù)

??????????????????????????????????????????????????????? Date d = new Date(); //當(dāng)前時(shí)間

??????????????????????????????????????????????????????? String content = d.toString(); //轉(zhuǎn)換為字符串

??????????????????????????????????????????????????????? byte[] data = content.getBytes();

??????????????????????????????????????????????????????? //初始化發(fā)送包對象

??????????????????????????????????????????????????????? sendDp = new DatagramPacket(data,data.length,address, serverPort);

??????????????????????????????????????????????????????? //發(fā)送

??????????????????????????????????????????????????????? ds.send(sendDp);

??????????????????????????????????????????????????????? //延遲

??????????????????????????????????????????????????????? Thread.sleep(10);

??????????????????????????????????????????????????????? //接收

??????????????????????????????????????????????????????? ds.receive(receiveDp);

??????????????????????????????????????????????????????? //讀取反饋內(nèi)容,并輸出

??????????????????????????????????????????????????????? byte[] response = receiveDp.getData();

??????????????????????????????????????????????????????? int len = receiveDp.getLength();

??????????????????????????????????????????????????????? String s = new String(response,0,len);

??????????????????????????????????????????????????????? System.out.println("服務(wù)器端反饋為:" + s);

???????? ??????????? }

???????? ??????? }catch(Exception e){

???????? ??????????? e.printStackTrace();

???????? ??????? }finally{

???????? ??????????? try{

??????????????????????????????????????????????????????? //關(guān)閉連接

??????????????????????????????????????????????????????? ds.close();

???????? ??????????? }catch(Exception e){}

???????? ??????? }

???????? }

???? }

在該示例中,將和服務(wù)器端進(jìn)行數(shù)據(jù)交換的邏輯寫在一個(gè)for循環(huán)的內(nèi)部,這樣就可以實(shí)現(xiàn)和服務(wù)器端的多次交換了,考慮到服務(wù)器端的響應(yīng)速度,在每次發(fā)送之間加入0.01秒的時(shí)間間隔。最后當(dāng)數(shù)據(jù)交換完成以后關(guān)閉連接,結(jié)束程序。

實(shí)現(xiàn)該邏輯的服務(wù)器端程序代碼如下:

package udp;

import java.net.*;

/**

*?可以并發(fā)處理數(shù)據(jù)包的服務(wù)器端

*?功能為:顯示客戶端發(fā)送的內(nèi)容,并向客戶端反饋?zhàn)址癘K”

*/

public class MulUDPServer {

public static void main(String[] args) {

DatagramSocket ds = null;?//連接對象

DatagramPacket receiveDp; //接收數(shù)據(jù)包對象

final int PORT = 10012; //端口

byte[] b = new byte[1024];

receiveDp = new DatagramPacket(b,b.length);

try{

//建立連接,監(jiān)聽端口

ds = new DatagramSocket(PORT);

System.out.println("服務(wù)器端已啟動(dòng):");

while(true){

//接收

ds.receive(receiveDp);

//啟動(dòng)線程處理數(shù)據(jù)包

new LogicThread(ds,receiveDp);

}

}catch(Exception e){

???????? e.printStackTrace();

}finally{

try{

//關(guān)閉連接

ds.close();

}catch(Exception e){}

}

}

}

該代碼實(shí)現(xiàn)了服務(wù)器端的接收邏輯,使用一個(gè)循環(huán)來接收客戶端發(fā)送過來的數(shù)據(jù)包,當(dāng)接收到數(shù)據(jù)包以后啟動(dòng)一個(gè)LogicThread線程處理該數(shù)據(jù)包。這樣服務(wù)器端就可以實(shí)現(xiàn)同時(shí)處理多個(gè)數(shù)據(jù)包了。

實(shí)現(xiàn)邏輯處理的線程代碼如下:

package udp;

import java.net.*;

/**

?*?邏輯處理線程

?*/

public class LogicThread extends Thread {

/**連接對象*/

DatagramSocket ds;

/**接收到的數(shù)據(jù)包*/

DatagramPacket dp;

?

public LogicThread(DatagramSocket ds,DatagramPacket dp){

this.ds = ds;

this.dp = dp;

start(); //啟動(dòng)線程

}

?

public void run(){

try{

//獲得緩沖數(shù)組

byte[] data = dp.getData();

//獲得有效數(shù)據(jù)長度

int len = dp.getLength();

//客戶端IP

InetAddress clientAddress = dp.getAddress();

//客戶端端口

int clientPort = dp.getPort();

//輸出

System.out.println("客戶端IP:" + clientAddress.getHostAddress());

System.out.println("客戶端端口號:" + clientPort);

System.out.println("客戶端發(fā)送內(nèi)容:" + new String(data,0,len));

//反饋到客戶端

byte[] b = "OK".getBytes();

DatagramPacket sendDp = new DatagramPacket(b,b.length,clientAddress,clientPort);

//發(fā)送

ds.send(sendDp);

}catch(Exception e){

e.printStackTrace();

}

}

}

在該線程中,只處理一次UDP通訊,當(dāng)通訊結(jié)束以后線程死亡,在線程內(nèi)部,每次獲得客戶端發(fā)送過來的信息,將獲得的信息輸出到服務(wù)器端程序的控制臺,然后向客戶端反饋?zhàn)址癘K”。

由于UDP數(shù)據(jù)傳輸過程中可能存在丟失,所以在運(yùn)行該程序時(shí)可能會出現(xiàn)程序阻塞的情況。如果需要避免該問題,可以將客戶端的網(wǎng)絡(luò)發(fā)送部分也修改成線程實(shí)現(xiàn)。

關(guān)于基礎(chǔ)的UDP網(wǎng)絡(luò)編程就介紹這么多了,下面將介紹一下網(wǎng)絡(luò)協(xié)議的概念。

?網(wǎng)絡(luò)協(xié)議

?????????對于需要從事網(wǎng)絡(luò)編程的程序員來說,網(wǎng)絡(luò)協(xié)議是一個(gè)需要深刻理解的概念。那么什么是網(wǎng)絡(luò)協(xié)議呢?

?????????網(wǎng)絡(luò)協(xié)議是指對于網(wǎng)絡(luò)中傳輸?shù)臄?shù)據(jù)格式的規(guī)定。對于網(wǎng)絡(luò)編程初學(xué)者來說,沒有必要深入了解TCP/IP協(xié)議簇,所以對于初學(xué)者來說去讀大部頭的《TCP/IP協(xié)議》也不是一件很合適的事情,因?yàn)樯钊肓私釺CP/IP協(xié)議是網(wǎng)絡(luò)編程提高階段,也是深入網(wǎng)絡(luò)編程底層時(shí)才需要做的事情。

?????????對于一般的網(wǎng)絡(luò)編程來說,更多的是關(guān)心網(wǎng)絡(luò)上傳輸?shù)倪壿嫈?shù)據(jù)內(nèi)容,也就是更多的是應(yīng)用層上的網(wǎng)絡(luò)協(xié)議,所以后續(xù)的內(nèi)容均以實(shí)際應(yīng)用的數(shù)據(jù)為基礎(chǔ)來介紹網(wǎng)絡(luò)協(xié)議的概念。

?????????那么什么是網(wǎng)絡(luò)協(xié)議呢,下面看一個(gè)簡單的例子。春節(jié)晚會上“小沈陽”和趙本山合作的小品《不差錢》中,小沈陽和趙本山之間就設(shè)計(jì)了一個(gè)協(xié)議,協(xié)議的內(nèi)容為:

???????????????????如果點(diǎn)的菜價(jià)錢比較貴是,就說沒有。

?????????按照該協(xié)議的規(guī)定,就有了下面的對話:

???????????????????趙本山:4斤的龍蝦

???????????????????小沈陽:(經(jīng)過判斷,得出價(jià)格比較高),沒有

???????????????????趙本山:鮑魚

???????????????????小沈陽:(經(jīng)過判斷,得出價(jià)格比較高),沒有

?????????這就是一種雙方達(dá)成的一種協(xié)議約定,其實(shí)這種約定的實(shí)質(zhì)和網(wǎng)絡(luò)協(xié)議的實(shí)質(zhì)是一樣的。網(wǎng)絡(luò)協(xié)議的實(shí)質(zhì)也是客戶端程序和服務(wù)器端程序?qū)τ跀?shù)據(jù)的一種約定,只是由于以計(jì)算機(jī)為基礎(chǔ),所以更多的是使用數(shù)字來代表內(nèi)容,這樣就顯得比較抽象一些。

?????????下 面再舉一個(gè)簡單的例子,介紹一些基礎(chǔ)的網(wǎng)絡(luò)協(xié)議設(shè)計(jì)的知識。例如需要設(shè)計(jì)一個(gè)簡單的網(wǎng)絡(luò)程序:網(wǎng)絡(luò)計(jì)算器。也就是在客戶端輸入需要計(jì)算的數(shù)字和運(yùn)算符,在 服務(wù)器端實(shí)現(xiàn)計(jì)算,并將計(jì)算的結(jié)果反饋給客戶端。在這個(gè)例子中,就需要約定兩個(gè)數(shù)據(jù)格式:客戶端發(fā)送給服務(wù)器端的數(shù)據(jù)格式,以及服務(wù)器端反饋給客戶端的數(shù) 據(jù)格式。

?????????可能你覺得這個(gè)比較簡單,例如客戶端輸入的數(shù)字依次是12和432,輸入的運(yùn)算符是加號,可能最容易想到的數(shù)據(jù)格式是形成字符串“12+432”,這樣格式的確比較容易閱讀,但是服務(wù)器端在進(jìn)行計(jì)算時(shí),邏輯就比較麻煩,因?yàn)樾枰紫炔鸱衷撟址?#xff0c;然后才能進(jìn)行計(jì)算,所以可用的數(shù)據(jù)格式就有了一下幾種:

?????????????????? “12,432,+”?? ??格式為:第一個(gè)數(shù)字,第二個(gè)數(shù)字,運(yùn)算符

?????????????????? “12,+,432”?????格式為:第一個(gè)數(shù)字,運(yùn)算符,第二個(gè)數(shù)字

?????????其實(shí)以上兩種數(shù)據(jù)格式很接近,比較容易閱讀,在服務(wù)器端收到該數(shù)據(jù)格式以后,使用“,”為分隔符分割字符串即可。

?????????假設(shè)對于運(yùn)算符再進(jìn)行一次約定,例如約定數(shù)字0代表+,1代表減,2代表乘,3代表除,整體格式遵循以上第一種格式,則上面的數(shù)字生產(chǎn)的協(xié)議數(shù)據(jù)為:

???????????????????“12,432,0”

?????????這就是一種基本的發(fā)送的協(xié)議約定了。

?????????另 外一個(gè)需要設(shè)計(jì)的協(xié)議格式就是服務(wù)器端反饋的數(shù)據(jù)格式,其實(shí)服務(wù)器端主要反饋計(jì)算結(jié)果,但是在實(shí)際接受數(shù)據(jù)時(shí),有可能存在格式錯(cuò)誤的情況,這樣就需要簡單 的設(shè)計(jì)一下服務(wù)器端反饋的數(shù)據(jù)格式了。例如規(guī)定,如果發(fā)送的數(shù)據(jù)格式正確,則反饋結(jié)果,否則反饋?zhàn)址板e(cuò)誤”。這樣就有了以下的數(shù)據(jù)格式:

???????????????????客戶端:“1,111,1”????? ???服務(wù)器端:”-110”

???????????????????客戶端:“123,23,0”?? ?服務(wù)器端:“146”

????????客戶端:“1,2,5”???????服務(wù)器端:“錯(cuò)誤”

?????????這樣就設(shè)計(jì)出了一種最最基本的網(wǎng)絡(luò)協(xié)議格式,從該示例中可以看出,網(wǎng)絡(luò)協(xié)議就是一種格式上的約定,可以根據(jù)邏輯的需要約定出各種數(shù)據(jù)格式,在進(jìn)行設(shè)計(jì)時(shí)一般遵循“簡單、通用、容易解析”的原則進(jìn)行。

?????????而對于復(fù)雜的網(wǎng)絡(luò)程序來說,需要傳輸?shù)臄?shù)據(jù)種類和數(shù)據(jù)量都比較大,這樣只需要依次設(shè)計(jì)出每種情況下的數(shù)據(jù)格式即可,例如QQ程序,在該程序中需要進(jìn)行傳輸?shù)木W(wǎng)絡(luò)數(shù)據(jù)種類很多,那么在設(shè)計(jì)時(shí)就可以遵循:登錄格式、注冊格式、發(fā)送消息格式等等,一一進(jìn)行設(shè)計(jì)即可。所以對于復(fù)雜的網(wǎng)絡(luò)程序來說,只是增加了更多的命令格式,在實(shí)際設(shè)計(jì)時(shí)的工作量增加不是太大。

?????????不管怎么說,在網(wǎng)絡(luò)編程中,對于同一個(gè)網(wǎng)絡(luò)程序來說,一般都會涉及到兩個(gè)網(wǎng)絡(luò)協(xié)議格式:客戶端發(fā)送數(shù)據(jù)格式和服務(wù)器端反饋數(shù)據(jù)格式,在實(shí)際設(shè)計(jì)時(shí),需要一一對應(yīng)。這就是最基本的網(wǎng)絡(luò)協(xié)議的知識。

?????????網(wǎng)絡(luò)協(xié)議設(shè)計(jì)完成以后,在進(jìn)行網(wǎng)絡(luò)編程時(shí),就需要根據(jù)設(shè)計(jì)好的協(xié)議格式,在程序中進(jìn)行對應(yīng)的編碼了,客戶端程序和服務(wù)器端程序需要進(jìn)行協(xié)議處理的代碼分別如下。

客戶端程序需要完成的處理為:

1、?客戶端發(fā)送協(xié)議格式的生成

2、?服務(wù)器端反饋數(shù)據(jù)格式的解析

服務(wù)器端程序需要完成的處理為:

1、?服務(wù)器端反饋協(xié)議格式的生成

2、?客戶端發(fā)送協(xié)議格式的解析

這里的生成是指將計(jì)算好的數(shù)據(jù),轉(zhuǎn)換成規(guī)定的數(shù)據(jù)格式,這里的解析指,從反饋的數(shù)據(jù)格式中拆分出需要的數(shù)據(jù)。在進(jìn)行對應(yīng)的代碼編寫時(shí),嚴(yán)格遵循協(xié)議約定即可。

所以,對于程序員來說,在進(jìn)行網(wǎng)絡(luò)程序編寫時(shí),需要首先根據(jù)邏輯的需要設(shè)計(jì)網(wǎng)絡(luò)協(xié)議格式,然后遵循協(xié)議格式約定進(jìn)行協(xié)議生成和解析代碼的編寫,最后使用網(wǎng)絡(luò)編程技術(shù)實(shí)現(xiàn)整個(gè)網(wǎng)絡(luò)編程的功能。

由于各種網(wǎng)絡(luò)程序使用不同的協(xié)議格式,所以不同網(wǎng)絡(luò)程序的客戶端之間無法通用。

而對于常見協(xié)議的格式,例如HTTP(Hyper Text Transfer Protocol,超文本傳輸協(xié)議)、FTP(File Transfer Protocol,文件傳輸協(xié)議),SMTP(Simple Mail Transfer Protocol,簡單郵件傳輸協(xié)議)等等,都有通用的規(guī)定,具體可以查閱相關(guān)的RFC文檔。

最后,對于一種網(wǎng)絡(luò)程序來說,網(wǎng)絡(luò)協(xié)議格式是該程序最核心的技術(shù)秘密,因?yàn)橐坏﹨f(xié)議格式泄漏,則任何一個(gè)人都可以根據(jù)該格式進(jìn)行客戶端的編寫,這樣將影響服務(wù)器端的實(shí)現(xiàn),也容易出現(xiàn)一些其它的影響。

13.2.6小結(jié)

?????????關(guān)于網(wǎng)絡(luò)編程基本的技術(shù)就介紹這么多,該部分介紹了網(wǎng)絡(luò)編程的基礎(chǔ)知識,以及Java語言對于網(wǎng)絡(luò)編程的支持,網(wǎng)絡(luò)編程的步驟等,并詳細(xì)介紹了TCP方式網(wǎng)絡(luò)編程和UDP方式網(wǎng)絡(luò)編程在Java語言中的實(shí)現(xiàn)。

?????????網(wǎng)絡(luò)協(xié)議也是網(wǎng)絡(luò)程序的核心,所以在實(shí)際開始進(jìn)行網(wǎng)絡(luò)編程時(shí),設(shè)計(jì)一個(gè)良好的協(xié)議格式也是必須進(jìn)行的工作。

網(wǎng)絡(luò)編程示例

?????????“實(shí)踐出真知”,所以在進(jìn)行技術(shù)學(xué)習(xí)時(shí),還是需要進(jìn)行很多的練習(xí),才可以體會技術(shù)的奧妙,下面通過兩個(gè)簡單的示例,演示網(wǎng)絡(luò)編程的實(shí)際使用。

13.3.1質(zhì)數(shù)判別示例

?????????該示例實(shí)現(xiàn)的功能是質(zhì)數(shù)判斷,程序?qū)崿F(xiàn)的功能為客戶端程序接收用戶輸入的數(shù)字,然后將用戶輸入的內(nèi)容發(fā)送給服務(wù)器端,服務(wù)器端判斷客戶端發(fā)送的數(shù)字是否是質(zhì)數(shù),并將判斷的結(jié)果反饋給客戶端,客戶端根據(jù)服務(wù)器端的反饋顯示判斷結(jié)果。

?????????質(zhì)數(shù)的規(guī)則是:最小的質(zhì)數(shù)是2,只能被1和自身整除的自然數(shù)。當(dāng)用戶輸入小于2的數(shù)字,以及輸入的內(nèi)容不是自然數(shù)時(shí),都屬于非法輸入。

?????????網(wǎng)絡(luò)程序的功能都分為客戶端程序和服務(wù)器端程序?qū)崿F(xiàn),下面先描述一下每個(gè)程序分別實(shí)現(xiàn)的功能:

1、?客戶端程序功能:

a)?????????接收用戶控制臺輸入

b)?????????判斷輸入內(nèi)容是否合法

c)?????????按照協(xié)議格式生成發(fā)送數(shù)據(jù)

d)?????????發(fā)送數(shù)據(jù)

e)?????????接收服務(wù)器端反饋

f)??????????解析服務(wù)器端反饋信息,并輸出

2、?服務(wù)器端程序功能:

a)?????????接收客戶端發(fā)送數(shù)據(jù)

b)?????????按照協(xié)議格式解析數(shù)據(jù)

c)?????????判斷數(shù)字是否是質(zhì)數(shù)

d)?????????根據(jù)判斷結(jié)果,生成協(xié)議數(shù)據(jù)

e)?????????將數(shù)據(jù)反饋給客戶端

分解好了網(wǎng)絡(luò)程序的功能以后,就可以設(shè)計(jì)網(wǎng)絡(luò)協(xié)議格式了,如果該程序的功能比較簡單,所以設(shè)計(jì)出的協(xié)議格式也不復(fù)雜。

?????????客戶端發(fā)送協(xié)議格式:

???????????????????將用戶輸入的數(shù)字轉(zhuǎn)換為字符串,再將字符串轉(zhuǎn)換為byte數(shù)組即可。

???????????????????例如用戶輸入16,則轉(zhuǎn)換為字符串“16”,使用getBytes轉(zhuǎn)換為byte數(shù)組。

???????????????????客戶端發(fā)送“quit”字符串代表結(jié)束連接

?????????服務(wù)器端發(fā)送協(xié)議格式:

反饋數(shù)據(jù)長度為1個(gè)字節(jié)。數(shù)字0代表是質(zhì)數(shù),1代表不是質(zhì)數(shù),2代表協(xié)議格式錯(cuò)誤。

例如客戶端發(fā)送數(shù)字12,則反饋1,發(fā)送13則反饋0,發(fā)送0則反饋2。

?????????功能設(shè)計(jì)完成以后,就可以分別進(jìn)行客戶端和服務(wù)器端程序的編寫了,在編寫完成以后聯(lián)合起來進(jìn)行調(diào)試即可。

?????????下面分別以TCP方式和UDP方式實(shí)現(xiàn)該程序,注意其實(shí)現(xiàn)上的差異。不管使用哪種方式實(shí)現(xiàn),客戶端都可以多次輸入數(shù)據(jù)進(jìn)行判斷。對于UDP方式來說,不需要向服務(wù)器端發(fā)送quit字符串。

?????????以TCP方式實(shí)現(xiàn)的客戶端程序代碼如下:

?????????????????? package example1;

import java.io.*;

import java.net.*;

/**

?*?以TCP方式實(shí)現(xiàn)的質(zhì)數(shù)判斷客戶端程序

?*/

public class TCPPrimeClient {

???????? static BufferedReader br;

???????? static Socket socket;

???????? static InputStream is;

???????? static OutputStream os;

???????? /**服務(wù)器IP*/

???????? final static String HOST = "127.0.0.1";

???????? /**服務(wù)器端端口*/

???????? final static int PORT = 10005;

????????

???????? public static void main(String[] args) {

?????????????????? init(); //初始化

?????????????????? while(true){

??????????????????????????? System.out.println("請輸入數(shù)字:");

??????????????????????????? String input = readInput(); //讀取輸入

??????????????????????????? if(isQuit(input)){ //判讀是否結(jié)束

???????????????????????????????????? byte[] b = "quit".getBytes();

???????????????????????????????????? send(b);

???????????????????????????????????? break; //結(jié)束程序

??????????????????????????? }

??????????????????????????? if(checkInput(input)){ //校驗(yàn)合法

???????????????????????????????????? //發(fā)送數(shù)據(jù)

???????????????????????????????????? send(input.getBytes());

???????????????????????????????????? //接收數(shù)據(jù)

???????????????????????????????????? byte[] data = receive();

???????????????????????????????????? //解析反饋數(shù)據(jù)

???????????????????????????????????? parse(data);

??????????????????????????? }else{

???????????????????????????????????? System.out.println("輸入不合法,請重新輸入!");

??????????????????????????? }

?????????????????? }

?????????????????? close();?//關(guān)閉流和連接

???????? }

????????

???????? /**

???????? ?*?初始化

???????? ?*/

???????? private static void init(){

?????????????????? try {

??????????????????????????? br = new BufferedReader(

?????????????????????????????????????????????? new InputStreamReader(System.in));

??????????????????????????? socket = new Socket(HOST,PORT);

??????????????????????????? is = socket.getInputStream();

??????????????????????????? os = socket.getOutputStream();

?????????????????? } catch (Exception e) {}

???????? }

????????

???????? /**

???????? ?*?讀取客戶端輸入

???????? ?*/

???????? private static String readInput(){

?????????????????? try {

??????????????????????????? return br.readLine();

?????????????????? } catch (Exception e) {

??????????????????????????? return null;

?????????????????? }

???????? }

????????

???????? /**

???????? ?*?判斷是否輸入quit

???????? ?* @param input?輸入內(nèi)容

???????? ?* @return true代表結(jié)束,false代表不結(jié)束

???????? ?*/

???????? private static boolean isQuit(String input){

?????????????????? if(input == null){

??????????????????????????? return false;

?????????????????? }else{

??????????????????????????? if("quit".equalsIgnoreCase(input)){

???????????????????????????????????? return true;

??????????????????????????? }else{

???????????????????????????????????? return false;

??????????????????????????? }

?????????????????? }

???????? }

????????

???????? /**

???????? ?*?校驗(yàn)輸入

???????? ?* @param input?用戶輸入內(nèi)容

???????? ?* @return true代表輸入符合要求,false代表不符合

???????? ?*/

???????? private static boolean checkInput(String input){

?????????????????? if(input == null){

??????????????????????????? return false;

?????????????????? }

?????????????????? try{

??????????????????????????? int n = Integer.parseInt(input);

??????????????????????????? if(n >= 2){

???????????????????????????????????? return true;

??????????????????????????? }else{

???????????????????????????????????? return false;

??????????????????????????? }

?????????????????? }catch(Exception e){

??????????????????????????? return false;?//輸入不是整數(shù)

?????????????????? }

???????? }

????????

???????? /**

???????? ?*?向服務(wù)器端發(fā)送數(shù)據(jù)

???????? ?* @param data?數(shù)據(jù)內(nèi)容

???????? ?*/

???????? private static void send(byte[] data){

?????????????????? try{

??????????????????????????? os.write(data);

?????????????????? }catch(Exception e){}

???????? }

????????

???????? /**

???????? ?*?接收服務(wù)器端反饋

???????? ?* @return?反饋數(shù)據(jù)

???????? ?*/

???????? private static byte[] receive(){

?????????????????? byte[] b = new byte[1024];

?????????????????? try {

??????????????????????????? int n = is.read(b);

??????????????????????????? byte[] data = new byte[n];

??????????????????????????? //復(fù)制有效數(shù)據(jù)

??????????????????????????? System.arraycopy(b, 0, data, 0, n);

??????????????????????????? return data;

?????????????????? } catch (Exception e){}

?????????????????? return null;

???????? }

????????

???????? /**

???????? ?*?解析協(xié)議數(shù)據(jù)

???????? ?* @param data?協(xié)議數(shù)據(jù)

???????? ?*/

???????? private static void parse(byte[] data){

?????????????????? if(data == null){

??????????????????????????? System.out.println("服務(wù)器端反饋數(shù)據(jù)不正確!");

??????????????????????????? return;

?????????????????? }

?????????????????? byte value = data[0]; //取第一個(gè)byte

?????????????????? //按照協(xié)議格式解析

?????????????????? switch(value){

?????????????????? case 0:

??????????????????????????? System.out.println("質(zhì)數(shù)");

??????????????????????????? break;

?????????????????? case 1:

??????????????????????????? System.out.println("不是質(zhì)數(shù)");

??????????????????????????? break;

?????????????????? case 2:

??????????????????????????? System.out.println("協(xié)議格式錯(cuò)誤");

??????????????????????????? break;

?????????????????? }

???????? }

????????

???????? /**

???????? ?*?關(guān)閉流和連接

???????? ?*/

???????? private static void close(){

?????????????????? try{

??????????????????????????? br.close();

??????????????????????????? is.close();

??????????????????????????? os.close();

??????????????????????????? socket.close();

?????????????????? }catch(Exception e){

??????????????????????????? e.printStackTrace();

?????????????????? }

???????? }

}

?????????在該代碼中,將程序的功能使用方法進(jìn)行組織,使得結(jié)構(gòu)比較清晰,核心的邏輯流程在main方法中實(shí)現(xiàn)。

?????????以TCP方式實(shí)現(xiàn)的服務(wù)器端的代碼如下:

?????????????????? package example1;

import java.net.*;

/**

?*?以TCP方式實(shí)現(xiàn)的質(zhì)數(shù)判別服務(wù)器端

?*/

public class TCPPrimeServer {

???????? public static void main(String[] args) {

?????????????????? final int PORT = 10005;

?????????????????? ServerSocket ss?= null;

?????????????????? try {

??????????????????????????? ss = new ServerSocket(PORT);

??????????????????????????? System.out.println("服務(wù)器端已啟動(dòng):");

??????????????????????????? while(true){

???????????????????????????????????? Socket s = ss.accept();

???????????????????????????????????? new PrimeLogicThread(s);

??????????????????????????? }

?????????????????? } catch (Exception e) {}

?????????????????? finally{

??????????????????????????? try {

???????????????????????????????????? ss.close();

??????????????????????????? } catch (Exception e2) {}

?????????????????? }

??????????????????

???????? }

}

package example1;

import java.io.*;

import java.net.*;

/**

?*?實(shí)現(xiàn)質(zhì)數(shù)判別邏輯的線程

?*/

public class PrimeLogicThread extends Thread {

???????? Socket socket;

???????? InputStream is;

???????? OutputStream os;

????????

???????? public PrimeLogicThread(Socket socket){

?????????????????? this.socket = socket;

?????????????????? init();

?????????????????? start();

???????? }

???????? /**

???????? ?*?初始化

???????? ?*/

???????? private void init(){

?????????????????? try{

??????????????????????????? is = socket.getInputStream();

??????????????????????????? os = socket.getOutputStream();

?????????????????? }catch(Exception e){}

???????? }

????????

???????? public void run(){

?????????????????? while(true){

??????????????????????????? //接收客戶端反饋

??????????????????????????? byte[] data = receive();

??????????????????????????? //判斷是否是退出

??????????????????????????? if(isQuit(data)){

???????????????????????????????????? break;?//結(jié)束循環(huán)

??????????????????????????? }

??????????????????????????? //邏輯處理

?????????????????? ???????? byte[] b = logic(data);

??????????????????????????? //反饋數(shù)據(jù)

??????????????????????????? send(b);

?????????????????? }

?????????????????? close();

???????? }

????????

???????? /**

???????? ?*?接收客戶端數(shù)據(jù)

???????? ?* @return?客戶端發(fā)送的數(shù)據(jù)

???????? ?*/

???????? private?byte[] receive(){

?????????????????? byte[] b = new byte[1024];

?????????????????? try {

??????????????????????????? int n = is.read(b);

??????????????????????????? byte[] data = new byte[n];

??????????????????????????? //復(fù)制有效數(shù)據(jù)

??????????????????????????? System.arraycopy(b, 0, data, 0, n);

??????????????????????????? return data;

?????????????????? } catch (Exception e){}

?????????????????? return null;

???????? }

????????

???????? /**

???????? ?*?向客戶端發(fā)送數(shù)據(jù)

???????? ?* @param data?數(shù)據(jù)內(nèi)容

???????? ?*/

???????? private void send(byte[] data){

?????????????????? try{

??????????????????????????? os.write(data);

?????????????????? }catch(Exception e){}

???????? }

????????

???????? /**

???????? ?*?判斷是否是quit

???????? ?* @return?是返回true,否則返回false

???????? ?*/

???????? private boolean isQuit(byte[] data){

?????????????????? if(data == null){

??????????????????????????? return false;

?????????????????? }else{

??????????????????????????? String s = new String(data);

??????????????????????????? if(s.equalsIgnoreCase("quit")){

???????????????????????????????????? return true;

??????????????????????????? }else{

???????????????????????????????????? return false;

??????????????????????????? }

?????????????????? }

???????? }

????????

???????? private byte[] logic(byte[] data){

?????????????????? //反饋數(shù)組

?????????????????? byte[] b = new byte[1];

?????????????????? //校驗(yàn)參數(shù)

?????????????????? if(data == null){

??????????????????????????? b[0] = 2;

??????????????????????????? return b;

?????????????????? }

?????????????????? try{

??????????????????????????? //轉(zhuǎn)換為數(shù)字

??????????????????????????? String s = new String(data);

??????????????????????????? int n = Integer.parseInt(s);

??????????????????????????? //判斷是否是質(zhì)數(shù)

??????????????????????????? if(n >= 2){

???????????????????????????????????? boolean flag = isPrime(n);

???????????????????????????????????? if(flag){

?????????????????? ??????????????????????????? b[0] = 0;

???????????????????????????????????? }else{

?????????????????????????????????????????????? b[0] = 1;

???????????????????????????????????? }

??????????????????????????? }else{

???????????????????????????????????? b[0] = 2;?//格式錯(cuò)誤

???????????????????????????????????? System.out.println(n);

??????????????????????????? }

?????????????????? }catch(Exception e){

??????????????????????????? e.printStackTrace();

??????????????????????????? b[0] = 2;

?????????????????? }

?????????????????? return b;

???????? }

????????

???????? /**

???????? ?*

???????? ?* @param n

???????? ?* @return

???????? ?*/

???????? private boolean isPrime(int n){

?????????????????? boolean b = true;

?????????????????? for(int i = 2;i <= Math.sqrt(n);i++){

??????????????????????????? if(n % i == 0){

???????????????????????????????????? b = false;

???????????????????????????????????? break;

??????????????????????????? }

?????????????????? }

?????????????????? return b;

???????? }

????????

???????? /**

???????? ?*?關(guān)閉連接

???????? ?*/

???????? private void close(){

?????????????????? try {

??????????????????????????? is.close();

??????????????????????????? os.close();

??????????????????????????? socket.close();

?????????????????? } catch (Exception e){}

???????? }

}

?????????本示例使用的服務(wù)器端的結(jié)構(gòu)和前面示例中的結(jié)構(gòu)一致,只是邏輯線程的實(shí)現(xiàn)相對來說要復(fù)雜一些,在線程類中的logic方法中實(shí)現(xiàn)了服務(wù)器端邏輯,根據(jù)客戶端發(fā)送過來的數(shù)據(jù),判斷是否是質(zhì)數(shù),然后根據(jù)判斷結(jié)果按照協(xié)議格式要求,生成客戶端反饋數(shù)據(jù),實(shí)現(xiàn)服務(wù)器端要求的功能。

?

?

猜數(shù)字小游戲

?????????下面這個(gè)示例是一個(gè)猜數(shù)字的控制臺小游戲。該游戲的規(guī)則是:當(dāng)客戶端第一次連接到服務(wù)器端時(shí),服務(wù)器端生產(chǎn)一個(gè)【0,50】之間的隨機(jī)數(shù)字,然后客戶端輸入數(shù)字來猜該數(shù)字,每次客戶端輸入數(shù)字以后,發(fā)送給服務(wù)器端,服務(wù)器端判斷該客戶端發(fā)送的數(shù)字和隨機(jī)數(shù)字的關(guān)系,并反饋比較結(jié)果,客戶端總共有5次猜的機(jī)會,猜中時(shí)提示猜中,當(dāng)輸入”quit”時(shí)結(jié)束程序。

?????????和 前面的示例類似,在進(jìn)行網(wǎng)絡(luò)程序開發(fā)時(shí),首先需要分解一下功能的實(shí)現(xiàn),覺得功能是在客戶端程序中實(shí)現(xiàn)還是在服務(wù)器端程序中實(shí)現(xiàn)。區(qū)分的規(guī)則一般是:客戶端 程序?qū)崿F(xiàn)接收用戶輸入等界面功能,并實(shí)現(xiàn)一些基礎(chǔ)的校驗(yàn)降低服務(wù)器端的壓力,而將程序核心的邏輯以及數(shù)據(jù)存儲等功能放在服務(wù)器端進(jìn)行實(shí)現(xiàn)。遵循該原則劃分 的客戶端和服務(wù)器端功能如下所示。

?????????客戶端程序功能列表:

1、?接收用戶控制臺輸入

2、?判斷輸入內(nèi)容是否合法

3、?按照協(xié)議格式發(fā)送數(shù)據(jù)

4、?根據(jù)服務(wù)器端的反饋給出相應(yīng)提示

?????????服務(wù)器端程序功能列表:

1、?接收客戶端發(fā)送數(shù)據(jù)

2、?按照協(xié)議格式解析數(shù)據(jù)

3、?判斷發(fā)送過來的數(shù)字和隨機(jī)數(shù)字的關(guān)系

4、?根據(jù)判斷結(jié)果生產(chǎn)協(xié)議數(shù)據(jù)

5、?將生產(chǎn)的數(shù)據(jù)反饋給客戶端

?????????在該示例中,實(shí)際使用的網(wǎng)絡(luò)命令也只有兩條,所以顯得協(xié)議的格式比較簡單。

?????????其中客戶端程序協(xié)議格式如下:

1、?將用戶輸入的數(shù)字轉(zhuǎn)換為字符串,然后轉(zhuǎn)換為byte數(shù)組

2、?發(fā)送“quit”字符串代表退出

?????????其中服務(wù)器端程序協(xié)議格式如下:

1、?反饋長度為1個(gè)字節(jié),數(shù)字0代表相等(猜中),1代表大了,2代表小了,其它數(shù)字代表錯(cuò)誤。

?????????實(shí)現(xiàn)該程序的代碼比較多,下面分為客戶端程序?qū)崿F(xiàn)和服務(wù)器端程序?qū)崿F(xiàn)分別進(jìn)行列舉。

?????????客戶端程序?qū)崿F(xiàn)代碼如下:

??????????????? ?package guess;

import java.net.*;

import java.io.*;

/**

?*?猜數(shù)字客戶端

?*/

public class TCPClient {

?public static void main(String[] args) {

???????? Socket socket = null;

???????? OutputStream os = null;

???????? InputStream is = null;

???????? BufferedReader br = null;

???????? byte[] data = new byte[2];

???????? try{

?????????????????? //建立連接

?????????????????? socket = new Socket(

???????????????????????????????????? "127.0.0.1",10001);

??????????????????

?????????????????? //發(fā)送數(shù)據(jù)

?????????????????? os= socket.getOutputStream();

??????????????????

?????????????????? //讀取反饋數(shù)據(jù)

?????????????????? is = socket.getInputStream();

??????????????????

?????????????????? //鍵盤輸入流

?????????????????? br = new BufferedReader(

???????????????????????????????????? new InputStreamReader(System.in));

??????????????????

?????????????????? //多次輸入

?????????????????? while(true){

??????????????????????????? System.out.println("請輸入數(shù)字:");

??????????????????????????? //接收輸入

??????????????????????????? String s = br.readLine();

??????????????????????????? //結(jié)束條件

??????????????????????????? if(s.equals("quit")){

???????????????????????????????????? os.write("quit".getBytes());

???????????????????????????????????? break;

??????????????????????????? }

??????????????????????????? //校驗(yàn)輸入是否合法

??????????????????????????? boolean b = true;

??????????????????????????? try{

???????????????????????????????????? Integer.parseInt(s);

??????????????????????????? }catch(Exception e){

???????????????????????????????????? b = false;

??????????????????????????? }

??????????????????????????? if(b){ //輸入合法

???????????????????????????????????? //發(fā)送數(shù)據(jù)

???????????????????????????????????? os.write(s.getBytes());

???????????????????????????????????? //接收反饋

???????????????????????????????????? is.read(data);

???????????????????????????????????? //判斷

???????????????????????????????????? switch(data[0]){

???????????????????????????????????? case 0:

?????????????????????????????????????????????? System.out.println("相等!祝賀你!");

?????????????????????????????????????????????? break;

???????????????????????????????????? case 1:

?????????????????????????????????????????????? System.out.println("大了!");

?????????????????????????????????????????????? break;

???????????????????????????????????? case 2:

?????????????????????????????????????????????? System.out.println("小了!");

?????????????????????????????????????????????? break;

???????????????????????????????????? default:

?????????????????????????????????????????????? System.out.println("其它錯(cuò)誤!");

???????????????????????????????????? }

???????????????????????????????????? //提示猜的次數(shù)

???????????????????????????????????? System.out.println("你已經(jīng)猜了" + data[1] + "次!");

???????????????????????????????????? //判斷次數(shù)是否達(dá)到5次

???????????????????????????????????? if(data[1] >= 5){

?????????????????????????????????????????????? System.out.println("你掛了!");

?????????????????????????????????????????????? //給服務(wù)器端線程關(guān)閉的機(jī)會

?????????????????????????????????????????????? os.write("quit".getBytes());

?????????????????????????????????????????????? //結(jié)束客戶端程序

?????????????????????????????????????????????? break;

???????????????????????????????????? }

??????????????????????????? }else{?//輸入錯(cuò)誤

???????????????????????????????????? System.out.println("輸入錯(cuò)誤!");

??????????????????????????? }

?????????????????? }

???????? }catch(Exception e){

?????????????????? e.printStackTrace();

???????? }finally{

?????????????????? try{

??????????????????????????? //關(guān)閉連接

??????????????????????????? br.close();

??????????????????????????? is.close();

??????????????????????????? os.close();

??????????????????????????? socket.close();

?????????????????? }catch(Exception e){

??????????????????????????? e.printStackTrace();

?????????????????? }

???????? }

?}

????? }

???在該示例中,首先建立一個(gè)到IP地址為127.0.0.1的端口為10001的連接,然后進(jìn)行各個(gè)流的初始化工作,將邏輯控制的代碼放入在一個(gè)while循環(huán)中,這樣可以在客戶端多次進(jìn)行輸入。在循環(huán)內(nèi)部,首先判斷用戶輸入的是否為quit字符串,如果是則結(jié)束程序,如果輸入不是quit,則首先校驗(yàn)輸入的是否是數(shù)字,如果不是數(shù)字則直接輸出“輸入錯(cuò)誤!”并繼續(xù)接收用戶輸入,如果是數(shù)字則發(fā)送給服務(wù)器端,并根據(jù)服務(wù)器端的反饋顯示相應(yīng)的提示信息。最后關(guān)閉流和連接,結(jié)束客戶端程序。

?????????服務(wù)器端程序的實(shí)現(xiàn)還是分為服務(wù)器控制程序和邏輯線程,實(shí)現(xiàn)的代碼分別如下:

??????????????? ?package guess;

import java.net.*;

/**

?* TCP連接方式的服務(wù)器端

?*?實(shí)現(xiàn)功能:接收客戶端的數(shù)據(jù),判斷數(shù)字關(guān)系

?*/

public class TCPServer {

?public static void main(String[] args) {

???????? try{

?????????????????? //監(jiān)聽端口

?????????????????? ServerSocket ss = new ServerSocket(10001);

?????????????????? System.out.println("服務(wù)器已啟動(dòng):");

?????????????????? //邏輯處理

?????????????????? while(true){

??????????????????????????? //獲得連接

??????????????????????????? Socket s = ss.accept();

??????????????????????????? //啟動(dòng)線程處理

??????????????????????????? new LogicThread(s);

?????????????????? }

??????????????????

???????? }catch(Exception e){

?????????????????? e.printStackTrace();

???????? }

?}

?????}

????? package guess;

import java.net.*;

import java.io.*;

import java.util.*;

/**

?*?邏輯處理線程

?*/

public class LogicThread extends Thread {

????? Socket s;

?????

????? static Random r = new Random();

?????

????? public LogicThread(Socket s){

????????????? this.s = s;

????????????? start();?//啟動(dòng)線程

????? }

?????

????? public void run(){

????????????? //生成一個(gè)[0,50]的隨機(jī)數(shù)

????????????? int randomNumber = Math.abs(r.nextInt() % 51);

????????????? //用戶猜的次數(shù)

????????????? int guessNumber = 0;

????????????? InputStream is = null;

????????????? OutputStream os = null;

????????????? byte[] data = new byte[2];

????????????? try{

?????????????????????? //獲得輸入流

?????????????????????? is = s.getInputStream();

?????????????????????? //獲得輸出流

?????????????????????? os = s.getOutputStream();

?????????????????????? while(true){?//多次處理

???????????????????????????????? //讀取客戶端發(fā)送的數(shù)據(jù)

???????????????????????????????? byte[] b = new byte[1024];

???????????????????????????????? int n = is.read(b);

???????????????????????????????? String send = new String(b,0,n);

???????????????????????????????? //結(jié)束判別

???????????????????????????????? if(send.equals("quit")){

????????????????????????????????????????? break;

???????????????????????????????? }

???????????????????????????????? //解析、判斷

???????????????????????????????? try{

????????????????????????????????????????? int num = Integer.parseInt(send);

????????????????????????????????????????? //處理

????????????????????????????????????????? guessNumber++; //猜的次數(shù)增加1

????????????????????????????????????????? data[1] = (byte)guessNumber;

????????????????????????????????????????? //判斷

????????????????????????????????????????? if(num > randomNumber){

?????????????????????????????????????????????????? data[0] = 1;

????????????????????????????????????????? }else if(num < randomNumber){

?????????????????????????????????????????????????? data[0] = 2;

????????????????????????????????????????? }else{

?????????????????????????????????????????????????? data[0] = 0;

?????????????????????????????????????????????????? //如果猜對

?????????????????????????????????????????????????? guessNumber = 0; //清零

?????????????????????????????????????????????????? randomNumber = Math.abs(r.nextInt() % 51);

????????????????????????????????????????? }

????????????????????????????????????????? //反饋給客戶端

????????????????????????????????????????? os.write(data);????????????????????????????????????

?????????????????????????????????????????

???????????????????????????????? }catch(Exception e){ //數(shù)據(jù)格式錯(cuò)誤

????????????????????????????????????????? data[0] = 3;

????????????????????????????????????????? data[1] = (byte)guessNumber;

????????????????????????????????????????? os.write(data);?//發(fā)送錯(cuò)誤標(biāo)識

????????????????????????????????????????? break;

???????????????????????????????? }

???????????????????????????????? os.flush();?? //強(qiáng)制發(fā)送

?????????????????????? }

??????????????????????

????????????? }catch(Exception e){

?????????????????????? e.printStackTrace();

????????????? }finally{

?????????????????????? try{

???????????????????????????????? is.close();

???????????????????????????????? os.close();

???????????????????????????????? s.close();

?????????????????????? }catch(Exception e){}

????????????? }

????? }

}

在 該示例中,服務(wù)器端控制部分和前面的示例中一樣。也是等待客戶端連接,如果有客戶端連接到達(dá)時(shí),則啟動(dòng)新的線程去處理客戶端連接。在邏輯線程中實(shí)現(xiàn)程序的 核心邏輯,首先當(dāng)線程執(zhí)行時(shí)生產(chǎn)一個(gè)隨機(jī)數(shù)字,然后根據(jù)客戶端發(fā)送過來的數(shù)據(jù),判斷客戶端發(fā)送數(shù)字和隨機(jī)數(shù)字的關(guān)系,然后反饋相應(yīng)的數(shù)字的值,并記憶客戶 端已經(jīng)猜過的次數(shù),當(dāng)客戶端猜中以后清零猜過的次數(shù),使得客戶端程序可以繼續(xù)進(jìn)行游戲。

總體來說,該程序示例的結(jié)構(gòu)以及功能都與上一個(gè)程序比較類似,希望通過比較這兩個(gè)程序,加深對于網(wǎng)絡(luò)編程的認(rèn)識,早日步入網(wǎng)絡(luò)編程的大門。

轉(zhuǎn)載于:https://www.cnblogs.com/wxy325/archive/2013/01/05/2845541.html

總結(jié)

以上是生活随笔為你收集整理的JAVA的网络编程【转】的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。