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(十三)網絡編程