1. 程式人生 > >java(十三)網絡編程

java(十三)網絡編程

java

網絡編程


網絡模型:

OSI(Open System Interconnection開放系統互連)參考模型

OSI模型把網絡通信的工作分為7層。1至4層被認為是低層,這些層與數據移動密切相關。5至7層是高層,包含應用程序級的數據。

第1層物理層:原始比特流的傳輸。集線器、網線

第2層數據鏈路層:在此層將數據分幀,並處理流控制。本層指定拓撲結構並提供硬件尋址。網卡,網橋,交換機

第3層網絡層:本層通過尋址來建立兩個節點之間的連接,它包括通過互連網絡來路由和中繼數據。路由器,防火墻

第4層傳輸層:常規數據遞送-面向連接或無連接。包括全雙工或半雙工、流控制和錯誤恢復服務。計算機的進程和端口

第5層會話層:在兩個節點之間建立端連接。此服務包括建立連接是以全雙工還是以半雙工的方式進行設置,盡管可以在層4中處理雙工方式。建立會話,SESSION認證、斷點續傳

第6層表示層:格式化數據,以便為應用程序提供通用接口。這可以包括加密服務。編碼方式,圖像編解碼、URL字段傳輸編碼

第7層應用層:直接對應用程序提供服務,應用程序可以變化,但要包括電子消息傳輸。應用程序,如FTP,SMTP,HTTP

TCP/IP參考模型

這個模型只有四層。主機至網絡層,網際層,傳輸層,應用層。


網絡通訊要素:IP地址 端口號 傳輸協議

IP地址:InetAddress

網絡中設備的標識,長度為4個字節,也就是32個0和1的數字編碼,為了方便使用,寫成十進制。比如192.168.1.100,沒個數不超過255,因為一個字節,8個0和1的數最多表示255。

本地回環地址:127.0.0.1 它代表設備的本地虛擬接口,只用來訪問本機,不能訪問其他機器。一般都會用來檢查本地網絡協議、基本數據接口等是否正常的。

IP地址不易記憶,就有了主機名:localhost,就是計算機的名字,這個名字可以隨時更改。

端口號:用於標識進程的邏輯地址,不同進程的標識。

有效端口:0-65535,其中0-1024系統使用或保留端口。

傳輸協議:通訊的規則

常見協議:

TCP 傳輸控制協議

建立連接,形成數據傳輸通道

在連接中進行大量數據傳輸

通過三次握手完成鏈接,是可靠協議

必須建立連接,效率稍低

UDP 數據報文協議

將數據及源和目的封裝成數據包中,不需要建立連接

每個數據報的大小限制在64K內

因無連接,是不可靠協議

不需要建立連接,速度快


package day25;

import java.net.InetAddress;

import java.net.UnknownHostException;


public class IPDemo {


public static void main(String[] args) throws UnknownHostException {

//獲取本地主機IP對象

InetAddress ip=InetAddress.getLocalHost();

System.out.println(ip.getHostAddress());

System.out.println(ip.getHostName());

//獲取其他主機的IP地址對象

ip=InetAddress.getByName("www.baidu.com");

System.out.println(ip.getHostName());

System.out.println(ip.getHostAddress());

}


}



解析地址的過程:

平常上網IP地址不便於記憶,比如新浪的IP是10.0.0.1 上新浪網總不能輸入這個吧,太難記了。

我們輸的是www.sina.com 。 那網址就要和IP對應啊,所以輸了這個不是直接就去新浪了,先找DNS(域名解析器),它專門記錄域名和IP的對應關系。它會返回給你一個IP,然後你就拿著這個IP才去了新浪。

域名解析時先走本機的hosts域名解析文件,解析失敗後才走DNS。

所以平常有廣告網頁時,把他的域名復制,寫到hosts文件裏,對應的IP寫成本機127.0.0.1 ,域名解析先走本地文件,這樣就可以屏蔽廣告了。


Socket(可以理解為插頭,港口)

Socket就是為網絡服務提供的一種機制。通信的兩端都有Socket。

網絡通信就是Socket間的通信,數據在兩個Socket間通過IO傳輸。





UDP傳輸

DatagramSocket 用來發送和接收數據包的套接字。

DatagramPacket 此類表示數據報包。數據報包用來實現無連接包投遞服務。


UDP傳輸的發送端:

package day25;

import java.io.IOException;

import java.net.DatagramPacket;

import java.net.DatagramSocket;

import java.net.InetAddress;


public class UDPSendDemo {


/**

* UDP傳輸的發送端、

* 1.建立UDP的socket服務。

* 2.將要發送的數據封裝到數據包中

* 3.通過UDP的socket服務將數據包發送出去

* 4.關閉socket服務

*

* 註意:在我們寫的這個例子中得先打開接收端,而不是發生端。

*

*/

public static void main(String[] args) throws IOException {

System.out.println("發送端啟動。。。");

//1.建立UDP的socket服務。使用DatagramSocket對象

DatagramSocket ds=new DatagramSocket();

//2.將要發送的數據封裝到數據包中。使用DatagramPacket將數據封裝到該對象包中。

String str="UDP傳輸演示:哥們來了!";

byte[] buf=str.getBytes();

DatagramPacket dp=new DatagramPacket(buf, buf.length, InetAddress.getByName("192.168.31.74"), 10000);

//3.通過UDP的socket服務將數據包發送出去。使用send方法

ds.send(dp);

//4.關閉socket服務

ds.close();

}

}




UDP傳輸的接收端:

package day25;

import java.io.IOException;

import java.net.DatagramPacket;

import java.net.DatagramSocket;


public class UDPReceiveDemo {


/**

* UDP傳輸的接收端

* 1.建立UDP的socket服務。 因為是要接收數據,所以必須明確端口號,和發送端口號一致才行。不指定端口號就是隨機的,就收不到。

* 2.創建數據包,用於存儲接收到的數據,方便用數據包對象的方法解析數據。

* 3.使用socket服務的receive方法將接受的數據存儲到數據包中。

* 4.通過數據包對象的方法解析數據包中的數據。

* 5.關閉資源。

*

* 註意:在我們寫的這個例子中得先打開接收端,而不是發生端。

*/

public static void main(String[] args) throws IOException {

System.out.println("接收端啟動。。。");

//1.建立UDP的socket服務。使用DatagramSocket對象

DatagramSocket ds=new DatagramSocket(10000); //端口號必須和發送的端口號一致才行。

//2.創建數據包,用於存儲接收到的數據,方便用數據包對象的方法解析數據。

byte[] buf=new byte[1024];

DatagramPacket dp=new DatagramPacket(buf,buf.length);

//3.使用socket服務的receive方法將接受的數據存儲到數據包中。

ds.receive(dp); //阻塞式的方法。

//4.通過數據包對象的方法解析數據包中的數據。比如IP地址,端口,數據內容等

String ip=dp.getAddress().getHostAddress();

int port=dp.getPort();

String text=new String(dp.getData(),0,dp.getLength());

System.out.println(ip+":"+port+":"+text);

//5.關閉資源

ds.close();

}


}







練習:實現一個簡易的聊天軟件

package day25;

import java.net.DatagramSocket;

import java.net.SocketException;


public class ChatDemo {


/**

* 因為有可能發送的同時要接受,所以要用到多線程

* 要實現群聊功能,只需要把chatsend中發送的IP地址最後一個字節改為255即可。

* 改成255就會發送給該網段的所有人。

* @param args

* @throws SocketException

*/

public static void main(String[] args) throws SocketException {

DatagramSocket send=new DatagramSocket();

DatagramSocket receive=new DatagramSocket(8000);

ChatSend s=new ChatSend(send);

ChatReceive r=new ChatReceive(receive);

new Thread(s).start();

new Thread(r).start();

}


}

發送模塊

package day25;

import java.io.BufferedReader;

import java.io.InputStreamReader;

import java.net.DatagramPacket;

import java.net.DatagramSocket;

import java.net.InetAddress;


public class ChatSend implements Runnable {


private DatagramSocket ds;

public ChatSend(DatagramSocket ds){

this.ds=ds;

}

@Override

public void run() {

try {

BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in));

String line=null;

while((line=bufr.readLine())!=null){

byte[] buf=line.getBytes();

DatagramPacket dp=new DatagramPacket(buf, buf.length, InetAddress.getByName("192.168.31.74"), 10000);

ds.send(dp);

if("over".equals(line)){

break;

}

ds.close();

}

} catch (Exception e) {

}

}


}

接收模塊

package day25;

import java.net.DatagramPacket;

import java.net.DatagramSocket;


public class ChatReceive implements Runnable {

private DatagramSocket ds;

public ChatReceive(DatagramSocket ds){

this.ds=ds;

}

@Override

public void run() {

try{

while(true){

byte[] buf=new byte[1024];

DatagramPacket dp=new DatagramPacket(buf,buf.length);

ds.receive(dp);

String text=new String(dp.getData(),0,dp.getLength());

if(text.equals("over")){

System.out.println("對方已退出");

}

System.out.println(text);

}

}catch(Exception e){}

}

}



TCP傳輸


面向連接,要經過3次握手連接才能形成數據通道。

客戶端:

package day25;

import java.io.IOException;

import java.io.OutputStream;

import java.net.Socket;

import java.net.UnknownHostException;


public class TCPClientDemo {


/**

* 需求:客戶端發送數據到服務端。

* tcp要經過3次握手連接,才能形成數據傳輸通道。

*

* 註意:tcp是面向連接的,運行時必須先開服務端。

* @throws IOException

* @throws UnknownHostException

*/

public static void main(String[] args) throws UnknownHostException, IOException {

/*

* Tcp傳輸客戶端建立的過程:

* 1.創建tcp客戶端socket服務,使用的是cosket對象。建議該對象一創建就明確目的地,即要連接的主機。

* 2.如果建立連接成功,就會形成數據傳輸通道Socket流(網絡IO流)。

* Socket流是底層建立好的,既能輸入又能輸出。想要輸出或者輸入對象,可以找socket獲取。

* 可以通過getInputStream()和getOutputStream來獲取兩個字節流。

* 3.使用輸出流,將數據寫出。

* 4.關閉資源。

*/

//創建客戶端socket服務

Socket socket=new Socket("192.168.37.74",6666); //創建對象時最好就明確發送地址的IP和端口

//獲取socket流中的輸出流

OutputStream out=socket.getOutputStream();

//使用輸出流將指定的數據寫出去

out.write("tcp演示:哥們來了!".getBytes());

//關閉資源

socket.close();

}

}



服務端:

package day25;

import java.io.IOException;

import java.io.InputStream;

import java.net.ServerSocket;

import java.net.Socket;


public class TCPServerDemo {


/**

* 需求:服務端接收客戶端發送過來的數據,並打印在控制臺上。

* 註意:tcp是面向連接的,運行時必須先開服務端。

* @param args

* @throws IOException

*/

public static void main(String[] args) throws IOException {

/*

* Tcp傳輸服務端建立的過程:

* 1.創建服務端socket服務。通過ServerSocket對象。

* 2.服務端必須對外提供一個端口,否則客戶端無法連接。

* 3.獲取連接過來的客戶端對象。

* 4.通過客戶端對象獲取socket流讀取客戶端發來的數據並打印。

* 5.關閉資源。這個不僅要關服務端,還得關客戶端。

*/

//創建服務端對象

ServerSocket ss=new ServerSocket(95232);

//獲取連接過來的客戶端對象

Socket s=ss.accept(); //阻塞式方法

String ip=s.getInetAddress().getHostAddress();

//通過socket對象獲取輸入流,讀取客戶端發來的數據

InputStream in=s.getInputStream();

byte[] buf=new byte[1024];

int len=in.read(buf);

String text=new String(buf,0,len);

System.out.println("Server"+ip+"..."+text);

//關閉資源

s.close();

ss.close();

}


}





有時候會出現服務端和客戶端都在等的情況,原因是用了阻塞式方法,具體可能有以下幾個問題:

1.客戶端根本沒寫進去。只寫到了socket輸出流裏,沒寫到socket.getOutputStream()裏面。給socket流加個刷新就行。

2.缺少結束標記。服務端讀到換行才會結束讀取,返回數據,所以加上"\r\n"。註意服務端和客戶端都要加,因為兩邊都要讀取數據。


解決上面兩個問題的簡便方法就是socket輸出流直接用PrintWriter。

創建PrintWriter對象時直接加上true就能自動刷新了。

輸出時,直接用println方法,直接就能換行。





練習:


需求:客戶端輸入字母數據,發送給服務端,服務端收到後顯示到控制臺。

並將該數據轉成大寫返回給客戶端,直到客戶端輸入over,轉換結束。

說白了,就是創建一個英文大寫轉換服務器。

客戶端:

package day25;

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.PrintWriter;

import java.net.Socket;

import java.net.UnknownHostException;


public class TransClient {


public static void main(String[] args) throws UnknownHostException, IOException{

/*

* 思路:

* 1.客戶端要先有socket斷點

* 2.客戶端的數據源:鍵盤

* 3.客戶端的目的:socket

* 4.接收服務端的數據, 源:socket

* 5.將數據打印出來 目的:控制臺

* 6.在這些流中操作的數據都是文本數據。可以用字符流操作,方便。

*

* 步驟:

* 1.創建socket客戶端對象。

* 2.獲取鍵盤錄入

* 3.將錄入的信息發送給socket輸出流

*/

//創建socket客戶端對象

Socket s=new Socket("192.168.1.113",10005);

//獲取鍵盤錄入

BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in));

//socket輸出流

PrintWriter out=new PrintWriter(s.getOutputStream(),true); //true自動刷新

//也可以這樣寫 BufferedWriter bufw=new BufferedWriter(new OutputStreamWriter(s.getOutputStream(),true);

//socket輸入流,讀取服務端返回的數據

BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));

String line=null;

while((line=bufr.readLine())!=null){

if("over".equals(line))

break;

out.println(line);

//讀取服務端返回的數據

String upperStr=bufIn.readLine();

System.out.println(upperStr);

}

s.close();

}

}

服務端:

package day25;

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.PrintWriter;

import java.net.ServerSocket;

import java.net.Socket;


public class TransServer {

public static void main(String[] args) throws IOException{

/*

* 思路:

* 1.serversocket服務。

* 2.獲取socket對象。

* 3.讀取客戶端發過來的數據 源:socket

* 4.目的:顯示在控制臺上

* 5.將數據轉成大寫發回給客戶端。

*/

ServerSocket ss=new ServerSocket(10005);

//獲取socket對象。

Socket s=ss.accept();

//獲取連接進來計算機的IP

String ip=s.getInetAddress().getHostAddress();

System.out.println(ip+"...connected");

//獲取socket讀取流,並裝飾

BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));

//獲取socket輸出流,並裝飾

PrintWriter out=new PrintWriter(s.getOutputStream(),true); //true是自動刷新的

String line=null;

while((line=bufIn.readLine())!=null){

System.out.println(line);

out.println(line.toUpperCase());

}

s.close();

ss.close();

}

}





需求:做一個上傳文件的程序。

客戶端:

package day25;

import java.io.BufferedReader;

import java.io.FileReader;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.PrintWriter;

import java.net.Socket;

import java.net.UnknownHostException;


public class UploadCilent {

/**

* 註意問題:1.默認緩沖區大小是8kb,裝滿後悔自動刷新一次,

* 所以如果文件超過8kb而且bufw不進行刷新操作,就會發現上傳的數據不完整。

* 2.服務端的while循環沒有結束標記。所以運行時會發現服務端和客戶端都在等的問題。

* 只要在客戶端發完數據後再發一個標記,服務端讀到就可以結束了。

* 那樣還是麻煩。java提供了一個更簡單方便的結束方法,socket流的shutdown方法。

* @param args

* @throws UnknownHostException

* @throws IOException

*/

public static void main(String[] args) throws UnknownHostException, IOException {

Socket s=new Socket("192.168.1.113",10007);

BufferedReader bufr=new BufferedReader(new FileReader("client.txt"));

PrintWriter out=new PrintWriter(s.getOutputStream(),true);

String line=null;

while((line=bufr.readLine())!=null){

out.println(line);

}

s.shutdownOutput(); //java提供的更為簡單方便的結束標記方法。

//out.print("over"); //給服務端一個結束的標記,要不結束不了。別用over,萬一文本裏也有over就完了。一般都用時間的毫秒值。

BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));

String str=bufIn.readLine();

System.out.println(str);

bufr.close();

s.close();

}

}



服務端:

package day25;

import java.io.BufferedReader;

import java.io.BufferedWriter;

import java.io.FileWriter;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.PrintWriter;

import java.net.ServerSocket;

import java.net.Socket;


public class UploadServer {


/**

* 註意問題:1.默認緩沖區大小是8kb,裝滿後悔自動刷新一次,

* 所以如果文件超過8kb而且bufw不進行刷新操作,就會發現上傳的數據不完整。

* 2.服務端的while循環沒有結束標記。所以運行時會發現服務端和客戶端都在等的問題。

* 只要在客戶端發完數據後再發一個標記,服務端讀到標記就可以結束了。

* 那樣還是麻煩。java提供了一個更簡單方便的結束方法,socket流的shutdown方法。

* @param args

* @throws IOException

*/

public static void main(String[] args) throws IOException {


ServerSocket ss=new ServerSocket(10007);

Socket s=ss.accept();

System.out.println(s.getInetAddress().getHostAddress()+".....connected");

BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));

BufferedWriter bufw=new BufferedWriter(new FileWriter("server.txt"));

String line=null;

while((line=bufIn.readLine())!=null){

bufw.write(line);

bufw.newLine(); //別忘了換行,要不寫的數據賊亂。

bufw.flush(); //別忘了刷新,要不出問題,具體看上面的文檔註釋。

}

PrintWriter out=new PrintWriter(s.getOutputStream(),true);

out.println("上傳成功!");

bufw.close();

s.close();

ss.close();

}

}




網絡編程:

最常見的客戶端: 瀏覽器 IE

最常見的服務端: 服務器 Tomcat


為了了解其原理:

1.自定義服務端使用已有的客戶端IE,了解一下客戶端給服務端發了什麽請求?


自己寫一個服務端,裏面把客戶端發過來的東西全打印出來,然後瀏覽器地址欄輸入http://ip:服務器的端口/。這樣就可以知道客戶端給服務端到底發了什麽請求。

發送的請求是:Get/HTTP/1.1 //第一行是請求行:請求方式(get和post)/請求的資源路徑 HTTP協議版本

//下面這些都是請求消息頭。 屬性名:屬性值

Accept:*/* //支持接收的文件格式,一般都很長,這裏就沒寫

Accept-Language:zh-cn,zu-;q=0.5 //支持的語言

Accept-Encoding:qzip,deflate //支持的壓縮方式

User-Agent:Mozilla/4.0(compatible;MSIE 6.0;Windows NT 5.1;SV1;InfoPath.2) //用戶的一些信息

Host:192.168.1.100:9090 //要訪問的主機

Connection:Keep-Alive

//空行

//下面有需要的話還有請求體。請求頭和請求體中間有空行,方便服務器解析。


2.了解一下服務端給客戶端發了什麽?

和上面一個道理。自己寫個客戶端,把服務端發回來的信息全部打出來就行。

發回的信息是:HTTP/1.1 200 OK //應答行,HTTP協議版本 應答狀態碼(200表示成功,404表示服務端沒有這個信息) 應答狀態描述信息

//下面都是應答信息頭。 屬性名:屬性值

Server:Apache-Coyote/1.1

ETag:W/"199-1323480176984"

Last-Modified:Sat,10 Dec 2011 01:22:56 GMT //上次訪問緩存的時間,如果和服務器端一致,服務器就不返回信息,直接訪問緩存好的,更快

Content-Type:text/html //文本類型

Content-length:199 //文本長度

Date:Fri,11 May 2012 07:51:39 GMT

Connection:close

//空行

//下面就是應答消息體。解析後就是網頁顯示的內容。



URL是統一資源定位符,也就是我們通常說的網址。

URI是同一資源標識符。URL是URI的子類。所以URL都是URI。

package day25;

import java.io.IOException;

import java.io.InputStream;

import java.net.URL;


public class URLDemo {


/**

* @param args

* @throws IOException

*/

public static void main(String[] args) throws IOException {

String str_url="http://192.168.1.113:8080/myweb/1.html?name=lisi";

//問號後面的是參數信息,原來是歡迎光臨,加上這個就可以變成歡迎lisi光臨

URL url=new URL(str_url);

System.out.println("getProtocol:"+url.getProtocol());

System.out.println("getHost:"+url.getHost());

System.out.println("getPort:"+url.getPort());

System.out.println("getFile:"+url.getFile());

System.out.println("getPath:"+url.getPath());

System.out.println("getQuery:"+url.getQuery());

//本來自己寫的客戶端不能解析,輸出總是把應答信息頭也輸出了,這是沒有必要的

//而url的openStream()就可以解決,輸出時就不會輸出應答信息頭。

//這個例子中我寫的url的IP以及文件是視頻中老師的,我沒裝tomcat,所以才顯示不出來。

InputStream in=url.openStream();

/*

* url.openStream()內部實現的原理說白了就是openConnection()+getInputStream();

* URLConnection con=url.connection();

* 上面這句就是獲取url連接器對象並封裝成對象。java中內置的可以解析具體協議的對象+socket。

* InputStream in=con.getInputStream();

*/

byte[] buf=new byte[1024];

int len=in.read(buf);

String text=new String(buf,0,len);

System.out.println(text);

//url不用close

in.close();

}


}




網絡結構:

1.C/S client/server

優點:客戶端在本地可以分擔一部分運算。

缺點:該結構的軟件,客戶端和服務端都需要編寫。

開發成本較高,維護較為麻煩。


2.B/S browser/server

優點:該結構的軟件,只開發服務器端,不開發客戶端,因為客戶端直接由瀏覽器取代。

開發成本相對低,維護更為簡單。

缺點:所有運算都要在服務端完成。


本文出自 “12946849” 博客,請務必保留此出處http://12956849.blog.51cto.com/12946849/1954475

java(十三)網絡編程