一、網絡參考模型
TCP/IP 參考模型
1. 物理層:主要定義物理設備標準,如網線的接口類型、光纖的接口類型、各種傳輸介質的傳輸速率等。它的主要作用是傳輸比特流(就是由1、0轉化爲電流強弱來進行傳輸,到達目的地後再轉化爲1、0,也就是我們常說的數模轉換與模數轉換)。這一層的數據叫做比特。
2. 數據鏈路層:主要將從物理層接收的數據進行MAC地址(網卡的地址)的封裝與解封裝。常把這一層的數據叫做幀。在這一層工作的設備是交換機,數據通過交換機來傳輸。
3. 網絡層:主要將下層接收到的數據進行IP地址(例,192.168.0.1)的封裝與解封裝。在這一層工作的設備是路由器,常把這一層的數據叫做數據包。
4. 傳輸層:定義了一些傳輸數據的協議和端口號(WWW端口號80等),如:TCP(傳輸控制協議,傳輸效率低,可靠性強,用於傳輸可靠性要求高,數據量大的數據),UDP(用戶數據報協議,與TCP特性恰恰相反,用於傳輸可靠性要求不高,數據量小的數據,如QQ聊天數據就是通過這種方式傳輸的)。主要是將從下層接收的數據進行分段和傳輸,到達目的地址後再進行重組。常常把這一層叫做段。
5. 會話層:通過傳輸層(端口號:傳輸端口與接收端口)建立數據傳輸的通路。主要在你的系統之間發起會話或者接收會話請求(設備之間需要互相認識可以是IP也可以是MAC或者是主機名)。
6. 表示層:主要是進行對接收的數據進行解釋,加密與解密、壓縮與解壓縮等(也就是把計算機能夠識別的東西轉換成人能夠識別的東西(如圖片、聲音等)。
7. 應用層:主要是一些終端的應用,比如說FTP(各種文件下載)、WEB(IE瀏覽)、QQ之類的(可以把它理解成我們在電腦屏幕上可以看到的東西,就是終端應用)。
二、如何實現主機名到IP地址的映射。
InetAddress類爲我們提供了靜態方法進行域名解析InetAddress getByName(String host): 在給定主機名的情況下確定主機的 IP 地址。
例如:
import java.net.*;
public class InetAddressTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
InetAddress i = InetAddress.getByName("www.baidu.com");
System.out.println("address:"+i.getHostAddress());
System.out.println("name:"+i.getHostName());
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
可以看到百度的其中一個IP地址是
address:119.75.218.70
name:www.baidu.com
由於某些網站域名對應的IP地址不唯一,InetAddress中存在靜態方法getAllByName(String host)返回的是主機名 對應的IP 地址所組成的數組。
三、TCP和UDP的比較
UDP:
1、將數據源和目的地址封裝在數據包中,不需要建立連接。
2、每個數據包的大小限制在64k內。
3、因無連接,所以是不可靠協議。
4、不需要建立連接,因而速度快。
TCP
1、建立連接,形成傳輸通道;
2、在連接中進行大量數據傳輸。
3、通過三次握手完成連接,是可靠傳輸協議。
4、必須建立連接,但效率會稍低。
四、TCPUDP編程
1、UDP編程
UDP編程步驟
發送端
1、建立UDPsocket服務
2、將數據封裝到Datagramacket數據包中
3、發送數據,調用Datagramsocket的send方法
4、關閉資源
接收端
1、定義UDPsocket服務
2、定義一個數據包DatagramPacket存儲接收到的數據;
3、通過DatagramSocket對象的receive方法,接收數據;
4、通過數據包對象特有功能,接收來自發送端的不同信息;
5、關閉資源;
需求:客戶端發送一個long 類型的數據到服務器端。
客戶端代碼:
import java.io.*;
import java.net.*;
public class TestUDPClient {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
long n = 9999999l;
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
//DataOutputStream允許將數據以機器無關的方式發送;
DataOutputStream dos = new DataOutputStream(bos);
dos.writeLong(n);
byte[] buf = bos.toByteArray();
DatagramPacket dp = new DatagramPacket(buf,0,buf.length,new InetSocketAddress("192.168.1.103",8888));//創建套接字
DatagramSocket ds = new DatagramSocket(9999);
ds.send(dp);
ds.close();
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
服務器端代碼:
import java.io.*;
import java.net.*;
public class TestUDPServer {
/**
* UDP編程,將一個long類型的數據發送到服務器端。
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
byte[] buf = new byte[1024];
ByteArrayInputStream bis = new ByteArrayInputStream(buf);
DataInputStream dis = new DataInputStream(bis);
try {
DatagramSocket ds = new DatagramSocket(8888);
while(true)
try {
DatagramPacket dp = new DatagramPacket(buf,buf.length);
ds.receive(dp);
System.out.println("clientAddress:"+dp.getAddress().getHostAddress());
System.out.println("port:"+ dp.getPort());
System.out.println(dis.readLong());
dis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
需求:寫一個程序,實現發送端從鍵盤讀取數據,發送到接收端。接收端讀取數據打印到控制檯。
發送端:
import java.net.*;
import java.io.*;
public class UdpSend {
/**
* @param args
*/
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
byte[] buf = new byte[1024];
DatagramSocket ds = new DatagramSocket();
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while((line = bufr.readLine())!=null){
if("bye".equals(line)){
break;
}
buf = line.getBytes();
DatagramPacket dp = new DatagramPacket(buf,0,line.length(),InetAddress.getByName("192.168.1.103"),10005);
ds.send(dp);
}
ds.close();
}
}
接收端:
import java.net.*;
public class UdpRece {
/**
* @param args
*/
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
byte[] buf = new byte[1024];
DatagramSocket ds = new DatagramSocket(10005);
while(true){
DatagramPacket dp = new DatagramPacket(buf,0,buf.length);
ds.receive(dp);
String ip = dp.getAddress().getHostAddress();
String data = new String(dp.getData(),0,dp.getLength());
System.out.println("ip" + ip + "發來數據" + data);
}
}
}
2、TCP編程
1、TCP分客戶端和服務端;分別對應的對象是Socket和ServerSocket。
2、TCP是面向連接的,啓動程序時,先啓動服務器端,後啓動客戶端;
客戶端步驟:
1、創建socket服務,指定要連接的主機和端口;
2、爲了發送數據,使用socket流中的輸出流;
3、發送數據;
4、關閉socket;
服務端步驟
1、建立服務端的socket服務,ServerSocket()來監聽一個端口;
2、獲取連接過來的客戶端對象,通過ServerSocket的accept方法(該方法是阻塞式);
3、如果客戶端發送數據,那麼服務端就要使用對應的客戶端對象的讀取流來取客戶端送過來的數據;
4、關閉服務(可選)。
需求:客戶端發送一個字符串,客戶端接收並打印到控制檯上。
服務端:
import java.io.*;
import java.net.*;
public class TcpServer {
/**
* @param args
*/
public static void main(String[] args)throws Exception {
// TODO Auto-generated method stub
//建立服務端的socket服務,監聽端口10006
ServerSocket ss= new ServerSocket(10006);
//接收來自客戶端的Socket對象
Socket s = ss.accept();
//獲取客戶端的IP地址
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"...已經連接上了");
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int length = in.read(buf);
String data = new String(buf,0,length);
System.out.println("客戶端發來的數據是:"+data);
}
}
客戶端
import java.net.*;
import java.io.*;
public class TcpClient {
/**
* @param args
*/
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
//創建socket服務,指定要連接的主機和端口;
Socket s = new Socket("192.168.1.103",10006);
//使用socket流中的輸出流
OutputStream out = s.getOutputStream();
byte[] buf = "hello...hello".getBytes();
out.write(buf);
s.close();
}
}
需求:客戶端發送字母到服務端,服務端返回大寫字母,客戶端接收大寫字母並且打印到控制檯上。這個需求是爲了說明客戶端和服務端可能互相等待的情況,一般是調用flush()方法和newLine(),看用的哪種流,視情況而定。
例如:發送流用PrintWriter,每發送完一條數據,必須調用其flush()方法,不然會引起接收端的阻塞。
客戶端:
import java.io.*;
import java.net.*;
public class TransClient {
/**
*客戶端發送字母到服務端,服務端返回大寫字母,
*客戶端接收大寫字母並且打印到控制檯上
*/
public static void main(String[] args) throws Exception {
Socket s = new Socket("192.168.1.103",10008);
//數據源,從鍵盤讀入數據
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
//目的:發送到服務器端
PrintWriter pw = new PrintWriter(s.getOutputStream());
//從服務端接收數據
BufferedReader bfIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = null;
while((line = bufr.readLine())!=null){
if("over".equals(line)){
break;
}
pw.println(line);
pw.flush();
//接收來自服務端的數據
System.out.println(bfIn.readLine());
}
bufr.close();
s.close();
}
}
服務端
import java.io.*;
import java.net.*;
public class TransServer {
/**
* @param args
*/
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
ServerSocket ss = new ServerSocket(10008);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println("IP:"+ip+"...contected");
//數據源:
BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter pw = new PrintWriter(s.getOutputStream());
String line = null;
while((line = bufr.readLine())!=null){
//轉換成大寫交給Socket輸出流
System.out.println(line);
pw.println(line.toUpperCase());
pw.flush();
}
bufr.close();
s.close();
ss.close();
}
}
然後我們看另外一種寫法:輸出流用BufferedWriter。這裏必須調用BufferedWriter的newLine()方法,不然會陷入相互等待。
客戶端:
import java.io.*;
import java.net.*;
public class TransClient1 {
/**
* 客戶端發送字母到服務端,服務端返回大寫字母,
* 客戶端接收大寫字母並且打印到控制檯上
*/
public static void main(String[] args)throws Exception{
Socket s = new Socket("192.168.1.103",10008);
//數據源,從鍵盤讀入數據
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
//目的:發送到服務器端
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
//從服務端接收數據
BufferedReader bfIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = null;
while((line = bufr.readLine())!=null){
if("over".equals(line)){
break;
}
bw.write(line);
bw.newLine();
bw.flush();
//接收來自服務端的數據
System.out.println(bfIn.readLine());
}
bufr.close();
s.close();
}
}
服務端
import java.io.*;
import java.net.*;
public class TransServer1 {
/**
* @param args
*/
public static void main(String[] args) throws Exception{
ServerSocket ss = new ServerSocket(10008);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println("IP:"+ip+"...contected");
//數據源:
BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream()));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String line = null;
while((line = bufr.readLine())!=null){
//轉換成大寫交給Socket輸出流
System.out.println(line);
bw.write(line.toUpperCase());
bw.newLine();
bw.flush();
}
bufr.close();
s.close();
ss.close();
}
}
需求:編寫客戶端服務端程序,完成文件從客戶端到服務端的拷貝(上傳);
客戶端:
import java.io.*;
import java.net.*;
public class TextClient {
/**
*客戶端傳送文件到服務端
*/
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
Socket s = new Socket("192.168.1.103",10009);
File file = new File("E:\\test\\test.txt");
if(!file.exists()){
file.createNewFile();
}
//數據源:先傳文件名
PrintWriter pw = new PrintWriter(s.getOutputStream());
pw.println(file.getName());
pw.flush();
BufferedReader bin = new BufferedReader(new InputStreamReader(s.getInputStream()));
BufferedReader bufr = new BufferedReader(new FileReader(file));
String line = null;
while((line = bufr.readLine())!=null){
//發送數據到客戶端
pw.println(line);
pw.flush();
}
pw.flush();
//接收來自服務端的反饋信息
String str = **bin.readLine()**;
System.out.println(str);
pw.close();
s.close();
}
}
服務端
import java.io.*;
import java.net.*;
public class TextServer {
/**
* @param args
*/
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
ServerSocket ss = new ServerSocket(10009);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println("ip:"+ip+"...contected");
//接收傳過來的文件
BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream()));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String fileName = bufr.readLine();
//創建文件
File file = new File("D:\\",fileName);
PrintWriter pw = new PrintWriter(file);
String line= null;
while((line = **bufr.readLine()**)!=null){
//將客戶端數據寫到服務端的文件中
pw.write(line);
pw.flush();
}
//反饋一個信號,已經寫完了
bw.write(new String("文件傳輸完畢"));
bw.newLine();
bw.flush();
bw.close();
pw.close();
s.close();
ss.close();
}
}
調試程序發現一個現象:程序陷入互相等待,但是文件已經完成了拷貝。
服務端的程序陷入等待,客戶端沒收到反饋也等待。問題根源在與服務端不知道客戶端已經完成了文件的上傳,因此,客戶端文件上傳完成後,通知服務端。
現假設這樣做:若傳送的數據爲over,則通知服務端文件已經讀完。
客戶端:
import java.io.*;
import java.net.*;
public class TextClient {
/**
*客戶端傳送文件到服務端
*/
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
Socket s = new Socket("192.168.1.103",10009);
File file = new File("E:\\test\\test.txt");
if(!file.exists()){
file.createNewFile();
}
//數據源:先傳文件名
PrintWriter pw = new PrintWriter(s.getOutputStream());
pw.println(file.getName());
pw.flush();
BufferedReader bin = new BufferedReader(new InputStreamReader(s.getInputStream()));
BufferedReader bufr = new BufferedReader(new FileReader(file));
String line = null;
while((line = bufr.readLine())!=null){
//發送數據到客戶端
pw.println(line);
pw.flush();
}
pw.println("over");
pw.flush();
//接收來自服務端的反饋信息
String str = bin.readLine();
System.out.println(str);
pw.close();
s.close();
}
}
服務端
import java.io.*;
import java.net.*;
public class TextServer {
/**
* @param args
*/
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
ServerSocket ss = new ServerSocket(10009);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println("ip:"+ip+"...contected");
//接收傳過來的文件
BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream()));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String fileName = bufr.readLine();
//創建文件
File file = new File("D:\\",fileName);
PrintWriter pw = new PrintWriter(file);
String line= null;
while((line = bufr.readLine())!=null){
//將客戶端數據寫到服務端的文件中
if("over".equals(line)){
break;
}
pw.write(line);
pw.flush();
}
//反饋一個信號,已經寫完了
bw.write(new String("文件傳輸完畢"));
bw.newLine();
bw.flush();
bw.close();
pw.close();
s.close();
ss.close();
}
}
問題似乎解決了,但是不建議這麼做,如過複製的文件中含有over標記,則文件還未上傳完,便結束了。
當然更普遍的是令一種方法:調用socket的shutdownOutput方法來關閉輸出流。
客戶端
import java.io.*;
import java.net.*;
public class TextClient {
/**
*客戶端傳送文件到服務端
*/
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
Socket s = new Socket("192.168.1.103",10009);
File file = new File("E:\\test\\test.txt");
if(!file.exists()){
file.createNewFile();
}
//數據源:先傳文件名
PrintWriter pw = new PrintWriter(s.getOutputStream());
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
pw.println(file.getName());
pw.flush();
BufferedReader bin = new BufferedReader(new InputStreamReader(s.getInputStream()));
BufferedReader bufr = new BufferedReader(new FileReader(file));
String line = null;
while ((line = bufr.readLine()) != null) {
// 發送數據到客戶端
pw.println(line);
pw.flush();
}
//關閉客戶端的輸出流,相當於在流中加入結束標記-1
s.shutdownOutput();
// 接收來自服務端的反饋信息
String str = bin.readLine();
System.out.println(str);
pw.close();
s.close();
}
}
服務端
import java.io.*;
import java.net.*;
public class TextServer {
/**
* @param args
*/
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
ServerSocket ss = new ServerSocket(10009);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println("ip:"+ip+"...contected");
//接收傳過來的文件
BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream()));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String fileName = bufr.readLine();
//創建文件
File file = new File("D:\\",fileName);
PrintWriter pw = new PrintWriter(file);
String line= null;
while((line = bufr.readLine())!=null){
//將客戶端數據寫到服務端的文件中
pw.write(line);
pw.flush();
}
//反饋一個信號,已經寫完了
bw.write(new String("文件傳輸完畢"));
bw.newLine();
bw.flush();
bw.close();
pw.close();
s.close();
ss.close();
}
}