Java高並發網絡編程(一)


一、OSI網絡七層模型

因特網是一個極為復雜的網絡,分層有助於我們對網絡的理解 。分層也是一種標准,為了使不同廠商的計算機能夠互相通信,以便在更大范圍內建立計算機網絡,有必要建立一個國際范圍的網絡體系結構標准。

 

 

 

 

 

 

 

ISO組織制定了OSI網絡七層模型

應用層
表示層
會話層
傳輸層
網絡層
鏈路層
物理層

而因特網只用到了五層

應用層
傳輸層
網絡層
鏈路層
物理層

低三層:

屏蔽底層網絡的復雜性

物理層:使原始的數據比特流能在物理介質上傳輸。

數據鏈路層:通過校驗、確認和反饋重發等手段,形成穩定的數據鏈路。(01010101)

網絡層:進行路由選擇和流量控制。(IP協議)

 

傳輸層:提供可靠的端口到端口的數據傳輸服務(TCP/UDP協議)。

 

高三層:

會話層:負責建立、管理和終止進程之間的會話和數據交換。

表示層:負責數據格式轉換、數據加密與解密、壓縮與解壓縮等。

應用層:為用戶的應用進程提供網絡服務。

網絡通信協議

 

 

二、傳輸層控制協議TCP

傳輸層控制協議(TCP)是Internet一個重要的傳輸層協議。TCP提供面向連接、可靠、有序、字節流傳輸服務。應用程序在使用TCP之前,必須先建立TCP連接。

 

 

 

1.TCP握手機制

檢測網絡是否通暢

 

 

 

 

 

三、用戶數據報協議UDP

用戶數據報協議UDP是Internet傳輸層協議。提供無連接、不可靠、數據盡力傳輸服務。

 

 

 

 

TCP和UDP比較

 

 

四、Socket

1.Scoket概述

IP地址:InetAddress

  • 唯一的標識Internet上的計算機
  • 本地回環地址(hostAddress):127.0.0.1 主機名(hostName):localhost
  • 不易記憶

端口號標識正在計算機上運行的進程(程序)

  • 不同的進程有不同的端口號
  • 被規定為一個16位的整數0~65535。其中,0~1023被預先定義的服務通信占用(如MySql占用端口3306,http占用端口80等)。除非我們需要訪問這些特殊服務,否則,應該使用1024~65535這些端口中的某一個進行通信,以免發生端口沖突。

 

端口號與IP地址的組合得出一個網絡套接字

我們知道兩個進程如果需要進行通訊最基本的一個前提能能夠唯一的標示一個進程,在本地進程通訊中我們可以使用PID來唯一標示一個進程,但PID只在本地唯一,網絡中的兩個進程PID沖突幾率很大,這時候我們需要另辟它徑了,我們知道IP層的ip地址可以唯一標示主機,而TCP層協議和端口號可以唯一標示主機的一個進程,這樣我們可以利用ip地址+協議+端口號唯一標示網絡中的一個進程。

能夠唯一標示網絡中的進程后,它們就可以利用socket進行通信了,什么是socket呢?我們經常把socket翻譯為套接字,socket是在應用層和傳輸層之間的一個抽象層,它把TCP/IP層復雜的操作抽象為幾個簡單的接口供應用層調用已實現進程在網絡中通信。

 

 

網絡應用程序由成對的進程組成,這些進程通過網絡相互發送報文。在一對進程之間的通信會話場景中,發起通信的進程被標識為客戶,在會話開始時等待聯系的進程是服務器。進程通過一個稱為套接字的軟件接口向網絡發送報文和從網絡接收報文。套接字是同一台主機內應用層與傳輸層之間的接口,由於該套接字是建立網絡應用程序的可編程接口,因此套接字也稱為應用程序和網路之間的應用程序編程接口(API)。應用程序開發者可以控制套接字在應用層端的一切,但是對該套接字的運輸層端幾乎沒有控制權,因此網絡編程實際上就是Socket編程

 

Internet應用最廣的網絡應用編程接口,實現與3種底層協議接口:

  • 數據報類型套接字SOCK_DGRAM(面向UDP接口)
  • 流式套接字SOCK_STREAM(面向TCP接口)
  • 原始套接字SOCK_RAW(面向網絡層協議接口IP、ICMP等)

 

主要socket API及其調用過程

 

 

 

socket是"打開—讀/寫—關閉"模式的實現,以使用TCP協議通訊的socket為例,其交互流程大概如下:

 

 

2.Java中的Socket編程

客戶端

public class SocketClient {

    public void client() {
        Socket sc = null;
        OutputStream os = null;
        InputStream is = null;
        try {
            sc = new Socket(InetAddress.getByName("127.0.0.1"), 9092);
            os = sc.getOutputStream();
            os.write("我是客戶端,我向你發送消息了".getBytes());
            sc.shutdownOutput(); // 顯式的告訴服務端發送完畢

            is = sc.getInputStream();
            byte[] b = new byte[30];
            int len;
            while ((len = is.read(b)) != -1) {
                String str = new String(b, 0, len);
                System.out.println(str);
            }

        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        } finally {

            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if (sc != null) {
                try {
                    sc.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        SocketClient client = new SocketClient();
        client.client();
    }
}

當Client程序需要從Server端獲取信息及其他服務時,應創建一個Socket對象

構造函數

 

new Socket(InetAddress.getByName("127.0.0.1"), 9092)會打開一個套接字

一旦套接字被打開,java.net.Socket類中的getInputStream方法就會返回一個InputStream對象,該對象可以像其他任何流一樣使用,接收另一端傳來的數據

getOutPutStream則是輸出流,可以傳給另一端

 

套接字離不開IP地址, Java中有IP相關的類

InetAddress類沒有提供公共的構造方法,而是提供了兩個靜態方法來獲取InetAddress實例

 

 InetAddress有如下常用方法:

 

服務器端

public class SocketServer {

    public void server() {
        ServerSocket ss = null;
        Socket s = null;
        InputStream is = null;
        OutputStream os = null;
        try {
            ss = new ServerSocket(9092);
            s = ss.accept();
            is = s.getInputStream();
            /**
             * 1、 read()方法,這個方法從輸入流中讀取數據的下一個字節。返回 0 到 255 范圍內的 int 字節值。如果因為已經到達流末尾而沒有可用的字節,則返回值 -1。 2、read(byte[] b,int
             * off,int len)方法,將輸入流中最多 len 個數據字節讀入 byte 數組。嘗試讀取len 個字節,但讀取的字節也可能小於該值。以整數形式返回實際讀取的字節數。 3、read(byte[]
             * b)方法,從輸入流中讀取一定數量的字節,並將其存儲在緩沖區數組 b 中。以整數形式返回實際讀取的字節數。
             */
            byte[] b = new byte[30];
            int len;
            while ((len = is.read(b)) != -1) {
                /**
                 * new String(bytes, offset, length) bytes為要解譯的字符串; offset為要解譯的第一個索引,比如從0開始就是從字符串bytes的第一個字符開始;
                 * length為要解譯的字符串bytes的長度
                 */
                String str = new String(b, 0, len);
                System.out.println(str);
            }
            os = s.getOutputStream();
            os.write("我是服務端,收到了你的信息".getBytes());

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if (s != null) {
                try {
                    s.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if (ss != null) {
                try {
                    ss.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }

        }
    }

    public static void main(String[] args) {
        SocketServer server = new SocketServer();
        server.server();
    }
}

服務器需隨時待命,因為不知道客戶端什么時候會發來請求,此時,我們需要使用ServerSocket,對應的是java.net.ServerSocket類

構造函數

 

 new ServerSocket(9092)建立一個監聽9092接口的服務器

ss.accept()告訴程序不停的等待,直到客戶端連接到這個接口。一旦有人通過網絡發送了正確的連接請求,並以此連接到了端口上,該方法就返回一個表示連接已經建立的Socket對象。

可以使用這個對象來得到輸入流和輸出流

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM