Socket理論知識

OSI七層網絡模型

OSI七層網絡模型(從下往上): 
這裏寫圖片描述
這裏寫圖片描述

OSI是一個理想的模型,一般的網絡系統只涉及其中的幾層,在七層模型中,每一層都提供一個特殊 的網絡功能,從網絡功能角度觀察:

  • 下面4層(物理層、數據鏈路層、網絡層和傳輸層)主要提供數據傳輸和交換功能, 即以節點到節點之間的通信爲主 
    第4層作爲上下兩部分的橋樑,是整個網絡體系結構中最關鍵的部分;
  • 上3層(會話層、表示層和應用層)則以提供用戶與應用程序之間的信息和數據處理功能爲主

簡言之,下4層主要完成通信子網的功能,上3層主要完成資源子網的功能。 
OSI七層模型詳解

TCP/IP四層模型

這裏寫圖片描述

TCP/UDP區別

這裏寫圖片描述

這裏寫圖片描述

Socket開發相關的一些概念名詞:

IP地址

IP地址是IP協議提供的一種統一的地址格式,它爲互聯網上的每一個網絡和每一臺主機分配一個邏輯地址,以此來屏蔽物理地址的差異。
  • 1
  • 2

端口

  • 用於區分不同的應用程序
  • 端口號的範圍爲0-65535,其中0-1023未系統的保留端口,我們的程序儘可能別使用這些端口!
  • IP地址和端口號組成了我們的Socket,Socket是網絡運行程序間雙向通信鏈路的終結點, 是TCP和UDP的基礎!
  • 常用協議使用的端口:HTTP:80,FTP:21,TELNET:23

TCP協議與UDP協議

TCP協議流程詳解

首先TCP/IP是一個協議簇,裏面包括很多協議的。UDP只是其中的一個。之所以命名爲TCP/IP協議, 因爲TCP,IP協議是兩個很重要的協議,就用他兩命名了。 
下面我們來講解TCP協議和UDP協議的區別: 
TCP(Transmission Control Protocol,傳輸控制協議)是面向連接的協議,即在收發數據錢 ,都需要與對面建立可靠的鏈接,這也是面試經常會問到的TCP的三次握手以及TCP的四次揮手

三次握手: 建立一個TCP連接時,需要客戶端和服務端總共發送3個包以確認連接的建立, 在Socket編程中,這一過程由客戶端執行connect來觸發,具體流程圖如下:

這裏寫圖片描述

  • 第一次握手:Client將標誌位SYN置爲1,隨機產生一個值seq=J,並將該數據包發送給Server, Client進入SYN_SENT狀態,等待Server確認。
  • 第二次握手:Server收到數據包後由標誌位SYN=1知道Client請求建立連接,Server將標誌位 SYN和ACK都置爲1,ack=J+1,隨機產生一個值seq=K,並將該數據包發送給Client以確認連接請求 ,Server進入SYN_RCVD狀態。
  • 第三次握手:Client收到確認後,檢查ack是否爲J+1,ACK是否爲1,如果正確則將標誌位ACK 置爲1,ack=K+1,並將該數據包發送給Server,Server檢查ack是否爲K+1,ACK是否爲1,如果正確則 連接建立成功,Client和Server進入ESTABLISHED狀態,完成三次握手,隨後Client與Server之間可以 開始傳輸數據了。

四次揮手: 終止TCP連接,就是指斷開一個TCP連接時,需要客戶端和服務端總共發送4個包以確認連接的斷開。 在Socket編程中,這一過程由客戶端或服務端任一方執行close來觸發,具體流程圖如下

這裏寫圖片描述

  • 第一次揮手:Client發送一個FIN,用來關閉Client到Server的數據傳送,Client進入 FIN_WAIT_1狀態
  • 第二次揮手:Server收到FIN後,發送一個ACK給Client,確認序號爲收到序號+1(與SYN相同, 一個FIN佔用一個序號),Server進入CLOSE_WAIT狀態。
  • 第三次揮手:Server發送一個FIN,用來關閉Server到Client的數據傳送,Server進入LAST_ACK 狀態。
  • 第四次揮手:Client收到FIN後,Client進入TIME_WAIT狀態,接着發送一個ACK給Server,確認序號爲收到序號+1,Server進入CLOSED狀態,完成四次揮手。 另外也可能是同時發起主動關閉的情況:

這裏寫圖片描述

另外還可能有一個常見的問題就是:爲什麼建立連接是三次握手,而關閉連接卻是四次揮手呢? 答:因爲服務端在LISTEN狀態下,收到建立連接請求的SYN報文後,把ACK和SYN放在一個報文裏 發送給客戶端。而關閉連接時,當收到對方的FIN報文時,僅僅表示對方不再發送數據了但是還 能接收數據,己方也未必全部數據都發送給對方了,所以己方可以立即close,也可以發送一些 數據給對方後,再發送FIN報文給對方來表示同意現在關閉連接,因此,己方ACK和FIN一般都會 分開發送。

UDP協議詳解

UDP(User Datagram Protocol)用戶數據報協議,非連接的協議,傳輸數據之前源端和終端不 建立連接,當它想傳送時就簡單地去抓取來自應用程序的數據,並儘可能快地把它扔到網絡上。 在發送端,UDP傳送數據的速度僅僅是受應用程序生成數據的速度、計算機的能力和傳輸帶寬 的限制;在接收端,UDP把每個消息段放在隊列中,應用程序每次從隊列中讀一個消息段。 相比TCP就是無需建立鏈接,結構簡單,無法保證正確性,容易丟包

Java中對於網絡提供的幾個關鍵類:

針對不同的網絡通信層次,Java給我們提供的網絡功能有四大類:

  • InetAddress: 用於標識網絡上的硬件資源
  • URL: 統一資源定位符,通過URL可以直接讀取或者寫入網絡上的數據
  • Socket和ServerSocket: 使用TCP協議實現網絡通信的Socket相關的類
  • Datagram: 使用UDP協議,將數據保存在數據報中,通過網絡進行通信

InetAddress:

public class InetAddressTest {
    public static void main(String[] args) throws Exception{
        //獲取本機InetAddress的實例:
        InetAddress address = InetAddress.getLocalHost();
        System.out.println("本機名:" + address.getHostName());
        System.out.println("IP地址:" + address.getHostAddress());
        byte[] bytes = address.getAddress();
        System.out.println("字節數組形式的IP地址:" + Arrays.toString(bytes));
        System.out.println("直接輸出InetAddress對象:" + address);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

基於TCP協議的Socket通信-簡易聊天室

基本介紹和使用

什麼是Socket

這裏寫圖片描述

Socket通信模型

這裏寫圖片描述

Socket通信實現步驟解析:

  • Step 1:創建ServerSocket和Socket
  • Step 2:打開連接到的Socket的輸入/輸出流
  • Step 3:按照協議對Socket進行讀/寫操作
  • Step 4:關閉輸入輸出流,以及Socket

我們接下來寫一個簡單的例子,開啓服務端後,客戶端點擊按鈕然後鏈接服務端, 並向服務端發送一串字符串,表示通過Socket鏈接上服務器~

Socket服務端的編寫

步驟

Step 1:創建ServerSocket對象,綁定監聽的端口 
Step 2:調用accept()方法監聽客戶端的請求 
Step 3:連接建立後,通過輸入流讀取客戶端發送的請求信息 
Step 4:通過輸出流向客戶端發送響應信息 
Step 5:關閉相關資源

Code

在Eclipse下創建一個Java項目,代碼如下:

package com.turing.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Step 1:創建ServerSocket對象,綁定監聽的端口 
 * Step 2:調用accept()方法監聽客戶端的請求
 * Step 3:連接建立後,通過輸入流讀取客戶端發送的請求信息 
 * Step 4:通過輸出流向客戶端發送響應信息 
 * Step 5:關閉相關資源
 *  *
 */
public class SocketServer {

    public static void main(String[] args) {

        try {
            // 1. 創建一個服務端Socket,即ServerSocket,指定綁定的端口,並監聽此端口
            ServerSocket serverSocket = new ServerSocket(9999);
            // 獲取服務端IP
            InetAddress inetAddress = InetAddress.getLocalHost();
            String ip = inetAddress.getHostAddress();
            System.out.println("服務端已就緒,等待客戶端接入,服務端ip地址: " + ip);
            // 2. 調用accept等待客戶端連接
            Socket socket = serverSocket.accept();
            // 3. 連接後獲取輸入流,讀取客戶端信息
            InputStream is = null;
            InputStreamReader isr = null;
            BufferedReader br = null;
            OutputStream os = null;
            PrintWriter pw = null;
            // 獲取輸入流
            is = socket.getInputStream();
            isr = new InputStreamReader(is, "UTF-8");
            br = new BufferedReader(isr);

            String info = null;
            while ((info = br.readLine()) != null) {
                System.out.println("客戶端發送過來的信息:" + info);
            }

            socket.shutdownInput();// 關閉輸入流
            socket.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

運行服務端 
這裏寫圖片描述

Socket客戶端的編寫

Android客戶端

步驟

Step 1:創建Socket對象,指明需要鏈接的服務器的地址和端號 
Step 2:鏈接建立後,通過輸出流向服務器發送請求信息 
Step 3:通過輸出流獲取服務器響應的信息 
Step 4:關閉相關資源

Code

package com.turing.base.activity.socket.baseuse;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;

import com.turing.base.R;
import com.turing.base.utils.AlertUtil;

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

/**
 * Step 1:創建Socket對象,指明需要鏈接的服務器的地址和端號
 Step 2:鏈接建立後,通過輸出流向服務器發送請求信息
 Step 3:通過輸出流獲取服務器響應的信息
 Step 4:關閉相關資源

 服務端Java工程:D:\workspace\ws-java-base\SocketServer
 */
public class SocketClientAct extends AppCompatActivity implements View.OnClickListener {

    private Button socketSend ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_socket_client);

        socketSend = (Button) findViewById(R.id.id_btn_sendMsg);
        socketSend.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {

        AlertUtil.showToastShort(this,"觀察服務端日誌~");
        //Android不允許在主線程(UI線程)中做網絡操作,
        // 所以這裏需要我們自己 另開一個線程來連接Socket!
        new Thread() {
            @Override
            public void run() {
                try {
                    acceptServer();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

    /**
     * 連接ServerSocket發送消息
     */
    private void acceptServer() throws  IOException{

        //1.創建客戶端Socket,指定服務器地址和端口
        Socket socket = new Socket("192.168.56.1", 9999);
        //2.獲取輸出流,向服務器端發送信息
        OutputStream os = socket.getOutputStream();//字節輸出流
        PrintWriter pw = new PrintWriter(os);//將輸出流包裝爲打印流
        //獲取客戶端的IP地址
        InetAddress address = InetAddress.getLocalHost();
        String ip = address.getHostAddress();
        // 將這個信息發送給服務端
        pw.write("客戶端" + ip + "接入服務器!!");
        pw.flush();
        socket.shutdownOutput();//關閉輸出流
        socket.close();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

因爲Android不允許在主線程(UI線程)中做網絡操作,所以這裏需要我們自己 另開一個線程來連接Socket!

運行結果: 
點擊按鈕後,服務端控制檯打印

這裏寫圖片描述

基於UDP協議的Socket通信

UDP以數據報作爲數據的傳輸載體,在進行傳輸時 首先要把傳輸的數據定義成數據報(Datagram),在數據報中指明數據要到達的Socket(主機地址 和端口號),然後再將數據以數據報的形式發送出去,然後就沒有然後了,服務端收不收到我就 不知道了,除非服務端收到後又給我回一段確認的數據報。

服務端實現步驟

Step 1:創建DatagramSocket,指定端口號 
Step 2:創建DatagramPacket 
Step 3:接收客戶端發送的數據信息 
Step 4:讀取數據

public class UPDServer {
    public static void main(String[] args) throws IOException {
        /*
         * 接收客戶端發送的數據
         */
        // 1.創建服務器端DatagramSocket,指定端口
        DatagramSocket socket = new DatagramSocket(8888);
        // 2.創建數據報,用於接收客戶端發送的數據
        byte[] data = new byte[1024];// 創建字節數組,指定接收的數據包的大小
        DatagramPacket packet = new DatagramPacket(data, data.length);
        // 3.接收客戶端發送的數據
        System.out.println("服務器端已經啓動,等待客戶端發送數據");
        socket.receive(packet);// 此方法在接收到數據報之前會一直阻塞
        // 4.讀取數據
        String info = new String(data, 0, packet.getLength());
        System.out.println("我是服務器,客戶端說:" + info);

        /*
         * 向客戶端響應數據
         */
        // 1.定義客戶端的地址、端口號、數據
        InetAddress address = packet.getAddress();
        int port = packet.getPort();
        byte[] data2 = "歡迎您!".getBytes();
        // 2.創建數據報,包含響應的數據信息
        DatagramPacket packet2 = new DatagramPacket(data2, data2.length, address, port);
        // 3.響應客戶端
        socket.send(packet2);
        // 4.關閉資源
        socket.close();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

客戶端實現步驟

Step 1:定義發送信息 
Step 2:創建DatagramPacket,包含將要發送的信息 
Step 3:創建DatagramSocket 
Step 4:發送數據

public class UDPClient {
    public static void main(String[] args) throws IOException {
        /*
         * 向服務器端發送數據
         */
        // 1.定義服務器的地址、端口號、數據
        InetAddress address = InetAddress.getByName("localhost");
        int port = 8888;
        byte[] data = "用戶名:admin;密碼:123".getBytes();
        // 2.創建數據報,包含發送的數據信息
        DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
        // 3.創建DatagramSocket對象
        DatagramSocket socket = new DatagramSocket();
        // 4.向服務器端發送數據報
        socket.send(packet);

        /*
         * 接收服務器端響應的數據
         */
        // 1.創建數據報,用於接收服務器端響應的數據
        byte[] data2 = new byte[1024];
        DatagramPacket packet2 = new DatagramPacket(data2, data2.length);
        // 2.接收服務器響應的數據
        socket.receive(packet2);
        // 3.讀取數據
        String reply = new String(data2, 0, packet2.getLength());
        System.out.println("我是客戶端,服務器說:" + reply);
        // 4.關閉資源
        socket.close();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

總結

將數據轉換爲字節,然後放到DatagramPacket(數據報包中),發送的 時候帶上接受者的IP地址和端口號,而接收時,用一個字節數組來緩存!發送的時候需要創建一個 DatagramSocket(端到端通信的類)對象,然後調用send方法給接受者發送數據報包

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章