Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 亚洲免费视频网,一二三区在线视频,欧美激情一区二区三区免费观看

          整合營銷服務(wù)商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          HTML框架分層設(shè)計

          算機網(wǎng)絡(luò)中的OSI七層模型

          計算機網(wǎng)絡(luò)中的OSI(Open Systems Interconnection)七層模型是一種理論框架,用于描述計算機網(wǎng)絡(luò)中數(shù)據(jù)通信的過程。OSI模型將計算機網(wǎng)絡(luò)通信過程劃分為七個層次,每個層次都有其特定的功能和協(xié)議。這種分層結(jié)構(gòu)有助于研究和理解計算機網(wǎng)絡(luò)中的通信原理。以下是OSI七層模型的各個層次及其主要功能:

          應用層是OSI模型的第七層,也是網(wǎng)絡(luò)應用程序和網(wǎng)絡(luò)協(xié)議之間的接口。應用層主要負責為用戶提供各類應用服務(wù),如文件傳輸、電子郵件、Web瀏覽等。

          表示層是OSI模型的第六層,主要負責處理在網(wǎng)絡(luò)中傳輸?shù)臄?shù)據(jù)的表示方式,如數(shù)據(jù)加密、解密、壓縮、解壓縮等。表示層確保了不同系統(tǒng)之間的數(shù)據(jù)兼容性。

          會話層是OSI模型的第五層,主要負責建立、維護和終止應用程序之間的通信會話。會話層提供了數(shù)據(jù)交換的同步和確認機制。

          傳輸層是OSI模型的第四層,主要負責在源主機和目標主機之間提供可靠的、端到端的數(shù)據(jù)傳輸服務(wù)。傳輸層通過分段、封裝和重組數(shù)據(jù)來實現(xiàn)可靠的數(shù)據(jù)傳輸。常見的傳輸層協(xié)議包括TCP(傳輸控制協(xié)議)和UDP(用戶數(shù)據(jù)報協(xié)議)。

          網(wǎng)絡(luò)層是OSI模型的第三層,主要負責將數(shù)據(jù)包從源主機路由到目標主機。網(wǎng)絡(luò)層主要負責邏輯尋址、路由選擇和分組轉(zhuǎn)發(fā)。常見的網(wǎng)絡(luò)層協(xié)議包括IP(互聯(lián)網(wǎng)協(xié)議)和ICMP(互聯(lián)網(wǎng)控制報文協(xié)議)。

          數(shù)據(jù)鏈路層是OSI模型的第二層,主要負責將網(wǎng)絡(luò)層傳來的數(shù)據(jù)包封裝成幀(Frame),并在同一局域網(wǎng)內(nèi)進行傳輸。數(shù)據(jù)鏈路層主要負責物理尋址、數(shù)據(jù)成幀、錯誤檢測和流量控制。常見的數(shù)據(jù)鏈路層協(xié)議包括以太網(wǎng)(Ethernet)、令牌環(huán)(Token Ring)和無線局域網(wǎng)(Wi-Fi)等。

          物理層是OSI模型的第一層,主要負責在物理介質(zhì)上實現(xiàn)比特流的透明傳輸。物理層主要關(guān)注硬件接口、電氣特性、光纖、無線傳輸?shù)确矫娴膯栴}。

          OSI七層模型提供了一個通用的框架,幫助研究和理解計算機網(wǎng)絡(luò)中的通信原理。實際應用中,我們通常使用TCP/IP四層模型,它包括了應用層、傳輸層、網(wǎng)絡(luò)層和鏈路層,與OSI模型有一定的對應關(guān)系。

          HTML框架的必要性

          HTML框架進行分層設(shè)計的主要原因是為了提高代碼的可讀性、可維護性和可重用性。將HTML框架分層可以提高整體項目的結(jié)構(gòu)和邏輯,便于開發(fā)者更好地理解和修改代碼。分層設(shè)計具有以下優(yōu)點:

          1. 提高可讀性:通過將HTML框架劃分為不同的層次,可以使代碼結(jié)構(gòu)更清晰,有助于開發(fā)者快速理解代碼的功能。
          2. 便于維護:分層設(shè)計有助于將功能模塊化,這樣可以方便地修改或替換某個模塊,而不會影響其他部分的代碼。這有助于提高項目的可維護性。
          3. 可重用性:將HTML框架分層可以將公共部分提取為可重用的組件,這樣可以在不同項目中重復使用這些組件,提高開發(fā)效率。
          4. 適應性:分層設(shè)計可以讓HTML框架更容易適應不同的設(shè)備和屏幕尺寸,提高項目的兼容性。
          5. 便于協(xié)作:在大型項目中,通常會有多個開發(fā)者參與。通過分層設(shè)計,開發(fā)者可以專注于自己的模塊,減少代碼沖突和溝通成本。

          HTML框架的組成

          HTML框架包括Application層``middleware層``route層``codec層``transport層 Application層 應用層通常包括與業(yè)務(wù)邏輯相關(guān)的代碼,如Web應用程序的控制器(Controller)、視圖(View)和模型(Model)。應用層的主要作用是處理用戶請求并返回相應的響應。

          Middleware層 中間件層是介于應用層和底層框架之間的一層,負責處理一些通用的功能,如身份驗證、授權(quán)、緩存、日志記錄等。中間件層有助于將業(yè)務(wù)邏輯與通用功能分離,使得應用層更加簡潔和易于維護。

          Route層 路由層負責處理HTTP請求的URL和HTTP方法(如GET、POST等),將請求分發(fā)到相應的控制器和方法。路由層的主要作用是根據(jù)URL映射來定位具體的功能代碼。

          Codec層 編解碼層負責處理數(shù)據(jù)的編碼和解碼。在Web開發(fā)中,編碼和解碼通常涉及到HTML、CSS、JavaScript等前端技術(shù)的處理,以及JSON、XML等數(shù)據(jù)交換格式的處理。編解碼層的主要作用是將數(shù)據(jù)轉(zhuǎn)換為特定的格式,以便在不同層之間進行傳輸和處理。

          Transport層 傳輸層負責處理底層的網(wǎng)絡(luò)通信,如TCP、UDP等協(xié)議的使用。在Web開發(fā)中,傳輸層通常涉及到HTTP協(xié)議的處理,包括請求和響應的創(chuàng)建、發(fā)送和接收。傳輸層的主要作用是確保數(shù)據(jù)的可靠傳輸和在網(wǎng)絡(luò)中的正確路由。

          這些層次在實際應用中可能因框架和場景的不同而有所差異。但是,從您提供的描述來看,它們分別負責處理Web應用程序中的不同功能,共同構(gòu)成了一個完整的Web開發(fā)框架。

          HTML框架和服務(wù)端客戶端之間的通信對比

          Application層應用層設(shè)計

          應用層設(shè)計主要是設(shè)置各種接口,用于路由使用。

          例如在字節(jié)后端進階版中的大項目中的注冊接口。

          /douyin/user/register/ - 用戶注冊接口

          新用戶注冊時提供用戶名,密碼,昵稱即可,用戶名需要保證唯一。創(chuàng)建成功后返回用戶 id 和權(quán)限token.

          接口類型

          POST

          接口定義

          go復制代碼syntax = "proto2";
          package douyin.core;
          
          message douyin_user_register_request {
            required string username = 1; // 注冊用戶名,最長32個字符
            required string password = 2; // 密碼,最長32個字符
          }
          
          message douyin_user_register_response {
            required int32 status_code = 1; // 狀態(tài)碼,0-成功,其他值-失敗
            optional string status_msg = 2; // 返回狀態(tài)描述
            required int64 user_id = 3; // 用戶id
            required string token = 4; // 用戶鑒權(quán)token
          }
          
          go復制代碼func Register(username, password string) (id int64, token int64, err error) {
             if len(username) > 32 {
                return 0, 0, errors.New("用戶名過長,不可超過32位")
             }
             if len(password) > 32 {
                return 0, 0, errors.New("密碼過長,不可超過32位")
             }
             // 先查布隆過濾器,不存在直接返回錯誤,降低數(shù)據(jù)庫的壓力
             if userNameFilter.TestString(username) {
                return 0, 0, errors.New("用戶名已經(jīng)存在!")
             }
             //雪花算法生成token
             node, err := snowflake.NewNode(1) //這里的userIdInt64就是 User.Id(主鍵)
             if err != nil {
                log.Println("雪花算法生成id錯誤!")
                log.Println(err)
             }
             token1 := node.Generate().Int64()
             tokenStr := strconv.FormatInt(token1, 10)
             user := domain.User{}
             // 再查緩存
             data, err := dao.RedisClient.Get(context.Background(), tokenStr).Result()
             if err == redis.Nil {
                fmt.Println("token does not exist")
             } else if err != nil {
                fmt.Println("Error:", err)
             } else {
                num, err := strconv.ParseInt(data, 10, 64)
                if err != nil {
                   fmt.Println("Error:", err)
                   return num, 0, err
                }
          
                return num, token1, nil
             }
             //在查數(shù)據(jù)庫
             user = domain.User{}
             dao.DB.Model(&domain.User{}).Where("name = ?", username).Find(&user)
             if user.Id != 0 {
                return 0, 0, errors.New("用戶已存在")
             }
             user.Name = username
             // 加密存儲用戶密碼
             user.Salt = randSalt()
             buf := bytes.Buffer{}
             buf.WriteString(username)
             buf.WriteString(password)
             buf.WriteString(user.Salt)
             pwd, err1 := bcrypt.GenerateFromPassword(buf.Bytes(), bcrypt.MinCost)
             if err1 != nil {
                return 0, 0, err
             }
             user.Pwd = string(pwd)
          
             //存在mysql里邊
             dao.DB.Model(&domain.User{}).Create(&user)
             //再把用戶id作為鍵 用戶的所有信息作為值存在其中
             //用戶信息的緩存是 保存在redis中 一個以id為鍵 user json為值
             jsonuser, err1 := MarshalUser(user)
             if err1 != nil {
                fmt.Println("err1", err1)
                return 0, 0, err1
             }
             err = dao.RedisClient.Set(context.Background(), strconv.FormatInt(user.Id, 10), jsonuser, 0).Err()
             if err != nil {
                fmt.Println("err", err)
                return 0, 0, err
             }
             // 布隆過濾器中加入新用戶
             userIdFilter.AddString(strconv.FormatInt(user.Id, 10))
             userNameFilter.AddString(username)
             return user.Id, token1, nil
          }
          

          本接口注冊功能實現(xiàn):把所有信息存在mysql里邊當然redis里邊也存在這些信息,當然username也存在了布容過濾器中去,當接收到用戶的username的時候我們現(xiàn)在布容過濾器中先查詢是否存在如果存在則直接返回err,不存在然后再在redis里邊查詢,因為redis相比于mysql是更為輕量級的所以我們要先在redis里邊進行查,如果查不到再進mysql里邊查去,查不到說明沒有注冊過,可以注冊。

          命名規(guī)范

          遵循命名規(guī)范原則。

          Middleware層中間件

          gin框架里的中間件分為全局中間件,局部中間件。那么什么是中間件?中間件是為應用提供通用服務(wù)和功能的軟件。數(shù)據(jù)管理、應用服務(wù)、消息傳遞、身份驗證和 API 管理通常都要通過中間件。在gin框架里,就是我們的所有API接口都要經(jīng)過我們的中間件,我們可以在中間件做一些攔截處理。

          中間件常用模型

          全局中間件

          這個是在服務(wù)啟動就開始注冊,全局意味著所有API接口都會經(jīng)過這里。Gin的中間件是通過Use方法設(shè)置的,它接收一個可變參數(shù),所以我們同時可以設(shè)置多個中間件。

          首先定義如下

          go復制代碼// 1.創(chuàng)建路由
          r := gin.Default()  //默認帶Logger(), Recovery()這兩個內(nèi)置中間件
          r:= gin.New()      //不帶任何中間件
          // 注冊中間件  
          r.Use(MiddleWare())
          r.Use(MiddleWare2())
          

          注意的是

          gin.Default()默認使用了Logger和Recovery中間件,其中:Logger中間件將日志寫入gin.DefaultWriter,即使配置了GIN_MODE=release。Recovery中間件會recover任何panic。如果有panic的話,會寫入500響應碼。如果不想使用上面兩個默認的中間件,可以使用gin.New()新建一個沒有任何默認中間件的路由。

          go復制代碼// 定義中間
          func MiddleWare() gin.HandlerFunc {
              return func(c *gin.Context) {
                  t := time.Now()
                  fmt.Println("中間件開始執(zhí)行了")
                  // 設(shè)置變量到Context的key中,可以通過Get()取
                  c.Set("request", "這是中間件設(shè)置的值")
                  status := c.Writer.Status()
                  fmt.Println("中間件執(zhí)行完畢", status)        
                  t2 := time.Since(t)
                  fmt.Println("time:", t2)
              }
          }
          

          然后啟動我們的服務(wù),訪問任意一個接口可以看到輸出如下

          這是請求先到了中間件,然后在到我們的API接口。在中間件里可以設(shè)置變量到Context的key中,然后在我們的API接口取值。

          go復制代碼        r.GET("/", func(c *gin.Context) {
                      // 取值
                      req, _ := c.Get("request")
                      fmt.Println("request:", req)
                      // 頁面接收
                      c.JSON(200, gin.H{"request": req})
                  })
          

          這時候在訪問就可以看到中間件設(shè)置的值是

          next方法是在中間件里面使用,這個是執(zhí)行后續(xù)中間件請求處理的意思(含沒有執(zhí)行的中間件和我們定義的GET方法處理,如果連續(xù)注冊幾個中間件則會是按照順序先進后出的執(zhí)行,遇到next就去執(zhí)行下一個中間件里的next前面方法。

          go復制代碼        // 執(zhí)行函數(shù)
                  c.Next()
                  // 中間件執(zhí)行完后續(xù)的一些事情
          

          局部中間件

          局部中間件意味著部分接口才會生效,只在局部使用,這時候訪問http:127.0.0.1:8000/ 才會看到中間件的日志打印,其他API接口則不會出現(xiàn)。

          go復制代碼   //局部中間件使用
              r.GET("/", MiddleWare(), func(c *gin.Context) {
                  // 取值
                  req, _ := c.Get("request")
                  fmt.Println("request:", req)
                  // 頁面接收
                  c.JSON(200, gin.H{"request": req})
              })
          

          gin內(nèi)置中間件

          go復制代碼
          func BasicAuth(accounts Accounts) HandlerFunc
          
          func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc
          
          func Bind(val interface{}) HandlerFunc
          
          func ErrorLogger() HandlerFunc
          
          func ErrorLoggerT(typ ErrorType) HandlerFunc
          
          func Logger() HandlerFunc
          
          func LoggerWithConfig(conf LoggerConfig) HandlerFunc
          
          func LoggerWithFormatter(f LogFormatter) HandlerFunc
          
          func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc
          
          func Recovery() HandlerFunc
          
          func RecoveryWithWriter(out io.Writer) HandlerFunc
          
          func WrapF(f http.HandlerFunc) HandlerFunc
          
          func WrapH(h http.Handler) HandlerFunc
          

          總結(jié)

          通過自定義中間件,我們可以很方便的攔截請求,來做一些我們需要做的事情,比如日志記錄、授權(quán)校驗、各種過濾等等。

          route層路由層

          Gin 是一個標準的 Web 服務(wù)框架,遵循 Restful API 接口規(guī)范,其路由庫是基于 httproute 實現(xiàn)的。

          本節(jié)將從 Gin 路由開始,詳細講述各種路由場景下,如何通過 Gin 來實現(xiàn)。

          基本路由

          1. GET:用于處理從客戶端發(fā)起的HTTP GET請求。GET請求用于從服務(wù)器獲取數(shù)據(jù),不應對服務(wù)器上的數(shù)據(jù)進行更改。例如,獲取用戶信息、獲取文章列表等。
          2. POST:用于處理從客戶端發(fā)起的HTTP POST請求。POST請求通常用于向服務(wù)器發(fā)送數(shù)據(jù),用于創(chuàng)建新的資源或更新已有的資源。例如,用戶注冊、發(fā)布文章、更新用戶信息等。
          3. PUT:用于處理從客戶端發(fā)起的HTTP PUT請求。PUT請求通常用于更新服務(wù)器上的資源。例如,更新用戶信息、更新文章內(nèi)容等。
          4. DELETE:用于處理從客戶端發(fā)起的HTTP DELETE請求。DELETE請求通常用于從服務(wù)器刪除資源。例如,刪除用戶賬戶、刪除文章等。
          5. PATCH:用于處理從客戶端發(fā)起的HTTP PATCH請求。PATCH請求通常用于對服務(wù)器上的資源進行部分更新。例如,更新用戶的部分信息、更新文章標題等。
          6. OPTIONS:用于處理從客戶端發(fā)起的HTTP OPTIONS請求。OPTIONS請求用于獲取服務(wù)器支持的HTTP方法。例如,跨域資源共享(CORS)場景。
          7. HEAD:用于處理從客戶端發(fā)起的HTTP HEAD請求。HEAD請求類似于GET請求,但不返回響應體。主要用于獲取響應頭信息。例如,檢查資源是否存在,但不需要獲取資源內(nèi)容。
          8. ANY:用于處理任何HTTP方法(GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD)的請求。適用于處理多種HTTP方法的情況。
          9. CONNECT:用于處理從客戶端發(fā)起的HTTP CONNECT請求。CONNECT請求通常用于建立客戶端與服務(wù)器之間的隧道,用于代理或其他場景。
          10. TRACE:用于處理從客戶端發(fā)起的HTTP TRACE請求。TRACE請求用于測試或診斷網(wǎng)絡(luò)連接。服務(wù)器應當返回原始的請求信息,以便客戶端可以檢查中間代理或防火墻是否進行了修改。

          示例

          字節(jié)大項目注冊接口

          go復制代碼syntax = "proto2";
          package douyin.core;
          
          message douyin_user_register_request {
            required string username = 1; // 注冊用戶名,最長32個字符
            required string password = 2; // 密碼,最長32個字符
          }
          
          message douyin_user_register_response {
            required int32 status_code = 1; // 狀態(tài)碼,0-成功,其他值-失敗
            optional string status_msg = 2; // 返回狀態(tài)描述
            required int64 user_id = 3; // 用戶id
            required string token = 4; // 用戶鑒權(quán)token
          }
          
          go復制代碼func Register(c *gin.Context) {
          
             username := c.Query("username")
             password := c.Query("password")
             id, token, err := service.Register(username, password)
             if err != nil {
                c.JSON(http.StatusOK, domain.Response{StatusCode: 1, StatusMsg: err.Error()})
             } else {
          
                c.JSON(http.StatusOK, domain.UserLoginResponse{
                   //可以直接去掉
                   Response: domain.Response{StatusCode: 0},
                   Id:       id,
                   Token:    token,
                })
             }
          }
          

          go復制代碼package main
          
          import (
             "github.com/gin-gonic/gin"
             "github.com/goTouch/TicTok_SimpleVersion/controller"
          )
          
          func initRouter(r *gin.Engine) {
             // public directory is used to serve static resources
             r.Static("/static", "./public")
          
             apiRouter := r.Group("/douyin")
          
             // basic apis
             //controller.VerifyToken,
             apiRouter.POST("/user/", controller.UserInfo)
             apiRouter.POST("/user/register/", controller.LoginLimit, controller.Register)
             apiRouter.POST("/user/login/", controller.LoginLimit, controller.Login)
             }
          

          codec層

          在Web開發(fā)中,編碼和解碼通常涉及到HTML、CSS、JavaScript等前端技術(shù)的處理,以及JSON、XML等數(shù)據(jù)交換格式的處理。編解碼層的主要作用是將數(shù)據(jù)轉(zhuǎn)換為特定的格式,以便在不同層之間進行傳輸和處理。

          示例

          在postman中的示例 json

          xml

          html復制代碼{"status_code":1,"status_msg":"redis: nil"}
          {"status_code":2,"status_msg":"no multipart boundary param in Content-Type"}
          
          Text復制代碼{"status_code":1,"status_msg":"redis: nil"}
          {"status_code":2,"status_msg":"no multipart boundary param in Content-Type"}
          
          Auto復制代碼{
              "status_code": 1,
              "status_msg": "redis: nil"
          }{
              "status_code": 2,
              "status_msg": "no multipart boundary param in Content-Type"
          }
          
          

          transport層傳輸層

          傳輸層負責處理底層的網(wǎng)絡(luò)通信,如TCP、UDP等協(xié)議的使用。在Web開發(fā)中,傳輸層通常涉及到HTTP協(xié)議的處理,包括請求和響應的創(chuàng)建、發(fā)送和接收。傳輸層的主要作用是確保數(shù)據(jù)的可靠傳輸和在網(wǎng)絡(luò)中的正確路由。

          golang語言中net/http這個庫中的conn 他是BIO自帶阻塞

          1. BIO (Blocking I/O)

          同步阻塞I/O模式,數(shù)據(jù)的讀取寫入必須阻塞在一個線程內(nèi)等待其完成。

          1.1 傳統(tǒng) BIO

          BIO通信(一請求一應答)模型圖如下(圖源網(wǎng)絡(luò),原出處不明):

          采用 BIO 通信模型 的服務(wù)端,通常由一個獨立的 Acceptor 線程負責監(jiān)聽客戶端的連接。我們一般通過在 while(true) 循環(huán)中服務(wù)端會調(diào)用 accept() 方法等待接收客戶端的連接的方式監(jiān)聽請求,請求一旦接收到一個連接請求,就可以建立通信套接字在這個通信套接字上進行讀寫操作,此時不能再接收其他客戶端連接請求,只能等待同當前連接的客戶端的操作執(zhí)行完成, 不過可以通過多線程來支持多個客戶端的連接,如上圖所示。

          如果要讓 BIO 通信模型 能夠同時處理多個客戶端請求,就必須使用多線程(主要原因是 socket.accept()、 socket.read()、 socket.write() 涉及的三個主要函數(shù)都是同步阻塞的),也就是說它在接收到客戶端連接請求之后為每個客戶端創(chuàng)建一個新的線程進行鏈路處理,處理完成之后,通過輸出流返回應答給客戶端,線程銷毀。這就是典型的 一請求一應答通信模型 。我們可以設(shè)想一下如果這個連接不做任何事情的話就會造成不必要的線程開銷,不過可以通過 線程池機制 改善,線程池還可以讓線程的創(chuàng)建和回收成本相對較低。使用FixedThreadPool 可以有效的控制了線程的最大數(shù)量,保證了系統(tǒng)有限的資源的控制,實現(xiàn)了N(客戶端請求數(shù)量):M(處理客戶端請求的線程數(shù)量)的偽異步I/O模型(N 可以遠遠大于 M),下面一節(jié)"偽異步 BIO"中會詳細介紹到。

          我們再設(shè)想一下當客戶端并發(fā)訪問量增加后這種模型會出現(xiàn)什么問題?

          程是寶貴的資源,線程的創(chuàng)建和銷毀成本很高,除此之外,線程的切換成本也是很高的。尤其在 Linux 這樣的操作系統(tǒng)中,線程本質(zhì)上就是一個進程,創(chuàng)建和銷毀線程都是重量級的系統(tǒng)函數(shù)。如果并發(fā)訪問量增加會導致線程數(shù)急劇膨脹可能會導致線程堆棧溢出、創(chuàng)建新線程失敗等問題,最終導致進程宕機或者僵死,不能對外提供服務(wù)。 golang實現(xiàn)BIO

          NIO

          NIO: NIO是一種同步非阻塞IO, 基于Reactor模型來實現(xiàn)的。其實相當于就是一個線程處理大量的客戶端的請求,通過一個線程輪詢大量的channel,每次就獲取一批有事件的channel,然后對每個請求啟動一個線程處理即可。這里的核心就是非阻塞,就那個selector一個線程就可以不停輪詢channel,所有客戶端請求都不會阻塞,直接就會進來,大不了就是等待一下排著隊而已。這里面優(yōu)化BIO的核心就是,一個客戶端并不是時時刻刻都有數(shù)據(jù)進行交互,沒有必要死耗著一個線程不放,所以客戶端選擇了讓線程歇一歇,只有客戶端有相應的操作的時候才發(fā)起通知,創(chuàng)建一個線程來處理請求。
          ————————————————
          NIO:模型圖

          Reactor模型:

          NIO核心組件詳細講解

          學習NIO先來搞清楚一些相關(guān)的概念,NIO通訊有哪些相關(guān)組件,對應的作用都是什么,之間有哪些聯(lián)系?

          多路復用機制實現(xiàn)Selector

          首先我們來了解下傳統(tǒng)的Socket網(wǎng)絡(luò)通訊模型。

          傳統(tǒng)Socket通訊原理圖

          為什么傳統(tǒng)的socket不支持海量連接

          每次一個客戶端接入,都是要在服務(wù)端創(chuàng)建一個線程來服務(wù)這個客戶端的,這會導致大量的客戶端的時候,服務(wù)端的線程數(shù)量可能達到幾千甚至幾萬,幾十萬,這會導致服務(wù)器端程序負載過高,不堪重負,最終系統(tǒng)崩潰死掉。

          • 接著來看下NIO是如何基于Selector實現(xiàn)多路復用機制支持的海量連接。

          NIO原理圖

          多路復用機制是如何支持海量連接

          NIO的線程模型 對Socket發(fā)起的連接不需要每個都創(chuàng)建一個線程,完全可以使用一個Selector來多路復用監(jiān)聽N多個Channel是否有請求,該請求是對應的連接請求,還是發(fā)送數(shù)據(jù)的請求,這里面是基于操作系統(tǒng)底層的Select通知機制的,一個Selector不斷的輪詢多個Channel,這樣避免了創(chuàng)建多個線程,只有當莫個Channel有對應的請求的時候才會創(chuàng)建線程,可能說1000個請求, 只有100個請求是有數(shù)據(jù)交互的, 這個時候可能server端就提供10個線程就能夠處理這些請求。這樣的話就可以避免了創(chuàng)建大量的線程。

          NIO如何通過Buffer來緩沖數(shù)據(jù)的

          NIO中的Buffer是個什么東西 ?

          學習NIO,首當其沖就是要了解所謂的Buffer緩沖區(qū),這個東西是NIO里比較核心的一個部分,一般來說,如果你要通過NIO寫數(shù)據(jù)到文件或者網(wǎng)絡(luò),或者是從文件和網(wǎng)絡(luò)讀取數(shù)據(jù)出來此時就需要通過Buffer緩沖區(qū)來進行。Buffer的使用一般有如下幾個步驟:

          寫入數(shù)據(jù)到Buffer,調(diào)用flip()方法,從Buffer中讀取數(shù)據(jù),調(diào)用clear()方法或者compact()方法。

          Buffer中對應的Position, Mark, Capacity,Limit都啥?

          capacity: 緩沖區(qū)容量的大小,就是里面包含的數(shù)據(jù)大小。
          limit: 對buffer緩沖區(qū)使用的一個限制,從這個index開始就不能讀取數(shù)據(jù)了。
          position: 代表著數(shù)組中可以開始讀寫的index, 不能大于limit。
          mark: 是類似路標的東西,在某個position的時候,設(shè)置一下mark,此時就可以設(shè)置一個標記,后續(xù)調(diào)用reset()方法可以把position復位到當時設(shè)置的那個mark上去,把position或limit調(diào)整為小于mark的值時,就丟棄這個mark。如果使用的是Direct模式創(chuàng)建的Buffer的話,就會減少中間緩沖直接使用的是DirectorBuffer來進行數(shù)據(jù)的存儲。
          ————————————————

          如何通過Channel和FileChannel讀取Buffer數(shù)據(jù)寫入磁盤的

          NIO中,Channel是什么?

          Channel是NIO中的數(shù)據(jù)通道,類似流,但是又有些不同,Channel即可從中讀取數(shù)據(jù),又可以從寫數(shù)據(jù)到通道中,但是流的讀寫通常是單向的。Channel可以異步的讀寫。Channel中的數(shù)據(jù)總是要先讀到一個Buffer中,或者從緩沖區(qū)中將數(shù)據(jù)寫到通道中。

          FileChannel的作用是什么 Buffer有不同的類型,同樣Channel也有好幾個類型。 FileChannel,DatagramChannel,SocketChannel,ServerSocketChannel。這些通道涵蓋了UDP 和 TCP 網(wǎng)絡(luò)IO,以及文件IO。而FileChannel就是文件IO對應的管道, 在讀取文件的時候會用到這個管道。 golang的NIO

          前面一篇文章:「高頻面試題」瀏覽器從輸入url到頁面展示中間發(fā)生了什么 中,我們有對瀏覽器的渲染流程做了一個概括性的介紹,今天這篇文章我們將深入學習這部分內(nèi)容。

          對于很多前端開發(fā)來說,平常做工主要專注于業(yè)務(wù)開發(fā),對瀏覽器的渲染階段可能不是很了解。實際上這個階段很重要,了解瀏覽器的渲染過程,能讓我們知道我們寫的HTML、CSS、JS代碼是如何被解析,并最終渲染成一個頁面的,在頁面性能優(yōu)化的時候有相應的解決思路。

          我們先來看一個問題:

          HTML、CSS、JS文件在瀏覽器中是如何轉(zhuǎn)化成頁面的?

          如果你回答不上來,那就往下看吧。

          按照渲染的時間順序,渲染過程可以分為下面幾個子階段:構(gòu)建DOM樹、樣式計算、布局階段、分層、柵格化和合成顯示。

          下面詳細看下每個階段都做了哪些事情。

          1. 構(gòu)建DOM樹

          HTML文檔描述一個頁面的結(jié)構(gòu),但是瀏覽器無法直接理解和使用HTML,所以需要通過HTML解析器將HTML轉(zhuǎn)換成瀏覽器能夠理解的結(jié)構(gòu)——DOM樹。

          HTML文檔中所有內(nèi)容皆為節(jié)點,各節(jié)點之間有層級關(guān)系,彼此相連,構(gòu)成DOM樹。

          構(gòu)建過程:讀取HTML文檔的字節(jié)(Bytes),將字節(jié)轉(zhuǎn)換成字符(Chars),依據(jù)字符確定標簽(Tokens),將標簽轉(zhuǎn)換成節(jié)點(Nodes),以節(jié)點為基準構(gòu)建DOM樹。參考下圖:

          打開Chrome的開發(fā)者工具,在控制臺輸入 document 后回車,就能看到一個完整的DOM樹結(jié)構(gòu),如下圖所示:

          在控制臺打印出來的DOM結(jié)構(gòu)和HTML內(nèi)容幾乎一樣,但和HTML不同的是,DOM是保存在內(nèi)存中的樹狀結(jié)構(gòu),可以通過JavaScript來查詢或修改其內(nèi)容。

          2. 樣式計算

          樣式計算這個階段,是為了計算出DOM節(jié)點中每個元素的表現(xiàn)樣式。

          2.1 解析CSS

          CSS樣式可以通過下面三種方式引入:

          • 通過link引用外部的CSS文件
          • style 標簽內(nèi)的CSS
          • 元素的style屬性內(nèi)嵌的CSS

          和HTML一樣,瀏覽器無法直接理解純文本的CSS樣式,需要通過CSS解析器將CSS解析成 styleSheets 結(jié)構(gòu),也就是我們常說的 CSSOM樹。

          styleSheets結(jié)構(gòu)同樣具備查詢和修改功能:

          document.styleSheets

          2.2 屬性值標準化

          屬性值標準化看字面意思有點不好理解,我們通過下面一個例子來看看什么是屬性值標準化:

          在寫CSS樣式的時候,我們在設(shè)置color屬性值的時候,經(jīng)常會用white、red等,但是這種值瀏覽器的渲染引擎不容易理解,所以需要將所有值轉(zhuǎn)換成渲染引擎容易理解的、標準化的計算值,這個過程就是屬性值標準化。

          white標準化后的值為 rgb(255, 255, 255)

          2.3 計算DOM樹中每個節(jié)點的樣式

          完成樣式的屬性值標準化后,就需要計算每個節(jié)點的樣式屬性,這個階段CSS有兩個規(guī)則我們需要清楚:

          • 繼承規(guī)則:每個DOM節(jié)點都包含有父節(jié)點的樣式
          • 層疊規(guī)則:層疊是CSS的一個基本特征,是一個定義了如何合并來自多個源的屬性值的算法。

          樣式計算階段是為了計算出DOM節(jié)點中每個元素的具體樣式,在計算過程中需要遵守CSS的繼承和層疊兩個規(guī)則。

          該階段最終輸出的內(nèi)容是每個DOM節(jié)點的樣式,并被保存在 ComputedStyle 的結(jié)構(gòu)中。

          3. 布局階段

          經(jīng)過上面的兩個步驟,我們已經(jīng)拿到了DOM樹和DOM樹中元素的樣式,接下來需要計算DOM樹中可見元素的幾何位置,這個計算過程就是布局。

          3.1 創(chuàng)建布局樹

          在DOM樹中包含了一些不可見的元素,例如 head 標簽,設(shè)置了 display:none 屬性的元素,所以我們需要額外構(gòu)建一棵只包含可見元素的布局樹。

          構(gòu)建過程:從DOM樹的根節(jié)點開始遍歷,將所有可見的節(jié)點加到布局樹中,忽略不可見的節(jié)點。

          3.2 布局計算

          到這里我們就有了一棵構(gòu)建好的布局樹,就可以開始計算布局樹節(jié)點的坐標位置了。從根節(jié)點開始遍歷,結(jié)合上面計算得到的樣式,確定每個節(jié)點對象在頁面上的具體大小和位置,將這些信息保存在布局樹中。

          布局階段的輸出是一個盒子模型,它會精確地捕獲每個元素在屏幕內(nèi)的確切位置與大小。

          4. 分層

          現(xiàn)在我們已經(jīng)有了布局樹,也知道了每個元素的具體位置信息,但是還不能開始繪制頁面,因為頁面中會有像3D變換、頁面滾動、或者用 z-index 進行z軸排序等復雜效果,為了更方便實現(xiàn)這些效果,渲染引擎還需要為特定的節(jié)點生成專用的圖層,并生成一棵對應的圖層樹(LayerTree)。

          在Chrome瀏覽器中,我們可以打開開發(fā)者工具,選擇 Elements-Layers 標簽,就可以看到頁面的分層情況,如下圖所示:

          瀏覽器的頁面實際上被分成了很多圖層,這些圖層疊加后合成了最終的頁面。

          到這里,我們構(gòu)建了兩棵樹:布局樹和圖層樹。下面我們來看下這兩棵樹之間的關(guān)系:

          正常情況下,并不是布局樹的每個節(jié)點都包含一個圖層,如果一個節(jié)點沒有對應的圖層,那么這個節(jié)點就從屬于父節(jié)點的圖層。

          那節(jié)點要滿足什么條件才會被提升為一個單獨的圖層?只要滿足下面其中一個條件即可:

          • 擁有層疊上下文屬性的元素會被提升為單獨的一個圖層
          • 需要剪裁(clip)的地方也會被創(chuàng)建為圖層。

          5. 圖層繪制

          構(gòu)建好圖層樹之后,渲染引擎就會對圖層樹中的每個圖層進行繪制。

          渲染引擎實現(xiàn)圖層繪制,會把一個圖層的繪制拆分成很多小的繪制指令,然后將這些指令按照順序組成一個繪制列表。

          6. 柵格化(raster)操作

          繪制一個圖層時會生成一個繪制列表,這只是用來記錄繪制順序和繪制指令的列表,實際上繪制操作是由渲染引擎中的合成線程來完成的。

          通過下圖來看下渲染主線程和合成線程之間的關(guān)系:

          當圖層的繪制列表準備好后,主線程會把該繪制列表提交給合成線程,合成線程開始工作。

          首先合成線程會將圖層劃分為圖塊(tile),圖塊大小通常是 256256 或者 512512。

          然后合成線程會按照視口附近的圖塊來優(yōu)先生成位圖,實際生成位圖的操作是由柵格化來執(zhí)行的。所謂柵格化,是指將圖塊轉(zhuǎn)換為位圖。而圖塊是柵格化執(zhí)行的最小單位。渲染進程維護了一個柵格化的線程池,所有的圖塊柵格化都是在線程池內(nèi)執(zhí)行的,運行方式如下圖所示:

          7. 合成和顯示

          一旦所有圖塊都被光柵化,合成線程就會生成一個繪制圖塊的命令——“DrawQuad”,然后將該命令提交給瀏覽器進程。

          瀏覽器進程里面有一個名字叫做 viz 的組件,用來接收合成線程發(fā)過來的 DrawQuad 命令,然后根據(jù)命令執(zhí)行。 DrawQuad 命令,將其頁面內(nèi)容繪制到內(nèi)存中,最后再將內(nèi)存顯示在屏幕上。

          多年開發(fā)老碼農(nóng)福利贈送:網(wǎng)頁制作,網(wǎng)站開發(fā),web前端開發(fā),從最零基礎(chǔ)開始的的HTML+CSS+JavaScript。jQuery,Vue、React、Ajax,node,angular框架等到移動端小程序項目實戰(zhàn)【視頻+工具+電子書+系統(tǒng)路線圖】都有整理,需要的伙伴可以私信我,發(fā)送“前端”等3秒后就可以獲取領(lǐng)取地址,送給每一位對編程感興趣的小伙伴

          8. 總結(jié)

          一個完整的渲染流程可以總結(jié)如下:

          • 1、渲染進程將HTML內(nèi)容轉(zhuǎn)換為瀏覽器能夠讀懂的DOM樹結(jié)構(gòu)。
          • 2、渲染引擎將CSS樣式表轉(zhuǎn)化為瀏覽器可以理解的styleSheets,計算出DOM節(jié)點的樣式。
          • 3、創(chuàng)建布局樹,并計算所需元素的布局信息。
          • 4、對布局樹進行分層,并生成分層樹。
          • 5、為每個圖層生成繪制列表,并將其提交到合成線程。
          • 6、合成線程將圖層分圖塊,并柵格化將圖塊轉(zhuǎn)換成位圖。
          • 7、合成線程發(fā)送繪制圖塊命令給瀏覽器進程。瀏覽器進程根據(jù)指令生成頁面,并顯示到顯示器上。

          渲染過程中還有兩個我們經(jīng)常聽到的概念:重排和重繪。在這篇文章中就不細說了,下一篇文章再詳細介紹。

          覽器渲染頁面有以下幾個步驟:

          1. 當瀏覽器的網(wǎng)絡(luò)進程接收到 HTML文檔后,會開啟一個渲染任務(wù),并將其傳遞給渲染主線程的消息隊列。
          2. 在事件循環(huán)機制的作用下,渲染主線程會取出消息隊列中的渲染任務(wù),開啟渲染流程。
          3. 整個的渲染流程分為多個階段,分別是:HTML 解析、樣式計算、布局、分層、繪制、分塊、光柵化、畫;
          4. 每個階段都有明確的輸入和輸出、上一個階段的輸出會成為下一個階段的輸入;這樣一來,整個渲染流程就形成了一套組織嚴密的生產(chǎn)流水線。

          主站蜘蛛池模板: 激情爆乳一区二区三区| 精品国产一区二区麻豆| 中文字幕无线码一区二区| 中文字幕一区二区三区有限公司| 中文字幕在线观看一区二区| 无码一区二区三区AV免费| 国产探花在线精品一区二区| 日韩免费无码一区二区视频| 亚洲一区二区三区在线| 无码人妻精品一区二区三区99仓本 | 日韩社区一区二区三区| 国产午夜精品片一区二区三区| 午夜福利av无码一区二区 | 好爽毛片一区二区三区四| 天堂va视频一区二区| 国模精品视频一区二区三区| 国产乱码精品一区二区三区香蕉 | 蜜桃传媒一区二区亚洲AV| 日韩人妻无码一区二区三区久久| 久久精品国产一区二区三区 | 日本一区频道在线视频| 日本一区频道在线视频| 无码国产精品一区二区免费式影视| 九九久久99综合一区二区| 日本亚洲国产一区二区三区| 中文无码精品一区二区三区 | 日韩AV无码一区二区三区不卡| 伊人久久大香线蕉AV一区二区 | 美女视频一区二区三区| 色多多免费视频观看区一区| 精品在线一区二区三区| 一区二区三区国产精品 | 人妖在线精品一区二区三区| 无码日韩精品一区二区免费| 国产激情з∠视频一区二区| 久久精品一区二区免费看| 国产成人无码精品一区二区三区| 国产成人精品无码一区二区三区| 国产亚洲情侣一区二区无| 亚洲AV永久无码精品一区二区国产| 亚洲人AV永久一区二区三区久久|