網(wǎng)絡(luò)編程有三個(gè)要素,分別是IP地址、端口號(hào)和通信協(xié)議。本文主要講述的是TCP與UDP這兩種通信協(xié)議,以及編程的實(shí)現(xiàn)。首先,我們需要了解一下IP地址、端口號(hào)、通信協(xié)議的相關(guān)知識(shí)。
一、IP地址
?
網(wǎng)絡(luò)中的計(jì)算機(jī)使用IP地址來(lái)進(jìn)行唯一標(biāo)識(shí),IP地址有IPv4和IPv6兩種類型。IPv4采用十進(jìn)制或二進(jìn)制表示形式,十進(jìn)制是一種比較常用的表示形式,如192.168.1.131,IPv6采用十六進(jìn)制表示形式,一般不常用。 如何查看IP地址相關(guān)信息:在Windows系統(tǒng)下,打開(kāi)cmd,輸入命令ipconfig,按回車即可查看。在Linux或Mac系統(tǒng)下,打開(kāi)終端,使用ifconfig命令,按回車即可查看。 ?
二、端口號(hào)
?
端口號(hào)是計(jì)算機(jī)中的應(yīng)用程序的一個(gè)整數(shù)數(shù)字標(biāo)號(hào),用來(lái)區(qū)分不同的應(yīng)用程序。 0 ~ 1023未被系統(tǒng)使用或保留的端口號(hào),1024 ~ 65535為有效的端口號(hào),也就是說(shuō)我們要對(duì)一些程序定義端口號(hào)的時(shí)候,要選擇1024 ~ 65535范圍內(nèi)的整數(shù)數(shù)字。比如,MySQL的端口號(hào)是3306,SQLServer的端口號(hào)是1433,查了一下Oracle的端口號(hào)是1521。一定要把這些數(shù)據(jù)庫(kù)對(duì)應(yīng)的端口號(hào),藏在深深的腦海里,以后在連接數(shù)據(jù)庫(kù)的時(shí)候,會(huì)使用到端口號(hào)。 ? ?
三、通信協(xié)議
?
說(shuō)得通俗一點(diǎn),通信協(xié)議就是網(wǎng)絡(luò)通信中的規(guī)則,分為TCP協(xié)議和UDP協(xié)議兩種。
第一種:TCP協(xié)議
英文名:Transmission Control Protocol 中文名:傳輸控制協(xié)議。 協(xié)議說(shuō)明:TCP是一種面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議。 舉例:打電話,需要雙方都接通,才能進(jìn)行對(duì)話。 特點(diǎn):效率低,數(shù)據(jù)傳輸比較安全。
第二種:UDP協(xié)議
英文名:User Datagram Protocol 中文名:數(shù)據(jù)報(bào)協(xié)議。 協(xié)議說(shuō)明:UDP是一種面向無(wú)連接的傳輸層通信協(xié)議。 舉例:發(fā)短信,不需要雙方建立連接,But,數(shù)據(jù)報(bào)的大小應(yīng)限制在64k以內(nèi)。 特點(diǎn):效率高,數(shù)據(jù)傳輸不安全,容易丟包。 ?
四、三要素關(guān)系圖與網(wǎng)絡(luò)模型圖
?
1、網(wǎng)絡(luò)編程三要素關(guān)系圖
注:圖中端口號(hào)、IP地址為演示,并非真實(shí)。 ? 2、OSI參考模型與TCP/IP參考模型
五、TCP編程
? ? TCP是基于字節(jié)流的傳輸層通信協(xié)議,所以TCP編程是基于IO流編程。 對(duì)于客戶端,我們需要使用Socket類來(lái)創(chuàng)建對(duì)象。對(duì)于服務(wù)器端,我們需要使用ServerSocket來(lái)創(chuàng)建對(duì)象,通過(guò)對(duì)象調(diào)用accept()方法來(lái)進(jìn)行監(jiān)聽(tīng)是否有客戶端訪問(wèn)。 客戶端與服務(wù)器端圖解:
客戶端與服務(wù)器端實(shí)現(xiàn)步驟: 前提:創(chuàng)建一個(gè)項(xiàng)目,在項(xiàng)目中創(chuàng)建兩個(gè)模塊(model),一個(gè)模塊用來(lái)放客戶端相關(guān)代碼,一個(gè)模塊用來(lái)放服務(wù)器端相關(guān)代碼。 目錄結(jié)構(gòu)如下圖:
客戶端: 1、創(chuàng)建Socket對(duì)象,并指定服務(wù)器端應(yīng)用程序的端口號(hào)和服務(wù)器端主機(jī)的IP地址。 2、使用Socket的對(duì)象調(diào)用getOutputStream()方法來(lái)獲取字節(jié)輸出流對(duì)象。 3、調(diào)用字節(jié)輸出流的write(byte[] buf)或者write(int b)向服務(wù)器發(fā)送指定數(shù)據(jù)。 4、記得關(guān)閉流。 ? 服務(wù)器端: 1、創(chuàng)建ServerSocket對(duì)象,并指定該應(yīng)用程序的端口號(hào),端口號(hào)必須和客戶端指定的端口號(hào)一樣。 2、使用ServerSocket對(duì)象的accept()方法來(lái)監(jiān)聽(tīng)客戶端發(fā)送過(guò)來(lái)的請(qǐng)求,返回值為Socket對(duì)象。 3、調(diào)用Socket對(duì)象的getInputStream()方法獲取字節(jié)輸入流對(duì)象 4、調(diào)用字節(jié)輸入流對(duì)象的read(byte[] buf)或read()方法獲取數(shù)據(jù)。 5、記得關(guān)閉流。 ? 實(shí)例: 客戶端向服務(wù)器端發(fā)送信息,并顯示在服務(wù)器端。 Client類(客戶端):
package?cn.tkrnet.client; import?java.io.IOException; import?java.io.OutputStream; import?java.net.Socket; public?class?Client?{? ?
??public?static?void?main(String[]?args)?throws?IOException?{ ???????? ????????//創(chuàng)建Socket對(duì)象,指定要發(fā)送到服務(wù)器端的IP地址,以及服務(wù)器端應(yīng)用程序接收的端口號(hào) ????
???//localhost代表本機(jī)IP地址 ????????Socket?client?=?new?Socket("localhost",9000); ???????? ??
??????//獲取輸出流,用于向服務(wù)器端發(fā)送數(shù)據(jù) ????????OutputStream?os?=?client.getOutputStream(); ????????
????????os.write("Java?is?my?friend?!".getBytes()); ????????System.out.println("信息已發(fā)送"); ????????//關(guān)閉流 ????
????os.close(); ????????client.close(); ????} } Server類(服務(wù)器端):
package?cn.tkrnet.server; import?java.io.IOException; import?java.io.InputStream; import?java.net.ServerSocket; import?java.net.Socket; public?class?Server?{
????public?static?void?main(String[]?args)?throws?IOException?{ ???
?????System.out.println("--服務(wù)器端已開(kāi)啟--"); ??????
??//創(chuàng)建ServerSocket對(duì)象,這里的端口號(hào)必須與客戶端的端口號(hào)相同 ????
????ServerSocket?server?=?new?ServerSocket(9000); ???
?????//調(diào)用方法accept(),用來(lái)監(jiān)聽(tīng)客戶端發(fā)來(lái)的請(qǐng)求 ??????
??Socket?socket?=?server.accept(); ???
?????//獲取輸入流對(duì)象 ????????InputStream?is?=?socket.getInputStream(); ????
????//讀取輸入流中的數(shù)據(jù) ????????int?b?=?0; ????
????while?((b?=is.read())?!=?-1){ ?????????
???System.out.print((char)b); ??????
??} ????????//關(guān)閉流 ????????is.close(); ?????
???socket.close(); ?????
???server.close();
????} } 提示:在運(yùn)行程序時(shí),一定要先運(yùn)行服務(wù)器端的程序代碼,再運(yùn)行客戶端的程序代碼。因?yàn)榭蛻舳艘蚍?wù)器發(fā)送請(qǐng)求,前提是服務(wù)器端要處于開(kāi)啟狀態(tài)。 Server類(服務(wù)器端)運(yùn)行結(jié)果:
信息已發(fā)送 Client類(客戶端)運(yùn)行后,Server類(服務(wù)器端)收到信息,運(yùn)行結(jié)果:
--服務(wù)器端已開(kāi)啟-- Java?is?my?friend?! 實(shí)例分析: 服務(wù)器端啟動(dòng)后,服務(wù)器端的accept()方法一直處于監(jiān)聽(tīng)狀態(tài),直到客戶端連接了服務(wù)器,服務(wù)器端再?gòu)牧髦凶x取客戶端發(fā)來(lái)的數(shù)據(jù)。恕我直言,這是一個(gè)超級(jí)無(wú)敵簡(jiǎn)單的單向通信實(shí)例。 ?
六、UDP編程
?
UDP使用數(shù)據(jù)報(bào)進(jìn)行數(shù)據(jù)傳輸,沒(méi)有客戶端與服務(wù)器端之分,只有發(fā)送方與接收方,兩者哪個(gè)先啟動(dòng)都不會(huì)報(bào)錯(cuò),但是會(huì)出現(xiàn)數(shù)據(jù)丟包現(xiàn)象。發(fā)送的內(nèi)容有字?jǐn)?shù)限制,大小必須限制在64k以內(nèi)。 ? 發(fā)送方與接收方實(shí)現(xiàn)步驟: 前提:創(chuàng)建一個(gè)項(xiàng)目,在項(xiàng)目中創(chuàng)建兩個(gè)模塊(model),一個(gè)模塊用來(lái)放發(fā)送方相關(guān)代碼,一個(gè)模塊用來(lái)放接收方相關(guān)代碼。 目錄結(jié)構(gòu)如下圖:
發(fā)送方: 1、創(chuàng)建DatagramSocket對(duì)象,可以指定應(yīng)用程序的端口號(hào),也可以不指定。 2、準(zhǔn)備需要發(fā)送的數(shù)據(jù)。 3、創(chuàng)建DatagramPacket對(duì)象,用來(lái)對(duì)發(fā)送的數(shù)據(jù)進(jìn)行打包,需要指定發(fā)送內(nèi)容、發(fā)送多少、發(fā)送到哪里和接收方的端口號(hào)四個(gè)參數(shù)。 4、調(diào)用DatagramSocket對(duì)象的send()方法發(fā)送數(shù)據(jù)。 5、記得關(guān)閉流。 ? 接收方: 1、創(chuàng)建DatagramSocket對(duì)象,指定接收方的端口號(hào),這個(gè)必須指定。 2、創(chuàng)建一個(gè)byte類型數(shù)組,用來(lái)接收發(fā)送方發(fā)送過(guò)來(lái)的數(shù)據(jù)。 3、創(chuàng)建DatagramPacket對(duì)象,準(zhǔn)備接收數(shù)據(jù)。 4、調(diào)用DatagramSocket對(duì)象的receive()方法用于接收數(shù)據(jù)。 5、使用String類的構(gòu)造方法將byte類型的數(shù)組中的數(shù)據(jù)轉(zhuǎn)化成String類型并顯示。 6、記得關(guān)閉流。 ? 實(shí)例: 發(fā)送方發(fā)送信息,接收方接收信息,并顯示。 Sender類(發(fā)送方):
package?cn.tkrnet.Sender; import?java.io.IOException; import?java.net.*; public?class?Sender?{ ????public?static?void?main(String[]?args)?throws?IOException?{ ????????//創(chuàng)建接受或發(fā)送的數(shù)據(jù)報(bào)套接字,并指定發(fā)送方的端口號(hào)為7770 ????????DatagramSocket?ds?=?new?DatagramSocket(7770);???//端口號(hào)也可以不指定 ????????System.out.println("---發(fā)送方---"); ????????//創(chuàng)建數(shù)據(jù)報(bào)對(duì)象,用來(lái)發(fā)送數(shù)據(jù) ????????byte[]?b?=?"Java is my friend !".getBytes(); ????????//8800為接收方的端口號(hào),netAddress.getByName("localhost")是獲取主機(jī)的IP地址 ????????DatagramPacket?dp?=?new?DatagramPacket(b,b.length,?InetAddress.getByName("localhost"),7788); ????????ds.send(dp);????//發(fā)送數(shù)據(jù)報(bào) ????????System.out.println("數(shù)據(jù)已發(fā)送"); ????????//關(guān)閉流 ????????ds.close(); ????} } Receiver類(接收方)
package?cn.tkrnet.receiver; import?java.io.IOException; import?java.net.DatagramPacket; import?java.net.DatagramSocket; public?class?Receiver?{ ????public?static?void?main(String[]?args)?throws?IOException?{ ????????System.out.println("---接收方---"); ????????//創(chuàng)建數(shù)據(jù)報(bào)套接字對(duì)象,指定的端口號(hào)要和發(fā)送方發(fā)送數(shù)據(jù)的端口號(hào)相同 ????????//?(不是發(fā)送方的端口號(hào)7770,是發(fā)送方發(fā)送數(shù)據(jù)的端口號(hào)7788) ????????DatagramSocket?ds?=?new?DatagramSocket(7788); ????????//創(chuàng)建接收數(shù)據(jù)報(bào)的對(duì)象 ????????byte[]?b?=?new?byte[1024]; ????????DatagramPacket?dp?=?new?DatagramPacket(b,b.length); ????????//接收數(shù)據(jù) ????????ds.receive(dp); ????????System.out.println(new?String(b,0,dp.getLength())); ????????//關(guān)閉流 ????????ds.close(); ????} } 提示:在運(yùn)行程序時(shí),先運(yùn)行發(fā)送方程序,還是先運(yùn)行接收方程序都不會(huì)報(bào)錯(cuò),但是有可能會(huì)出現(xiàn)數(shù)據(jù)丟包,一般我們都先運(yùn)行接收方的程序代碼,再運(yùn)行發(fā)送方的程序代碼。 Receiver類(接收方)運(yùn)行結(jié)果:
---接收方--- Sender類(發(fā)送方)運(yùn)行結(jié)果:
---發(fā)送方--- 數(shù)據(jù)已發(fā)送 Sender類(發(fā)送方)運(yùn)行后,Receiver類(接收方)接收到信息,運(yùn)行結(jié)果:
????---接收方--- ??? Java is my friend ! 實(shí)例分析: 只有接收方先啟動(dòng)運(yùn)行,才會(huì)存在端口號(hào)為7788的程序,發(fā)送方才能發(fā)送數(shù)據(jù)到指定端口號(hào)7788,接收方才能接收數(shù)據(jù)。不瞞你說(shuō),這也是個(gè)超級(jí)無(wú)敵簡(jiǎn)單的單向通信實(shí)例。 ?
編輯:黃飛
評(píng)論
查看更多