網絡程式設計
一、網絡模型
1、TCP/IP參考模型圖

2、網絡程式設計三要素
舉例:
我想和林青霞說話了。腫麼辦?
A:我要找到林青霞。(電話号碼等資訊)
這就像一台電腦要和另一台電腦通信,必須要有對方的IP位址
B:對她說話,要對耳朵說。
這裡的“耳朵”指的是端口号,一台計算機上的一個應用程式以端口号作為辨別
C:我說什麼呢?"I Love You"
但是,她沒學過英語,聽不懂。
我沒必要說英語,說漢語就可以了:我愛你
這裡我說漢語,林青霞才能聽懂,這漢語是我和林青霞的溝通方式,而計算機互相交流,就得要一個規定,這個規定就是各種協定,就像國家的各種語言。
一句話:網絡程式設計的三要素是IP位址、端口、協定
(1)先聊一聊IP位址是怎麼回事?
定義:網絡中計算機的唯一辨別。
因為計算機隻能識别二進制的資料,是以我們的IP位址應該是一個二進制的資料。但是呢,我們配置的IP位址确不是二進制的,為什麼呢?
IP:192.168.1.100
換算:11000000 10101000 00000001 01100100
假如真是:11000000 10101000 00000001 01100100的話。
我們如果用的時候要配置該IP位址,記憶起來就比較的麻煩。
是以,為了友善表示IP位址,我們就把IP位址的每一個位元組上的資料換算成十進制,然後用.分開來表示:"點分十進制"
IP位址的組成:網絡号段+主機号段
A類:第一号段為網絡号段+後三段的主機号段
一個網絡号:256*256*256 = 16777216
B類:前二号段為網絡号段+後二段的主機号段
一個網絡号:256*256 = 65536
C類:前三号段為網絡号段+後一段的主機号段
一個網絡号:256
IP位址的分類:
A類 1.0.0.1---127.255.255.254
(1)10.X.X.X是私有位址(私有位址就是在網際網路上不使用,而被用在區域網路絡中的位址)
(2)127.X.X.X是保留位址,用做循環測試用的。
B類 128.0.0.1---191.255.255.254172.16.0.0---172.31.255.255是私有位址。169.254.X.X是保留位址。
C類 192.0.0.1---223.255.255.254192.168.X.X是私有位址
D類 224.0.0.1---239.255.255.254
E類 240.0.0.1---247.255.255.254
兩個DOS指令:
ipconfig 檢視本機ip位址
ping 後面跟ip位址。測試本機與指定的ip位址間的通信是否有問題
特殊的IP位址:
127.0.0.1 回環位址(表示本機)
x.x.x.255 廣播位址
x.x.x.0 網絡位址
(2)端口号:
正在運作的程式的辨別。比如QQ的端口号是4000,當然QQ的端口号不止一個。因為你一台電腦同時能登入的QQ号不止一個,如果沒記錯,一台電腦最多可以
登入10個QQ,因為QQ隻有10個端口号。
有效端口:0~65535,其中0~1024系統使用或保留端口。
3、網絡通訊過程
(1)找到對方 IP。
(2)資料要發到對方指定的應用程式上,為了辨別這些應用程式,是以給這些網絡應用程式都用數字進行了辨別。為了友善稱呼這個數字,叫做端口。(邏輯端口)
4、UDP-TCP
UDP 特點:(面向無連接配接)(聊天)
(1)将資料及源和目的封裝成資料包中,不需要建立連接配接。(封包,無連接配接)
(2)每個資料包的大小限制在64k内。(小資料)
(3)因無連接配接,是不可靠協定。(不可靠,丢資料)
(4)不需要建立連接配接,速度快。(速度快)
TCP 特點:(面向連接配接)(下載下傳)
(1)建立連接配接,形成傳輸資料的通道。(傳輸,連接配接)
(2)在連接配接中進行大資料量傳輸。(大資料)
(3)通過三次捂手完成連接配接,是可靠協定。(可靠。在? 我在!我知道你在了)
(4)必須建立連接配接,效率會稍低。(速度慢)
二、網絡通信基礎類
1、InetAddress
JAVA提供了InetAddress類代表IP位址,InetAddress類下還有兩個子類,Inet4Address,Inet6Address,它們分别代表Internet Protocol version 4(IPv4)位址和Internet Protocol version 6(IPv6)位址
InetAddress類沒有構造器,而是提供了兩個靜态方法來擷取InetAddress的對象
public static InetAddress getByName(String host)throws UnknownHostException
根據主機擷取對應的InetAddress 對象
public static InetAddress[] getAllByName(String host)throws UnknownHostException
在給定主機名的情況下,根據系統上配置的名稱服務傳回其 IP 位址所組成的數組。 比如百度的主機不止一台,是以傳回多個。
此外InetAddress還提供了一個isReachable的方法。
public boolean isReachable(int timeout) throws IOException
測試是否可以達到該位址。實作盡最大努力試圖到達主機,但防火牆和伺服器配置可能阻塞請求,使其在某些特定的端口可以通路時處于不可到達狀态。
如果可以獲得權限,則典型實作将使用 ICMP ECHO REQUEST;否則它将試圖在目标主機的端口 7 (Echo) 上建立 TCP 連接配接。
package cn.wangjing.internet;
import java.io.IOException;
import java.net.InetAddress;
public class InetAddressTest {
public static void main(String[] args) throws IOException {
// 根據主機名來擷取對應的InetAddress執行個體
InetAddress ip = InetAddress.getByName("www.baidu.com");
// 判斷是否可達
System.out.println("baidu是否可達:" + ip.isReachable(2000));
// 擷取該InetAddress執行個體的IP字元串
System.out.println(ip.getHostAddress());
// 根據原始IP位址來擷取對應的InetAddress執行個體
InetAddress local = InetAddress.getByAddress(new byte[]{127,0,0,1});
System.out.println("本機是否可達:" + local.isReachable(5000));
// 擷取該InetAddress執行個體對應的全限定域名
System.out.println(local.getCanonicalHostName());
}
}
上面程式簡單示範了InetAddress類幾個方法用法,InetAddress本身并不複雜,但是它代表了一個IP位址對象,是網絡通信的基礎,在網絡通信中會大量使用該類。
2、URLDecoder 和URLEncoder
URLDecoder和URLEncoder用于完成普通字元串和application/x-www-form-urlencoded MIME 字元串之間的互相轉換
在介紹application/x-www-form-urlencoded MIME 字元串之前,先使用www.google.com搜尋關鍵字“黑馬程式員”,當我們搜尋的關鍵字包含中文時,這些關鍵字就變成了“亂碼”,實際上這不是亂碼,這就是所謂的application/x-www-form-urlencoded MIME字元串。當URL位址裡包含非西歐字元的字元串時,系統會将這些非西歐字元串轉換成我們所見的“亂碼”。
那麼程式設計過程中可能涉及将普通字元串和這種特殊字元串的相關轉換,這就需要使用URLDecoder和URLEncoder類,其中:
URLDecoder類包含一個decode(String s,String enc)靜态方法,它可以将看上去是亂碼的特殊字元串轉轉成普通字元串。
URLEncoder類包含一個encode(String s,String enc)靜态方法,它可以将普通字元串轉換成application/x-www-form-urlencoded MIME字元串。
下面程式示範了如何将“亂碼”轉換成普通字元串,并示範了如何将普通字元串轉換成application/x-www-form-urlencoded MIME字元串。
public class URLDecoderTest
{
public static void main(String[] args)
throws Exception
{
// 将application/x-www-form-urlencoded字元串
// 轉換成普通字元串
// 其中的字元串直接從圖17.3所示視窗複制過來
String keyWord = URLDecoder.decode(
"%B7%E8%BF%F1java", "GBK");
System.out.println(keyWord);
// 将普通字元串轉換成
// application/x-www-form-urlencoded字元串
String urlStr = URLEncoder.encode(
"瘋狂Android講義" , "GBK");
System.out.println(urlStr);
}
}
3、使用URL
通常情況而言,URL可以由協定名、主機、端口和資源組成。即滿足如下格式:
protocol://host:port/resourceName 例如如下的URL位址:
http://www.oneedu.cn/Index.htm URL類提供了多個構造器用于建立URL對象,一旦獲得了URL對象之後,可以調用如下方法來通路該URL對應的資源:
String getFile():擷取此URL的資源名。
String getHost():擷取此URL的主機名。
String getPath():擷取此URL的路徑部分。
int getPort():擷取此 URL 的端口号。
String getProtocol():擷取此 URL 的協定名稱。
String getQuery():擷取此 URL 的查詢字元串部分。
URLConnection openConnection():傳回一個URLConnection對象,它表示到URL所引用的遠端對象的連接配接。
InputStream openStream():打開與此URL的連接配接,并傳回一個用于讀取該URL資源的InputStream.
URL對象中前面幾個方法都非常容易了解,而該對象提供的openStream()可以讀取該URL資源的InputStream,通過該方法可以非常友善地讀取遠端資源--甚至實作多線程下載下傳。
此外URL這個類在android程式設計中是非常常用的,是以得掌握。
二、網絡程式編寫案例
1、基于UDP協定編寫一個資料接收和發送的程式
資料的發送端程式如下:
package cn.wangjing.internet;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/*
* 資料來自于鍵盤錄入
* 鍵盤錄入資料要自己控制錄入結束。
*/
public class SendDemo {
public static void main(String[] args) throws IOException {
// 建立發送端的Socket對象
DatagramSocket ds = new DatagramSocket();
// 封裝鍵盤錄入資料
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while ((line = br.readLine()) != null) {
if ("886".equals(line)) {
break;
}
// 建立資料并打包
byte[] bys = line.getBytes();
// DatagramPacket dp = new DatagramPacket(bys, bys.length,
// InetAddress.getByName("192.168.12.92"), 12345);
DatagramPacket dp = new DatagramPacket(bys, bys.length,
InetAddress.getByName("192.168.12.255"), 12345);
// 發送資料
ds.send(dp);
}
// 釋放資源
ds.close();
}
}
資料的接收端程式如下:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/*
* 多次啟動接收端:
* java.net.BindException: Address already in use: Cannot bind
* 端口被占用。
*/
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
// 建立接收端的Socket對象
DatagramSocket ds = new DatagramSocket(12345);
while (true) {
// 建立一個包裹
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
// 接收資料
ds.receive(dp);
// 解析資料
String ip = dp.getAddress().getHostAddress();
String s = new String(dp.getData(), 0, dp.getLength());
System.out.println("from " + ip + " data is : " + s);
}
// 釋放資源(這裡接收端應該一直開着等待接收資料,是不需要關閉)
// ds.close();
}
}
從上面的這個小Demo,看出網絡程式設計其實并不難,隻要記住網絡程式設計的步驟,都能搞得定。
再次強調一下服務端與用戶端程式設計的步驟
服務端: 1.建立接收端的Socket對象,2.建立一個包裹,3. 解析資料,4.釋放資源(看情況)
用戶端:1.建立發送端的Socket對象,2. 封裝鍵盤錄入資料,3.建立資料并打包,4.發送資料,5.釋放資源
2、基于TCP協定編寫一個資料接收和發送的程式
要求:資料從鍵盤錄入,輸入“886”,結束錄入
基于TCP協定的和基于UDP的步驟基本差不多,但也有差别,請仔細看看下面的Demo。
接收端程式:
public class TcpReceive {
public static void main(String[] args) throws Exception {
//建立服務端Socket對象
ServerSocket ss=new ServerSocket(12345);
while(true){
//監聽用戶端發來的請求
Socket socket = ss.accept();
BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line =null ;
while((line=br.readLine())!=null){
System.out.println(line);
}
OutputStream stream = socket.getOutputStream();
stream.write("資料已收到".getBytes());
stream.flush();
socket.close();
}
//關閉資源
//ss.close();
}
}
發送端:
public class TcpSend {
public static void main(String[] args) throws Exception {
Socket s = new Socket("wangjing-PC", 12345);
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while ((line = br.readLine()) != null) {
if (line.equals("886"))
break;
bw.write(line);
bw.newLine();
bw.flush();
}
//釋放資源
//bw.close(); //bw不需要關閉了,因為bw是由s來的,是以s關閉了,bw就不用關閉了
//br.close();
s.close();
}
}
這就是基于TCP的發送與接收程式,可以看到基于TCP發送端的套接字是Socket,接收端的套接字的ServerSocket,而基于UDP協定的,無論是接收端還是發送端的套接字都DatagramSocket。同時發現基于TCP的比基于UDP的要簡單一點,因為基于UDP的要對發送資料進行包裹,而TCP是不需要的。
注意:在程式運作的時候,一定要先運作服務端,因為TCP進行連接配接的時候要三次握手,如果服務端沒有開啟,用戶端發起請求就會被拒絕,此時程式抛出Exception in thread "main" java.net.ConnectException: Connection refused: connect。
3、基于TCP協定編寫一個檔案上傳程式
上傳一個叫InetAddressDemo.java的檔案到伺服器,并且,上傳成功之後,告訴用戶端檔案上傳成功
用戶端:
public class UploadClient {
public static void main(String[] args) throws IOException {
// 建立用戶端Socket對象
Socket s = new Socket("wangjing-PC", 11111);
// 封裝文本檔案
BufferedReader br = new BufferedReader(new FileReader(
"InetAddressDemo.java"));
// 封裝通道内流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
s.getOutputStream()));
String line = null;
while ((line = br.readLine()) != null) { // 阻塞
bw.write(line);
bw.newLine();
bw.flush();
}
//自定義一個結束标記
//bw.write("over");
//bw.newLine();
<span style="white-space:pre"> </span> //bw.flush();
//Socket提供了一個終止,它會通知伺服器你别等了,我沒有資料過來了
s.shutdownOutput();
// 接收回報
BufferedReader brClient = new BufferedReader(new InputStreamReader(
s.getInputStream()));
String client = brClient.readLine(); // 阻塞
System.out.println(client);
// 釋放資源
br.close();
s.close();
}
}
服務端:
public class UploadServer {
public static void main(String[] args) throws IOException {
// 建立伺服器端的Socket對象
ServerSocket ss = new ServerSocket(11111);
// 監聽用戶端連接配接
Socket s = ss.accept();// 阻塞
// 封裝通道内的流
BufferedReader br = new BufferedReader(new InputStreamReader(
s.getInputStream()));
// 封裝文本檔案
BufferedWriter bw = new BufferedWriter(new FileWriter("Copy.java"));
String line = null;
while ((line = br.readLine()) != null) { // 阻塞
// if("over".equals(line)){
// break;
// }
bw.write(line);
bw.newLine();
bw.flush();
}
// 給出回報
BufferedWriter bwServer = new BufferedWriter(new OutputStreamWriter(
s.getOutputStream()));
bwServer.write("檔案上傳成功");
bwServer.newLine();
bwServer.flush();
// 釋放資源
bw.close();
s.close();
}
}
4、基于TCP協定編寫一個檔案上傳程式
在實際情況中,一個伺服器對應了多個用戶端,所下面就模拟一下。
思路:通過while循環可以改進一個伺服器接收多個用戶端。
* 但是這個是有問題的。
* 如果是這種情況,假設我還有張三,李四,王五這三個人分别執行用戶端
* 張三:好好學習.avi(100M)256k
* 李四:天天向上.mp3(3M)1M
* 王五:ILoveJava.txt(1k)100M
現在張三的帶寬小,上傳的檔案卻很大,但是他又是第一上傳,那麼李四,王五肯定等的非常痛苦,是以服務端肯定需要采用多線程。
但是用戶端都是一樣的,下面貼出伺服器端的代碼。
服務端:
public class UploadServer {
public static void main(String[] args) throws IOException {
// 建立伺服器Socket對象
ServerSocket ss = new ServerSocket(11111);
while (true) {
Socket s = ss.accept();
new Thread(new UserThread(s)).start();
}
}
}
public class UserThread implements Runnable {
private Socket s;
public UserThread(Socket s) {
this.s = s;
}
@Override
public void run() {
try {
// 封裝通道内的流
BufferedReader br = new BufferedReader(new InputStreamReader(
s.getInputStream()));
// 封裝文本檔案
// BufferedWriter bw = new BufferedWriter(new
// FileWriter("Copy.java"));
// 為了防止名稱沖突
String newName = System.currentTimeMillis() + ".java";
BufferedWriter bw = new BufferedWriter(new FileWriter(newName));
String line = null;
while ((line = br.readLine()) != null) { // 阻塞
bw.write(line);
bw.newLine();
bw.flush();
}
// 給出回報
BufferedWriter bwServer = new BufferedWriter(
new OutputStreamWriter(s.getOutputStream()));
bwServer.write("檔案上傳成功");
bwServer.newLine();
bwServer.flush();
// 釋放資源
bw.close();
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
好的今天的部落格就寫到這,寫的不對的地方還請指正!!!