Java基礎:網絡編程

1. 網絡編程概述

1.1 計算機網絡

是指將地理位置不同的具有獨立功能的多臺計算機及其外部設備,通過通信線路連接起來,在網絡操作系統,網絡管理軟件及網絡通信協議的管理和協調下,實現資源共享和信息傳遞的計算機系統。

1.2 網絡編程

就是用來實現網絡互連的不同計算機上運行的程序間可以進行數據交換。

1.3 網絡模型

計算機網絡之間以何種規則進行通信,就是網絡模型研究問題。

網絡模型一般是指OSI(Open System Interconnection開放系統互連)參考模型或者TCP/IP參考模型。

應用層:http、https、ftp,傳輸層:TCP、UDP,網絡層:IP,物理層,數據鏈路層

網絡模型

1.4 網絡模型7層概述

  • 物理層

主要定義物理設備標準,如網線的接口類型、光纖的接口類型、各種傳輸介質的傳輸速率等。它的主要作用是傳輸比特流(就是由1、0轉化為電流強弱來進行傳輸,到達目的地后在轉化為1、0,也就是我們常說的數模轉換與模數轉換)。這一層的數據叫做比特。

  • 數據鏈路層

主要將從物理層接收的數據進行MAC地址(網卡的地址)的封裝與解封裝。常把這一層的數據叫做幀。在這一層工作的設備是交換機,數據通過交換機來傳輸。

  • 網絡層

主要將從下層接收到的數據進行IP地址(例192.168.0.1)的封裝與解封裝。在這一層工作的設備是路由器,常把這一層的數據叫做數據包。

  • 傳輸層

定義了一些傳輸數據的協議和端口號(WWW端口80等),如:TCP(傳輸控制協議,傳輸效率低,可靠性強,用于傳輸可靠性要求高,數據量大的數據),UDP(用戶數據報協議,與TCP特性恰恰相反,用于傳輸可靠性要求不高,數據量小的數據,如QQ聊天數據就是通過這種方式傳輸的)。 主要是將從下層接收的數據進行分段和傳輸,到達目的地址后再進行重組。常常把這一層數據叫做段。

  • 會話層

通過傳輸層(端口號:傳輸端口與接收端口)建立數據傳輸的通路。主要在你的系統之間發起會話或者接受會話請求(設備之間需要互相認識可以是IP也可以是MAC或者是主機名)

  • 表示層

主要是進行對接收的數據進行解釋、加密與解密、壓縮與解壓縮等(也就是把計算機能夠識別的東西轉換成人能夠能識別的東西(如圖片、聲音等)。

  • 應用層

主要是一些終端的應用,比如說FTP(各種文件下載),WEB(IE瀏覽),QQ之類的(可以把它理解成我們在電腦屏幕上可以看到的東西.就是終端應用)。

PS:

  • 每個網卡的MAC地址都是全球唯一的。
  • 路由器實現將數據包發送到指定的地點。
  • 應用軟件之間通信的過程就是層與層之間封包、解封包的過程。
  • OSI參考模型雖然設計精細,但過于麻煩,效率不高,因此才產生了簡化版的TCP/IP參考模型。

1.5 封包、解封包的過程

封包、解封包

2. 網絡編程三要素

網絡模型說完了,我們要進行通訊,需要哪些要素呢?

比如說:我要跟你說話
第一個條件:我要先找到你 (IP)
第二個條件:你得有接收數據的地方,耳朵 (端口)
第三個條件:我跟你說話,你能接收到,咱按什么方式接收啊,我說英文你懂嗎,說韓文你懂嗎,不懂是吧,所以我還是說中文把(協議)

2.1 IP地址

網絡中計算機的唯一標識,不易記憶,可用主機名。本地回環地址:127.0.0.1,主機名:localhost。計算機只能識別二進制的數據,所以我們的IP地址應該是一個二進制的數據。為了方便表示IP地址,我們就把IP地址的每一個字節上的數據換算成十進制,然后用.分開來表示:"點分十進制"。

所謂IP地址就是給每個連接在Internet上的主機分配的一個32bit地址。按照TCP/IP規定,IP地址用二進制來表示,每個IP地址長32bit,比特換算成字節,就是4個字節。例如一個采用二進制形式的IP地址是“00001010000000000000000000000001”,這么長的地址,人們處理起來也太費勁了。為了方便人們的使用,IP地址經常被寫成十進制的形式,中間使用符號“.”分開不同的字節。于是,上面的IP地址可以表示為“10.0.0.1”。IP地址的這種表示法叫做“點分十進制表示法”,這顯然比1和0容易記憶得多。

通過ping 127.0.0.1可以測試網絡是不是通,如果不通,可能是網卡出問題了

ip地址

通過ping命令還可以獲取到url對應的IP地址,例如獲取網易新聞url(c.m.163.com)的IP地址

ping命令

查看本機IP地址ipconfig

ping命令

IP地址分類

IP地址的組成:IP地址 = 網絡號碼+主機地址

IPV4數量已經不夠分配,所以產生了IPV6。

ip

InetAddress類的使用

此類表示互聯網協議 (IP) 地址

返回值 方法 說明
InetAddress getByName(String host) 根據主機名或者IP地址的字符串表示得到IP地址對象
String getHostName() 獲取此 IP 地址的主機名
String getHostAddress() 返回 IP 地址字符串

代碼示例:

package cn.itcast_01;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class InetAddressDemo {
    public static void main(String[] args) throws UnknownHostException {
        // public static InetAddress getByName(String host)
        // InetAddress address = InetAddress.getByName("liuyi");
        // InetAddress address = InetAddress.getByName("192.168.12.92");
        InetAddress address = InetAddress.getByName("192.168.12.63");

        // 獲取兩個東西:主機名,IP地址
        // public String getHostName()
        String name = address.getHostName();
        // public String getHostAddress()
        String ip = address.getHostAddress();
        System.out.println(name + "---" + ip);
    }
}

運行結果:

ip

2.2 端口號

正在運行的程序的標識,用于標識進程的邏輯地址,不同進程的標識。有效端口:065535,其中01024系統使用或保留端口。

端口分為:物理端口,網卡口;邏輯端口,我們指的就是邏輯端口。

  • A:每個網絡程序都會至少有一個邏輯端口
  • B:用于標識進程的邏輯地址,不同進程的標識
  • C:有效端口:065535,其中01024系統使用或保留端口。
  • D:所謂防火墻,其功能就是將發送到某程序端口的數據屏蔽掉以及將從該程序端口發出的數據也屏蔽掉。
端口號

2.3 傳輸協議

傳輸協議就是通訊的規則,常見協議:TCP,UDP。

UDP將數據源和目的封裝成數據包中,不需要建立連接;每個數據報的大小在限制在64k;因無連接,是不可靠協議;不需要建立連接,速度快

TCP建立連接,形成傳輸數據的通道;在連接中進行大數據量傳輸;通過三次握手完成連接,是可靠協議;必須建立連接,效率會稍低

UDP和TCP的特點

  • UDP:面向無連接;不可靠;速度快;將數據封包傳輸,數據包最大64k
    舉例:聊天留言,在線視頻,視頻會議,發短信,郵局包裹。
  • TCP:面向連接;安全可靠效率稍低;通過三次握手確保連接的建立。
    舉例:下載,打電話,QQ聊天(你在線嗎,在線,就回應下,就開始聊天了)

2.4 域名解析

域名解析

在瀏覽器中輸入新浪的域名,DNS解析域名成IP,然后計算機再通過獲取到的IP訪問新浪服務器。

域名解析,最先走是本地的hosts(C:\WINDOWS\system32\drivers\etc\hosts)文件,解析失敗了,才去訪問DNS服務器解析、獲取IP地址。

域名解析
域名解析
import java.net.InetAddress;
import java.net.UnknownHostException;

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

        InetAddress ip = InetAddress.getLocalHost();

        ip = InetAddress.getByName("192.168.1.110");

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

運行結果

域名解析

應用:通過hosts文件可以屏蔽游戲網站內容彈出,例如:在hosts文件中添加,127.0.0.1 www.game18.com

3. Socket套接字

3.1 Socket套接字

網絡上具有唯一標識的IP地址和端口號組合在一起才能構成唯一能識別的標識符套接字。

3.2 Socket原理機制

  • 通信的兩端都有Socket
  • 網絡通信其實就是Socket間的通信
  • 數據在兩個Socket間通過IO傳輸

3.3 Socket機制圖解

socket

4. UDP編程

UDP:UDP 協議全稱是用戶數據報協議,在網絡中它與TCP 協議一樣用于處理數據包,是一種無連接的協議。在OSI 模型中,在第四層——傳輸層,處于IP 協議的上一層。UDP 有不提供數據包分組、組裝和不能對數據包進行排序的缺點,也就是說,當報文發送之后,是無法得知其是否安全完整到達的。UDP 用來支持那些需要在計算機之間傳輸數據的網絡應用。包括網絡視頻會議系統在內的眾多的客戶/服務器模式的網絡應用都需要使用UDP協議。UDP 協議從問世至今已經被使用了很多年,雖然其最初的光彩已經被一些類似協議所掩蓋,但是即使是在今天UDP 仍然不失為一項非常實用和可行的網絡傳輸層協議。

4.1 UDP傳輸

  • DatagramSocket與DatagramPacket
  • 建立發送端,接收端
  • 建立數據包
  • 調用Socket的發送接收方法
  • 關閉Socket
  • 發送端與接收端是兩個獨立的運行程序

4.2 DatagramSocket

此類表示用來發送和接收數據報包的套接字

數據報套接字是包投遞服務的發送或接收點。每個在數據報套接字上發送或接收的包都是單獨編址和路由的。從一臺機器發送到另一臺機器的多個包可能選擇不同的路由,也可能按不同的順序到達

在 DatagramSocket 上總是啟用 UDP 廣播發送。為了接收廣播包,應該將 DatagramSocket 綁定到通配符地址。在某些實現中,將 DatagramSocket 綁定到一個更加具體的地址時廣播包也可以被接收。

構造方法

DatagramSocket(int port) // 創建數據報套接字并將其綁定到本地主機上的指定端口
DatagramSocket(int port, InetAddress laddr) // 創建數據報套接字,將其綁定到指定的本地地址

UDP傳輸-發送端思路

  • 建立udp的socket服務
  • 將要發送的數據封裝成數據包
  • 通過udp的socket服務,將數據包發送出
  • 關閉資源
package cn.itcast_02;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/*
 * UDP協議發送數據:
 * A:創建發送端Socket對象
 * B:創建數據,并把數據打包
 * C:調用Socket對象的發送方法發送數據包
 * D:釋放資源
 */
public class SendDemo {
    public static void main(String[] args) throws IOException {
        // 創建發送端Socket對象
        // DatagramSocket()
        DatagramSocket ds = new DatagramSocket();

        // 創建數據,并把數據打包
        // DatagramPacket(byte[] buf, int length, InetAddress address, int port)
        // 創建數據
        byte[] bys = "hello,udp,我來了".getBytes();
        // 長度
        int length = bys.length;
        // IP地址對象
        InetAddress address = InetAddress.getByName("192.168.12.92");
        // 端口
        int port = 10086;
        DatagramPacket dp = new DatagramPacket(bys, length, address, port);

        // 調用Socket對象的發送方法發送數據包
        // public void send(DatagramPacket p)
        ds.send(dp);

        // 釋放資源
        ds.close();
    }
}

4.3 DatagramPacket

此類表示數據報包。數據報包用來實現無連接包投遞服務。每條報文僅根據該包中包含的信息從一臺機器路由到另一臺機器。從一臺機器發送到另一臺機器的多個包可能選擇不同的路由,也可能按不同的順序到達。不對包投遞做出保證。

構造方法

  • DatagramPacket(byte[] buf, int length)
    構造 DatagramPacket,用來接收長度為 length 的數據包。

  • DatagramPacket(byte[] buf, int length, InetAddress address, int port)
    構造數據報包,用來將長度為 length 的包發送到指定主機上的指定端口號。

  • DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
    構造數據報包,用來將長度為 length 偏移量為 offset 的包發送到指定主機上的指定端口號。

UDP傳輸-接收端思路

  • 建立udp的socket服務.
  • 通過receive方法接收數據
  • 將收到的數據存儲到數據包對象中
  • 通過數據包對象的功能來完成對接收到數據進行解析
  • 可以對資源進行關閉
package cn.itcast_02;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/*
 * UDP協議接收數據:
 * A:創建接收端Socket對象
 * B:創建一個數據包(接收容器)
 * C:調用Socket對象的接收方法接收數據
 * D:解析數據包,并顯示在控制臺
 * E:釋放資源
 */
public class ReceiveDemo {
    public static void main(String[] args) throws IOException {
        // 創建接收端Socket對象
        // DatagramSocket(int port)
        DatagramSocket ds = new DatagramSocket(10086);

        // 創建一個數據包(接收容器)
        // DatagramPacket(byte[] buf, int length)
        byte[] bys = new byte[1024];
        int length = bys.length;
        DatagramPacket dp = new DatagramPacket(bys, length);

        // 調用Socket對象的接收方法接收數據
        // public void receive(DatagramPacket p)
        ds.receive(dp); // 阻塞式

        // 解析數據包,并顯示在控制臺
        // 獲取對方的ip
        // public InetAddress getAddress()
        InetAddress address = dp.getAddress();
        String ip = address.getHostAddress();
        // public byte[] getData():獲取數據緩沖區
        // public int getLength():獲取數據的實際長度
        byte[] bys2 = dp.getData();
        int len = dp.getLength();
        String s = new String(bys2, 0, len);
        System.out.println(ip + "傳遞的數據是:" + s);

        // 釋放資源
        ds.close();
    }
}

運行結果:

udp

4.4 UDP案例

從鍵盤錄入數據進行發送,如果輸入的是886那么客戶端就結束輸入數據。

發送端

package cn.itcast_04;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
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();
    }
}

運行結果:

udp

接收端

package cn.itcast_04;

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();
    }
}

運行結果:

udp

5. TCP編程

TCP/IP:Transmission Control Protocol/Internet Protocol 的簡寫,中譯名為傳輸控制協議/因特網互聯協議,又名網絡通訊協議,是Internet 最基本的協議、Internet 國際互聯網絡的基礎,由網絡層的IP 協議和傳輸層的TCP協議組成。TCP/IP 定義了電子設備如何連入因特網,以及數據如何在它們之間傳輸的標準。協議采用了4 層的層級結構,每一層都呼叫它的下一層所提供的協議來完成自己的需求。通俗而言:TCP 負責發現傳輸的問題,一有問題就發出信號,要求重新傳輸,直到所有數據安全正確地傳輸到目的地。而IP 是給因特網的每一臺聯網設備規定一個地址。

TCP/IP 協議棧主要分為四層:應用層、傳輸層、網絡層、數據鏈路層,每層都有相應的協議,如下圖:

tcp

所謂的協議就是雙方進行數據傳輸的一種格式。

5.1 TCP傳輸

  • Socket和ServerSocket
  • 建立客戶端和服務器端
  • 建立連接后,通過Socket中的IO流進行數據的傳輸
  • 關閉socket
  • 同樣,客戶端與服務器端是兩個獨立的應用程序。

5.2 Socket

此類實現客戶端套接字(也可以就叫“套接字”)。套接字是兩臺機器間通信的端點。

構造方法

  • Socket(String host, int port) :創建一個流套接字并將其連接到指定主機上的指定端口號。
  • Socket(InetAddress address, int port) :創建一個流套接字并將其連接到指定 IP 地址的指定端口號。

TCP傳輸-客戶端思路

  • 建立客戶端的Socket服務,并明確要連接的服務器。
  • 如果連接建立成功,就表明,已經建立了數據傳輸的通道.就可以在該通道通過IO進行數據的讀取和寫入.該通道稱為Socket流,Socket流中既有讀取流,也有寫入流.
  • 通過Socket對象的方法,可以獲取這兩個流
  • 通過流的對象可以對數據進行傳輸
  • 如果傳輸數據完畢,關閉資源
package cn.itcast_06;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

/*
 * TCP協議發送數據:
 * A:創建發送端的Socket對象
 *      這一步如果成功,就說明連接已經建立成功了。
 * B:獲取輸出流,寫數據
 * C:釋放資源
 *
 * 連接被拒絕。TCP協議一定要先看服務器。
 * java.net.ConnectException: Connection refused: connect
 */
public class ClientDemo {
    public static void main(String[] args) throws IOException {
        // 創建發送端的Socket對象
        // Socket(InetAddress address, int port)
        // Socket(String host, int port)
        // Socket s = new Socket(InetAddress.getByName("192.168.12.92"), 8888);
        Socket s = new Socket("192.168.12.92", 8888);

        // 獲取輸出流,寫數據
        // public OutputStream getOutputStream()
        OutputStream os = s.getOutputStream();
        os.write("hello,tcp,我來了".getBytes());

        // 釋放資源
        s.close();
    }
}

5.3 ServerSocket

此類實現服務器套接字。服務器套接字等待請求通過網絡傳入。它基于該請求執行某些操作,然后可能向請求者返回結果。

構造方法

ServerSocket(int port) // 創建綁定到特定端口的服務器套接字

TCP傳輸-服務器端思路

  • 建立服務器端的socket服務,需要一個端口
  • 服務端沒有直接流的操作,而是通過accept方法獲取客戶端對象,在通過獲取到的客戶端對象的流和客戶端進行通信
  • 通過客戶端的獲取流對象的方法,讀取數據或者寫入數據
  • 如果服務完成,需要關閉客戶端,然后關閉服務器,但是,一般會關閉客戶端,不會關閉服務器,因為服務端是一直提供服務的
package cn.itcast_06;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/*
 * TCP協議接收數據:
 * A:創建接收端的Socket對象
 * B:監聽客戶端連接。返回一個對應的Socket對象
 * C:獲取輸入流,讀取數據顯示在控制臺
 * D:釋放資源
 */
public class ServerDemo {
    public static void main(String[] args) throws IOException {
        // 創建接收端的Socket對象
        // ServerSocket(int port)
        ServerSocket ss = new ServerSocket(8888);

        // 監聽客戶端連接。返回一個對應的Socket對象
        // public Socket accept()
        Socket s = ss.accept(); // 偵聽并接受到此套接字的連接。此方法在連接傳入之前一直阻塞。

        // 獲取輸入流,讀取數據顯示在控制臺
        InputStream is = s.getInputStream();

        byte[] bys = new byte[1024];
        int len = is.read(bys); // 阻塞式方法
        String str = new String(bys, 0, len);

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

        System.out.println(ip + "---" + str);

        // 釋放資源
        s.close();
        // ss.close(); //這個不應該關閉
    }
}

5.4 TCP傳輸案例

客戶端鍵盤錄入,服務器輸出到控制臺

客戶端:

package cn.itcast_08;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;

/*
 * 客戶端鍵盤錄入,服務器輸出到控制臺
 */
public class ClientDemo {
    public static void main(String[] args) throws IOException {
        // 創建客戶端Socket對象
        Socket s = new Socket("192.168.12.92", 22222);

        // 鍵盤錄入數據
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        // 把通道內的流給包裝一下
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                s.getOutputStream()));

        String line = null;
        while ((line = br.readLine()) != null) {
            // 鍵盤錄入數據要自定義結束標記
            if ("886".equals(line)) {
                break;
            }
            bw.write(line);
            bw.newLine();
            bw.flush();
        }

        // 釋放資源
        // bw.close();
        // br.close();
        s.close();
    }
}

運行結果:

tcp

服務器端:

package cn.itcast_08;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        // 創建服務器Socket對象
        ServerSocket ss = new ServerSocket(22222);

        // 監聽客戶端連接
        Socket s = ss.accept();

        // 包裝通道內容的流
        BufferedReader br = new BufferedReader(new InputStreamReader(
                s.getInputStream()));
        String line = null;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }

        // br.close();
        s.close();
        // ss.close();
    }
}

運行結果:

tcp

5.5 上傳圖片案例

客戶端:

package cn.itcast_13;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;

public class UploadClient {
    public static void main(String[] args) throws IOException {
        // 創建客戶端Socket對象
        Socket s = new Socket("192.168.12.92", 19191);

        // 封裝圖片文件
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
                "林青霞.jpg"));
        // 封裝通道內的流
        BufferedOutputStream bos = new BufferedOutputStream(s.getOutputStream());

        byte[] bys = new byte[1024];
        int len = 0;
        while ((len = bis.read(bys)) != -1) {
            bos.write(bys, 0, len);
            bos.flush();
        }

        s.shutdownOutput();

        // 讀取反饋
        InputStream is = s.getInputStream();
        byte[] bys2 = new byte[1024];
        int len2 = is.read(bys2);
        String client = new String(bys2, 0, len2);
        System.out.println(client);

        // 釋放資源
        bis.close();
        s.close();
    }
}

服務器端:

package cn.itcast_13;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class UploadServer {
    public static void main(String[] args) throws IOException {
        // 創建服務器Socket對象
        ServerSocket ss = new ServerSocket(19191);

        // 監聽客戶端連接
        Socket s = ss.accept();

        // 封裝通道內流
        BufferedInputStream bis = new BufferedInputStream(s.getInputStream());
        // 封裝圖片文件
        BufferedOutputStream bos = new BufferedOutputStream(
                new FileOutputStream("mn.jpg"));

        byte[] bys = new byte[1024];
        int len = 0;
        while ((len = bis.read(bys)) != -1) {
            bos.write(bys, 0, len);
            bos.flush();
        }

        // 給一個反饋
        OutputStream os = s.getOutputStream();
        os.write("圖片上傳成功".getBytes());

        bos.close();
        s.close();
    }
}

運行結果:

tcp

5.6 TCP傳輸容易出現的問題

  • 客戶端連接上服務端,兩端都在等待,沒有任何數據傳輸
  • 通過例程分析:因為read方法或者readLine方法是阻塞式
  • 解決辦法:自定義結束標記,使用shutdownInput,shutdownOutput方法

6. TCP、UDP 特點對比

TCP 協議是面向連接、保證高可靠性(數據無丟失、數據無失序、數據無錯誤、數據無重復到達)傳輸層協議。UDP 協議也是傳輸層協議,它是無連接,不保證可靠的傳輸層協議。

TCP UDP
面向連接 面向非連接
可靠的連接 不可靠的連接
速度慢 速度快
大文件、重要的數據等 適合小數據、不重要

7. TCP 三次握手過程

1、請求端(通常稱為客戶)發送一個SYN 段指明客戶打算連接的服務器的端口,以及初始序號(ISN)

2、服務器發回包含服務器的初始序號的SYN 報文段(報文段2)作為應答。同時,將確認序號設置為客戶的ISN加1 以對客戶的SYN 報文段進行確認。

3、客戶必須將確認序號設置為服務器的ISN 加1 以對服務器的SYN 報文段進行確認(報文段3)這三個報文段完成連接的建立。這個過程也稱為三次握手(three-way handshake)。

上面的過程如下圖所示:

tcp

8. 客戶端和服務器端原理

8.1 常見的客戶端、服務器端

最常見的客戶端:瀏覽器,IE/chrome
最常見的服務端:服務器,Tomcat

8.2 常見網絡結構

網絡結構

8.3 URL&URI

URI:統一資源標識符

URI是統一資源標識符,是一個用于標識某一互聯網資源名稱的字符串。 該種標識允許用戶對任何(包括本地和互聯網)的資源通過特定的協議進行交互操作。URI由包括確定語法和相關協議的方案所定義。由是三個組成部分:訪問資源的命名機制、存放資源的主機名、資源自身的名稱,由路徑表示。

URL:統一資源定位符

也就是說根據URL能夠定位到網絡上的某個資源,它是指向互聯網“資源”的指針。

每個URL都是URI,但不一定每個URI都是URL。這是因為URI還包括一個子類,即統一資源名稱(URN),它命名資源但不指定如何定位資源。

URL是統一資源定位,是對可以從互聯網上得到的資源的位置和訪問方法的一種簡潔的表示,是互聯網上標準資源的地址。互聯網上的每個文件都有一個唯一的URL,它包含的信息指出文件的位置以及瀏覽器應該怎么處理它。

比如百度URL即是http://www.baidu.com

9. TCP的三次握手/四次揮手

TCP是面向連接的運輸層協議,TCP協議提供可靠的連接服務,所以用了建立鏈接的三次握手和關閉連接的四次揮手來保證可靠服務。
通過TCP通信就像是兩個應用在打電話一樣,打電話前得先撥號建立連接,通話結束后要掛機釋放連接。

9.1 建立TCP連接的三次握手

TCP連接的三次握手分別為:

  • 客戶端發送一個帶SYN標志的TCP報文到服務器,表示告訴服務器我想建立一個連接。
  • 服務器收到客戶端的帶SYN標志的文后,就給客戶端回復一個帶ACK標志和帶SYN標志的報文,ACK表示回復客戶端:OK,我準備好了建立連接;然后SYN表示服務器又問客戶端:你準備好建立連接了么?
  • 然后客戶端又要發送一個帶ACK標志的TCP報文,回答服務器說:我準備好了。
    然后一個TCP連接就建立起來了。

SYN相當于詢問的標志,ACK相當于回復的標志。

這里有一個問題:為什么最后客戶端還要發送一次確認呢?這主要是防止已經失效了的請求報文段突然又傳到了服務器,因而產生錯誤。
“已經失效了的請求報文段”大致是這樣產生的:A發出第一個連接請求報文段并沒有丟失,在一些網絡結點上面長時間滯留,以致延誤到連接釋放以后的某個時間才到達B。本來這是一個早已失效的報文段。但B收到這個失效的報文段后,就誤以為是A發出的又一次新的連接請求,于是就向A發出確認報文段,同意建立連接,如果不采用三次握手,那么只要B發出確認后,新的連接就建立了。

9.2 釋放TCP連接的四次揮手

由于TCP是全雙工的,所以在釋放TCP連接時,要雙方都得單獨關閉。意思就是服務器和客戶端都要釋放連接。原則是某一方主動關閉時,先發一個FIN報文來表示終止這個方向的連接,收到一個FIN報文就意味著這個方向不再有數據流動,但另一個方向仍可以有數據流動,當這一個方向也發送了FIN報文后,那么這一方的連接也可以關閉了。
釋放TCP連接相對于要復雜點,具體釋放TCP連接的四次揮手流程如下:

  • A發送一個FIN給B,說:我這邊要傳給你的數據已經傳完了,我要關閉連接了。A進入FIN-WAIT-1狀態,等待B確認。
  • B收到了上面的FIN報文后,回復一個ACK報文說:OK。A就關閉了A->B的連接。但是此時B還能給A發送數據,A也能接收B發來的數據。(此時A收到確認后進入FIN-WAIT-2狀態。TCP處于半關閉狀態)
  • 當B也發送完數據后,就給A發送一個FIN報文說:我這邊要傳給你的數據也已經傳完了,我也要關閉連接了。(B進入LAST-ACK狀態,等待A確認)
  • A收到了上面的報文后,回復一個ACK報文說:OK。A進入TIME-WAIT狀態。現在TCP連接還沒有釋放掉,然后經過等待計時器(TIME-WAIT timer)設置的時間2MSL后,A才進入CLOSE狀態。

然后,當A撤銷相應的傳輸控制塊TCB后,一個TCP連接就關閉了。

10. Http、Tcp、Udp、Socket的區別

IP,網絡層協議;TCP和UDP,傳輸層協議;HTTP,應用層協議;SOCKET:TCP/IP網絡的API。

TCP/IP代表傳輸控制協議/網際協議,指的是一系列協議。

TCP和UDP使用IP協議從一個網絡傳送數據包到另一個網絡。把IP想像成一種高速公路,它允許其它協議在上面行駛并找到到其它電腦的出口。TCP和UDP是高速公路上的“卡車”,它們攜帶的貨物就是像HTTP,文件傳輸協議FTP這樣的協議等。

TCP和UDP是FTP,HTTP和SMTP之類使用的傳輸層協議。雖然TCP和UDP都是用來傳輸其他協議的,它們卻有一個顯著的不同:TCP提供有保證的數據傳輸,而UDP不提供。這意味著TCP有一個特殊的機制來確保數據安全的不出錯的從一個端點傳到另一個端點,而UDP不提供任何這樣的保證。

HTTP(超文本傳輸協議)是利用TCP在兩臺電腦(通常是Web服務器和客戶端)之間傳輸信息的協議。客戶端使用Web瀏覽器發起HTTP請求給Web服務器,Web服務器發送被請求的信息給客戶端。

記住,需要IP協議來連接網絡;TCP是一種允許我們安全傳輸數據的機制,使用TCP協議來傳輸數據的HTTP是Web服務器和客戶端使用的特殊協議。

Socket 接口是TCP/IP網絡的API,Socket接口定義了許多函數或例程,用以開發TCP/IP網絡上的應用程序。

本節原文鏈接:http://www.lxweimin.com/p/1f512687ea19

11. URL

URI:統一資源標示符。

URL:統一資源定位符,也就是說根據URL能夠定位到網絡上的某個資源,它是指向互聯網“資源”的指針。

每個URL都是URI,但不一定每個URI都是URL。這是因為URI還包括一個子類,即統一資源名稱(URN),它命名資源但不指定如何定位資源。

public class URLDemo
{
    public static void main(String[] args) throws MalformedURLException,IOException {
        String str_url = "http://192.168.1.100:8080/myweb/1.html?name=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());
        InputStream in = url.openStream();//相當于 url.openConnection().getInputStream();
        byte[] buf = new byte[1024];
        int len = in.read(buf);
        String text = new String(buf,0,len);
        System.out.println(text);
        in.close();
    }
}

運行結果

url

之所以運行結果中響應頭不見了,只能看到主體數據的原因在于:URLConnection對象已經把響應頭
給解析了

12. URLConnection

public class URLDemo
{
    public static void main(String[] args) throws MalformedURLException,IOException {
        String str_url = "http://192.168.1.100:8080/myweb/1.html?name=lisi";
        URL url = new URL(str_url);
        //獲取url對象的Url連接器對象。將連接封裝成了對象:
        // java中內置的可以解析的具體協議對象+socket。
        URLConnection conn = url.openConnection();
        System.out.println(conn);
        //由于URLConnection對象已經把響應頭給解析了,所以,
        // 可以通過URLConnection對象獲取響應頭某屬性名對應的屬性值。
        String value = conn.getHeaderField("Content-Type");
        System.out.println(value);
    }
}

運行結果

urlconnection

13. HttpURLConnection

URL newURL = new URL(url);
URLConnection urlConnection = newURL.openConnection();
urlConnection.setConnectTimeout(mConfig.connTimeOut);
urlConnection.setReadTimeout(mConfig.soTimeOut);
urlConnection.setDoInput(true);
urlConnection.setUseCaches(false);

// HttpsURLConnection
HttpsURLConnection.setDefaultSSLSocketFactory(sslFactory);
HttpsURLConnection.setDefaultHostnameVerifier();

HttpURLConnection常用方法

方法聲明 功能描述
addRequestProperty() 添加請求屬性
setRequestMethod() 設置請求方式
connect() 連接網絡
disconnect() 斷開連接
setDoOutput() 設置打開連接對象輸出流,把要提交的數據寫入流中
setDoInput() 設置打開連接對象輸入流
setConnectTimeout() 設置連接超時
setReadTimeout() 設置讀取超時
setUseCaches() 設置是否使用緩存
getResponseCode() 獲取響應碼
getOutputStream() 獲取輸出流
getInputStream() 獲取輸入流
getErrorStream() 獲取錯誤流
getResponseMessage() 獲取響應信息
getContentLength() 獲取內容長度
getContentEncoding() 獲取內容編碼
getContentType() 獲取內容類型
getHeaderFields() 獲取所有的頭字段

setRequestProperty和addRequestProperty的區別

setRequestProperty和addRequestProperty的區別就是,setRequestProperty會覆蓋已經存在的key的所有values,有清零重新賦值的作用。而addRequestProperty則是在原來key的基礎上繼續添加其他value。

/**
 * Adds the given property to the request header. Existing properties with
 * the same name will not be overwritten by this method.
 */
 public void addRequestProperty(String field, String newValue) {
        ...
    }

字節流轉換為字符

public class Tools {
    public static String getTextFromStream(InputStream is) {
        try {
            byte[] b = new byte[1024];
            int len;
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            while ((len = is.read(b)) != -1) {
                bos.write(b, 0, len);
            }
            //把輸出流里的內容轉換成字節數組
            String text = new String(bos.toByteArray());
            return text;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

14. URLEncoder和URLDecoder

URLEncoder.encode();
URLDecoder.decode();
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 個人認為,Goodboy1881先生的TCP /IP 協議詳解學習博客系列博客是一部非常精彩的學習筆記,這雖然只是...
    貳零壹柒_fc10閱讀 5,093評論 0 8
  • 1.這篇文章不是本人原創的,只是個人為了對這部分知識做一個整理和系統的輸出而編輯成的,在此鄭重地向本文所引用文章的...
    SOMCENT閱讀 13,134評論 6 174
  • 短篇集。三個短篇,前兩個不錯!很有創意!總的來說還是有點太陰暗、負面了! 內容簡介:(摘自網絡) 《只有你聽到Ca...
    烏麗曼閱讀 1,217評論 0 1
  • 每個人都希望他們有更好的面試問題。因此,我咨詢了各個領域中的創始人或CEO,問了他們最喜愛的面試問題是什么以及這些...
    小好閱讀 854評論 0 4
  • 生活在 Linux 下,折騰肯定是少不了的,所以玩壞的情況也是常有的事情。如果有一臺時光機器可以讓系統回到某個時間...
    左藍閱讀 6,553評論 0 9