使用2個(gè)空格縮進(jìn)
<ul>
<li>Fantastic</li>
<li>Great</li>
</ul>
.example {
color: blue;
}
只允許使用小寫。
所有的代碼都用小寫字母:適用于元素名,屬性,屬性值(除了文本和CDATA), 選擇器,特性,特性值(除了字符串)。
<!-- 不推薦 -->
<A HREF="/">Home</A>
<!-- 推薦 -->
<img src="google.png"
alt="Google">
建議刪除行尾白空格。
<!-- 不推薦 -->
<p>What? </p>
<!-- 推薦 -->
<p>Yes please.</p>
如果沒(méi)有特殊需求,一般采用utf-8編碼。如果是cms站點(diǎn),則遵守該站點(diǎn)的編碼規(guī)則。
<!-- 網(wǎng)頁(yè)編碼 -->
<meta charset="utf-8">
盡可能的去解釋你寫的代碼。說(shuō)明該代碼包括什么、目的是什么、能做什么、為什么使用它等。
注釋是否需要詳盡,取決于項(xiàng)目的復(fù)雜程度。
一般單行注釋:
<!-- col -->
模塊間注釋:
<!-- news -->
<div class="news">
<h2>News</h2>
<p>...</p>
</div>
<!--/ news -->
循環(huán)注釋:
<ul>
<!-- loop: new list -->
<li>new's title 1</li>
<li>new's title 2</li>
<li>new's title 3</li>
<li>new's title 4</li>
<li>new's title 5</li>
<!-- /loop: new list -->
</ul>
cms輸出注釋:
<!-- cms: news list -->
<ul>
<li>new's title 1</li>
<li>new's title 2</li>
<li>new's title 3</li>
<li>new's title 4</li>
<li>new's title 5</li>
</ul>
<!-- /cms: news list -->
Tab選項(xiàng)卡內(nèi)容注釋:
<!-- tab: news list -->
<div class="tab"></div>
<!-- /tab: news list -->
使用html5文檔聲明,不再使用XHTML(application/xhtml+xml)。
HTML5是目前所有HTML文檔類型中的首選:
<!DOCTYPE html>
編寫有效、正確的HTML代碼,否則很難達(dá)到性能上的提升。
可以使用一些工具驗(yàn)證你的代碼,如 W3C HTML validator
根據(jù)HTML各個(gè)元素的用途而去使用它們。
<!-- 不推薦 -->
<div class="col">
<div class="title">
news</div>
<p>list1</p>
<p>list2</p>
<p>list3</p>
</div>
<!-- 推薦 -->
<div class="col">
<h2 class="title">
news</h2>
<p>list1</p>
<p>list2</p>
<p>list3</p>
</div>
部分標(biāo)簽說(shuō)明:
不推薦使用的標(biāo)簽:
給多媒體元素,比如canvas、videos、 images增加alt屬性,提高可用性(特別是常用的img標(biāo)簽,盡可量得加上alt屬性,提供圖片的描述信息)。
<!-- 不推薦 -->
<img src="world.jpg">
<!-- 推薦 -->
<img src="world.jpg"
alt="our world images">
在樣式表和腳本的標(biāo)簽中忽略type屬性。
HTML5默認(rèn)type為text/css和text/javascript類型,所以沒(méi)必要指定。即便是老瀏覽器也是支持的。
<!-- 不推薦 -->
<link rel="stylesheet"
href="//www.google.com/css/maia.css"
type="text/css">
<script src="
//www.google.com/
js/gweb/analytics/autotrack.js"
type="text/javascript">
</script>
<!-- 推薦 -->
<link rel="stylesheet"
href="//www.google.com/css/maia.css">
<script src="
//www.google.com/
js/gweb/analytics/autotrack.js">
</script>
每個(gè)塊元素、列表元素或表格元素都獨(dú)占一行,每個(gè)子元素都相對(duì)于父元素進(jìn)行縮進(jìn)。按設(shè)計(jì)稿劃分模塊,盡量使頁(yè)面模塊化,模塊與模塊之前要有清晰的注釋。
如上面頁(yè)面框架,推薦寫法:
<!-- hader -->
<div class="header">header</div>
<!-- /hader -->
<!-- nav -->
<div class="nav">nav</div>
<!-- /nav -->
<!-- main -->
<div class="main">
<!-- container -->
<div class="container">
<!--news-->
<div class="news">
<h2>news<h2>
<p>...</p>
</div>
<!--news-->
</div>
<!--/container-->
<!--sidebar-->
<div class="sidebar">
sidebar</div>
<!--sidebar-->
</div>
<!--/main-->
<!--footer-->
<div class="footer">
footer</div>
<!--/footer-->
保證整個(gè)頁(yè)面在未加載樣式表時(shí)仍有較好的層次清晰的頁(yè)面結(jié)構(gòu)。
<!-- 不推薦 -->
<div class="logo">My Site</div>
<div class="nav">
<a href="#">Home</a>
<a href="#">News</a>
<a href="#">Mobile</a>
</div>
<div class="news">
<div>News</div>
<a href="#">
news list 1</a>
<a href="#">
news list 2</a>
<a href="#">
news list 3</a>
</div>
<!-- 推薦 -->
<h1 class="logo">My Site</h1>
<ul class="nav">
<li><a href="#">
Home</a></li>
<li><a href="#">
News</a></li>
<li><a href="#">
Mobile</a></li>
</ul>
<div class="news">
<h2>News</h2>
<ul>
<li><a href="#">
news list 1</a>
</li>
<li><a href="#">
news list 2</a>
</li>
<li><a href="#">
news list 3</a>
</li>
</ul>
</div>
H標(biāo)簽使用
strong、b使用
將需要加粗的文字使用b標(biāo)簽來(lái)顯示。
將需要強(qiáng)調(diào)的文字(主要指包含關(guān)鍵詞的信息)使用strong標(biāo)簽來(lái)強(qiáng)調(diào)主要內(nèi)容。
注:b是粗體標(biāo)簽,屬于實(shí)體標(biāo)簽,它所包圍的字符將被設(shè)為bold(粗體);strong 是加重語(yǔ)氣標(biāo)簽,屬于邏輯標(biāo)簽,它的作用是加強(qiáng)字符語(yǔ)氣。
在很多情況下,a都要使用title來(lái)說(shuō)明該鏈接的相關(guān)說(shuō)明或目的意義。
例如:當(dāng)使用overflow隱藏掉a中的溢出文字時(shí),該a中的title是必不可少的,它可以告訴用戶被隱藏掉的文字內(nèi)容是什么;又或者當(dāng)一個(gè)圖片型鏈接出現(xiàn)時(shí),該a中的title同樣是必不可少的,它可以告訴用戶這個(gè)圖片鏈接是做什么用的。
注:僅在img里添加alt標(biāo)簽在火狐提示文字是出不來(lái)的,alt是圖片加載失敗或未加載完全時(shí)顯示出來(lái)的提示文字,要想鼠標(biāo)移上去顯示提示信息應(yīng)該用title,嚴(yán)謹(jǐn)?shù)膶懛ㄊ莍mg里加入alt和title這兩個(gè)標(biāo)簽。
代碼保持精簡(jiǎn),最優(yōu)化,這樣搜索引擎才更喜歡。
計(jì)算機(jī)網(wǎng)絡(luò)中的OSI(Open Systems Interconnection)七層模型是一種理論框架,用于描述計(jì)算機(jī)網(wǎng)絡(luò)中數(shù)據(jù)通信的過(guò)程。OSI模型將計(jì)算機(jī)網(wǎng)絡(luò)通信過(guò)程劃分為七個(gè)層次,每個(gè)層次都有其特定的功能和協(xié)議。這種分層結(jié)構(gòu)有助于研究和理解計(jì)算機(jī)網(wǎng)絡(luò)中的通信原理。以下是OSI七層模型的各個(gè)層次及其主要功能:
應(yīng)用層是OSI模型的第七層,也是網(wǎng)絡(luò)應(yīng)用程序和網(wǎng)絡(luò)協(xié)議之間的接口。應(yīng)用層主要負(fù)責(zé)為用戶提供各類應(yīng)用服務(wù),如文件傳輸、電子郵件、Web瀏覽等。
表示層是OSI模型的第六層,主要負(fù)責(zé)處理在網(wǎng)絡(luò)中傳輸?shù)臄?shù)據(jù)的表示方式,如數(shù)據(jù)加密、解密、壓縮、解壓縮等。表示層確保了不同系統(tǒng)之間的數(shù)據(jù)兼容性。
會(huì)話層是OSI模型的第五層,主要負(fù)責(zé)建立、維護(hù)和終止應(yīng)用程序之間的通信會(huì)話。會(huì)話層提供了數(shù)據(jù)交換的同步和確認(rèn)機(jī)制。
傳輸層是OSI模型的第四層,主要負(fù)責(zé)在源主機(jī)和目標(biāo)主機(jī)之間提供可靠的、端到端的數(shù)據(jù)傳輸服務(wù)。傳輸層通過(guò)分段、封裝和重組數(shù)據(jù)來(lái)實(shí)現(xiàn)可靠的數(shù)據(jù)傳輸。常見(jiàn)的傳輸層協(xié)議包括TCP(傳輸控制協(xié)議)和UDP(用戶數(shù)據(jù)報(bào)協(xié)議)。
網(wǎng)絡(luò)層是OSI模型的第三層,主要負(fù)責(zé)將數(shù)據(jù)包從源主機(jī)路由到目標(biāo)主機(jī)。網(wǎng)絡(luò)層主要負(fù)責(zé)邏輯尋址、路由選擇和分組轉(zhuǎn)發(fā)。常見(jiàn)的網(wǎng)絡(luò)層協(xié)議包括IP(互聯(lián)網(wǎng)協(xié)議)和ICMP(互聯(lián)網(wǎng)控制報(bào)文協(xié)議)。
數(shù)據(jù)鏈路層是OSI模型的第二層,主要負(fù)責(zé)將網(wǎng)絡(luò)層傳來(lái)的數(shù)據(jù)包封裝成幀(Frame),并在同一局域網(wǎng)內(nèi)進(jìn)行傳輸。數(shù)據(jù)鏈路層主要負(fù)責(zé)物理尋址、數(shù)據(jù)成幀、錯(cuò)誤檢測(cè)和流量控制。常見(jiàn)的數(shù)據(jù)鏈路層協(xié)議包括以太網(wǎng)(Ethernet)、令牌環(huán)(Token Ring)和無(wú)線局域網(wǎng)(Wi-Fi)等。
物理層是OSI模型的第一層,主要負(fù)責(zé)在物理介質(zhì)上實(shí)現(xiàn)比特流的透明傳輸。物理層主要關(guān)注硬件接口、電氣特性、光纖、無(wú)線傳輸?shù)确矫娴膯?wèn)題。
OSI七層模型提供了一個(gè)通用的框架,幫助研究和理解計(jì)算機(jī)網(wǎng)絡(luò)中的通信原理。實(shí)際應(yīng)用中,我們通常使用TCP/IP四層模型,它包括了應(yīng)用層、傳輸層、網(wǎng)絡(luò)層和鏈路層,與OSI模型有一定的對(duì)應(yīng)關(guān)系。
HTML框架進(jìn)行分層設(shè)計(jì)的主要原因是為了提高代碼的可讀性、可維護(hù)性和可重用性。將HTML框架分層可以提高整體項(xiàng)目的結(jié)構(gòu)和邏輯,便于開(kāi)發(fā)者更好地理解和修改代碼。分層設(shè)計(jì)具有以下優(yōu)點(diǎn):
HTML框架包括Application層``middleware層``route層``codec層``transport層 Application層 應(yīng)用層通常包括與業(yè)務(wù)邏輯相關(guān)的代碼,如Web應(yīng)用程序的控制器(Controller)、視圖(View)和模型(Model)。應(yīng)用層的主要作用是處理用戶請(qǐng)求并返回相應(yīng)的響應(yīng)。
Middleware層 中間件層是介于應(yīng)用層和底層框架之間的一層,負(fù)責(zé)處理一些通用的功能,如身份驗(yàn)證、授權(quán)、緩存、日志記錄等。中間件層有助于將業(yè)務(wù)邏輯與通用功能分離,使得應(yīng)用層更加簡(jiǎn)潔和易于維護(hù)。
Route層 路由層負(fù)責(zé)處理HTTP請(qǐng)求的URL和HTTP方法(如GET、POST等),將請(qǐng)求分發(fā)到相應(yīng)的控制器和方法。路由層的主要作用是根據(jù)URL映射來(lái)定位具體的功能代碼。
Codec層 編解碼層負(fù)責(zé)處理數(shù)據(jù)的編碼和解碼。在Web開(kāi)發(fā)中,編碼和解碼通常涉及到HTML、CSS、JavaScript等前端技術(shù)的處理,以及JSON、XML等數(shù)據(jù)交換格式的處理。編解碼層的主要作用是將數(shù)據(jù)轉(zhuǎn)換為特定的格式,以便在不同層之間進(jìn)行傳輸和處理。
Transport層 傳輸層負(fù)責(zé)處理底層的網(wǎng)絡(luò)通信,如TCP、UDP等協(xié)議的使用。在Web開(kāi)發(fā)中,傳輸層通常涉及到HTTP協(xié)議的處理,包括請(qǐng)求和響應(yīng)的創(chuàng)建、發(fā)送和接收。傳輸層的主要作用是確保數(shù)據(jù)的可靠傳輸和在網(wǎng)絡(luò)中的正確路由。
這些層次在實(shí)際應(yīng)用中可能因框架和場(chǎng)景的不同而有所差異。但是,從您提供的描述來(lái)看,它們分別負(fù)責(zé)處理Web應(yīng)用程序中的不同功能,共同構(gòu)成了一個(gè)完整的Web開(kāi)發(fā)框架。
應(yīng)用層設(shè)計(jì)主要是設(shè)置各種接口,用于路由使用。
例如在字節(jié)后端進(jìn)階版中的大項(xiàng)目中的注冊(cè)接口。
新用戶注冊(cè)時(shí)提供用戶名,密碼,昵稱即可,用戶名需要保證唯一。創(chuàng)建成功后返回用戶 id 和權(quán)限token.
接口類型
POST
接口定義
go復(fù)制代碼syntax = "proto2";
package douyin.core;
message douyin_user_register_request {
required string username = 1; // 注冊(cè)用戶名,最長(zhǎng)32個(gè)字符
required string password = 2; // 密碼,最長(zhǎng)32個(gè)字符
}
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復(fù)制代碼func Register(username, password string) (id int64, token int64, err error) {
if len(username) > 32 {
return 0, 0, errors.New("用戶名過(guò)長(zhǎng),不可超過(guò)32位")
}
if len(password) > 32 {
return 0, 0, errors.New("密碼過(guò)長(zhǎng),不可超過(guò)32位")
}
// 先查布隆過(guò)濾器,不存在直接返回錯(cuò)誤,降低數(shù)據(jù)庫(kù)的壓力
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錯(cuò)誤!")
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ù)庫(kù)
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
// 加密存儲(chǔ)用戶密碼
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中 一個(gè)以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
}
// 布隆過(guò)濾器中加入新用戶
userIdFilter.AddString(strconv.FormatInt(user.Id, 10))
userNameFilter.AddString(username)
return user.Id, token1, nil
}
本接口注冊(cè)功能實(shí)現(xiàn):把所有信息存在mysql里邊當(dāng)然redis里邊也存在這些信息,當(dāng)然username也存在了布容過(guò)濾器中去,當(dāng)接收到用戶的username的時(shí)候我們現(xiàn)在布容過(guò)濾器中先查詢是否存在如果存在則直接返回err,不存在然后再在redis里邊查詢,因?yàn)閞edis相比于mysql是更為輕量級(jí)的所以我們要先在redis里邊進(jìn)行查,如果查不到再進(jìn)mysql里邊查去,查不到說(shuō)明沒(méi)有注冊(cè)過(guò),可以注冊(cè)。
遵循命名規(guī)范原則。
gin框架里的中間件分為全局中間件,局部中間件。那么什么是中間件?中間件是為應(yīng)用提供通用服務(wù)和功能的軟件。數(shù)據(jù)管理、應(yīng)用服務(wù)、消息傳遞、身份驗(yàn)證和 API 管理通常都要通過(guò)中間件。在gin框架里,就是我們的所有API接口都要經(jīng)過(guò)我們的中間件,我們可以在中間件做一些攔截處理。
這個(gè)是在服務(wù)啟動(dòng)就開(kāi)始注冊(cè),全局意味著所有API接口都會(huì)經(jīng)過(guò)這里。Gin的中間件是通過(guò)Use方法設(shè)置的,它接收一個(gè)可變參數(shù),所以我們同時(shí)可以設(shè)置多個(gè)中間件。
首先定義如下
go復(fù)制代碼// 1.創(chuàng)建路由
r := gin.Default() //默認(rèn)帶Logger(), Recovery()這兩個(gè)內(nèi)置中間件
r:= gin.New() //不帶任何中間件
// 注冊(cè)中間件
r.Use(MiddleWare())
r.Use(MiddleWare2())
注意的是
gin.Default()默認(rèn)使用了Logger和Recovery中間件,其中:Logger中間件將日志寫入gin.DefaultWriter,即使配置了GIN_MODE=release。Recovery中間件會(huì)recover任何panic。如果有panic的話,會(huì)寫入500響應(yīng)碼。如果不想使用上面兩個(gè)默認(rèn)的中間件,可以使用gin.New()新建一個(gè)沒(méi)有任何默認(rèn)中間件的路由。
go復(fù)制代碼// 定義中間
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
fmt.Println("中間件開(kāi)始執(zhí)行了")
// 設(shè)置變量到Context的key中,可以通過(guò)Get()取
c.Set("request", "這是中間件設(shè)置的值")
status := c.Writer.Status()
fmt.Println("中間件執(zhí)行完畢", status)
t2 := time.Since(t)
fmt.Println("time:", t2)
}
}
然后啟動(dòng)我們的服務(wù),訪問(wèn)任意一個(gè)接口可以看到輸出如下
這是請(qǐng)求先到了中間件,然后在到我們的API接口。在中間件里可以設(shè)置變量到Context的key中,然后在我們的API接口取值。
go復(fù)制代碼 r.GET("/", func(c *gin.Context) {
// 取值
req, _ := c.Get("request")
fmt.Println("request:", req)
// 頁(yè)面接收
c.JSON(200, gin.H{"request": req})
})
這時(shí)候在訪問(wèn)就可以看到中間件設(shè)置的值是
next方法是在中間件里面使用,這個(gè)是執(zhí)行后續(xù)中間件請(qǐng)求處理的意思(含沒(méi)有執(zhí)行的中間件和我們定義的GET方法處理,如果連續(xù)注冊(cè)幾個(gè)中間件則會(huì)是按照順序先進(jìn)后出的執(zhí)行,遇到next就去執(zhí)行下一個(gè)中間件里的next前面方法。
go復(fù)制代碼 // 執(zhí)行函數(shù)
c.Next()
// 中間件執(zhí)行完后續(xù)的一些事情
局部中間件意味著部分接口才會(huì)生效,只在局部使用,這時(shí)候訪問(wèn)http:127.0.0.1:8000/ 才會(huì)看到中間件的日志打印,其他API接口則不會(huì)出現(xiàn)。
go復(fù)制代碼 //局部中間件使用
r.GET("/", MiddleWare(), func(c *gin.Context) {
// 取值
req, _ := c.Get("request")
fmt.Println("request:", req)
// 頁(yè)面接收
c.JSON(200, gin.H{"request": req})
})
go復(fù)制代碼
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
通過(guò)自定義中間件,我們可以很方便的攔截請(qǐng)求,來(lái)做一些我們需要做的事情,比如日志記錄、授權(quán)校驗(yàn)、各種過(guò)濾等等。
Gin 是一個(gè)標(biāo)準(zhǔn)的 Web 服務(wù)框架,遵循 Restful API 接口規(guī)范,其路由庫(kù)是基于 httproute 實(shí)現(xiàn)的。
本節(jié)將從 Gin 路由開(kāi)始,詳細(xì)講述各種路由場(chǎng)景下,如何通過(guò) Gin 來(lái)實(shí)現(xiàn)。
示例
字節(jié)大項(xiàng)目注冊(cè)接口
go復(fù)制代碼syntax = "proto2";
package douyin.core;
message douyin_user_register_request {
required string username = 1; // 注冊(cè)用戶名,最長(zhǎng)32個(gè)字符
required string password = 2; // 密碼,最長(zhǎng)32個(gè)字符
}
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復(fù)制代碼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復(fù)制代碼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)
}
在Web開(kāi)發(fā)中,編碼和解碼通常涉及到HTML、CSS、JavaScript等前端技術(shù)的處理,以及JSON、XML等數(shù)據(jù)交換格式的處理。編解碼層的主要作用是將數(shù)據(jù)轉(zhuǎn)換為特定的格式,以便在不同層之間進(jìn)行傳輸和處理。
示例
在postman中的示例 json
xml
html復(fù)制代碼{"status_code":1,"status_msg":"redis: nil"}
{"status_code":2,"status_msg":"no multipart boundary param in Content-Type"}
Text復(fù)制代碼{"status_code":1,"status_msg":"redis: nil"}
{"status_code":2,"status_msg":"no multipart boundary param in Content-Type"}
Auto復(fù)制代碼{
"status_code": 1,
"status_msg": "redis: nil"
}{
"status_code": 2,
"status_msg": "no multipart boundary param in Content-Type"
}
傳輸層負(fù)責(zé)處理底層的網(wǎng)絡(luò)通信,如TCP、UDP等協(xié)議的使用。在Web開(kāi)發(fā)中,傳輸層通常涉及到HTTP協(xié)議的處理,包括請(qǐng)求和響應(yīng)的創(chuàng)建、發(fā)送和接收。傳輸層的主要作用是確保數(shù)據(jù)的可靠傳輸和在網(wǎng)絡(luò)中的正確路由。
golang語(yǔ)言中net/http這個(gè)庫(kù)中的conn 他是BIO自帶阻塞
同步阻塞I/O模式,數(shù)據(jù)的讀取寫入必須阻塞在一個(gè)線程內(nèi)等待其完成。
BIO通信(一請(qǐng)求一應(yīng)答)模型圖如下(圖源網(wǎng)絡(luò),原出處不明):
采用 BIO 通信模型 的服務(wù)端,通常由一個(gè)獨(dú)立的 Acceptor 線程負(fù)責(zé)監(jiān)聽(tīng)客戶端的連接。我們一般通過(guò)在 while(true) 循環(huán)中服務(wù)端會(huì)調(diào)用 accept() 方法等待接收客戶端的連接的方式監(jiān)聽(tīng)請(qǐng)求,請(qǐng)求一旦接收到一個(gè)連接請(qǐng)求,就可以建立通信套接字在這個(gè)通信套接字上進(jìn)行讀寫操作,此時(shí)不能再接收其他客戶端連接請(qǐng)求,只能等待同當(dāng)前連接的客戶端的操作執(zhí)行完成, 不過(guò)可以通過(guò)多線程來(lái)支持多個(gè)客戶端的連接,如上圖所示。
如果要讓 BIO 通信模型 能夠同時(shí)處理多個(gè)客戶端請(qǐng)求,就必須使用多線程(主要原因是 socket.accept()、 socket.read()、 socket.write() 涉及的三個(gè)主要函數(shù)都是同步阻塞的),也就是說(shuō)它在接收到客戶端連接請(qǐng)求之后為每個(gè)客戶端創(chuàng)建一個(gè)新的線程進(jìn)行鏈路處理,處理完成之后,通過(guò)輸出流返回應(yīng)答給客戶端,線程銷毀。這就是典型的 一請(qǐng)求一應(yīng)答通信模型 。我們可以設(shè)想一下如果這個(gè)連接不做任何事情的話就會(huì)造成不必要的線程開(kāi)銷,不過(guò)可以通過(guò) 線程池機(jī)制 改善,線程池還可以讓線程的創(chuàng)建和回收成本相對(duì)較低。使用FixedThreadPool 可以有效的控制了線程的最大數(shù)量,保證了系統(tǒng)有限的資源的控制,實(shí)現(xiàn)了N(客戶端請(qǐng)求數(shù)量):M(處理客戶端請(qǐng)求的線程數(shù)量)的偽異步I/O模型(N 可以遠(yuǎn)遠(yuǎn)大于 M),下面一節(jié)"偽異步 BIO"中會(huì)詳細(xì)介紹到。
我們?cè)僭O(shè)想一下當(dāng)客戶端并發(fā)訪問(wèn)量增加后這種模型會(huì)出現(xiàn)什么問(wèn)題?
程是寶貴的資源,線程的創(chuàng)建和銷毀成本很高,除此之外,線程的切換成本也是很高的。尤其在 Linux 這樣的操作系統(tǒng)中,線程本質(zhì)上就是一個(gè)進(jìn)程,創(chuàng)建和銷毀線程都是重量級(jí)的系統(tǒng)函數(shù)。如果并發(fā)訪問(wèn)量增加會(huì)導(dǎo)致線程數(shù)急劇膨脹可能會(huì)導(dǎo)致線程堆棧溢出、創(chuàng)建新線程失敗等問(wèn)題,最終導(dǎo)致進(jìn)程宕機(jī)或者僵死,不能對(duì)外提供服務(wù)。 golang實(shí)現(xiàn)BIO
NIO: NIO是一種同步非阻塞IO, 基于Reactor模型來(lái)實(shí)現(xiàn)的。其實(shí)相當(dāng)于就是一個(gè)線程處理大量的客戶端的請(qǐng)求,通過(guò)一個(gè)線程輪詢大量的channel,每次就獲取一批有事件的channel,然后對(duì)每個(gè)請(qǐng)求啟動(dòng)一個(gè)線程處理即可。這里的核心就是非阻塞,就那個(gè)selector一個(gè)線程就可以不停輪詢channel,所有客戶端請(qǐng)求都不會(huì)阻塞,直接就會(huì)進(jìn)來(lái),大不了就是等待一下排著隊(duì)而已。這里面優(yōu)化BIO的核心就是,一個(gè)客戶端并不是時(shí)時(shí)刻刻都有數(shù)據(jù)進(jìn)行交互,沒(méi)有必要死耗著一個(gè)線程不放,所以客戶端選擇了讓線程歇一歇,只有客戶端有相應(yīng)的操作的時(shí)候才發(fā)起通知,創(chuàng)建一個(gè)線程來(lái)處理請(qǐng)求。
————————————————
NIO:模型圖
Reactor模型:
學(xué)習(xí)NIO先來(lái)搞清楚一些相關(guān)的概念,NIO通訊有哪些相關(guān)組件,對(duì)應(yīng)的作用都是什么,之間有哪些聯(lián)系?
首先我們來(lái)了解下傳統(tǒng)的Socket網(wǎng)絡(luò)通訊模型。
傳統(tǒng)Socket通訊原理圖
每次一個(gè)客戶端接入,都是要在服務(wù)端創(chuàng)建一個(gè)線程來(lái)服務(wù)這個(gè)客戶端的,這會(huì)導(dǎo)致大量的客戶端的時(shí)候,服務(wù)端的線程數(shù)量可能達(dá)到幾千甚至幾萬(wàn),幾十萬(wàn),這會(huì)導(dǎo)致服務(wù)器端程序負(fù)載過(guò)高,不堪重負(fù),最終系統(tǒng)崩潰死掉。
NIO原理圖
NIO的線程模型 對(duì)Socket發(fā)起的連接不需要每個(gè)都創(chuàng)建一個(gè)線程,完全可以使用一個(gè)Selector來(lái)多路復(fù)用監(jiān)聽(tīng)N多個(gè)Channel是否有請(qǐng)求,該請(qǐng)求是對(duì)應(yīng)的連接請(qǐng)求,還是發(fā)送數(shù)據(jù)的請(qǐng)求,這里面是基于操作系統(tǒng)底層的Select通知機(jī)制的,一個(gè)Selector不斷的輪詢多個(gè)Channel,這樣避免了創(chuàng)建多個(gè)線程,只有當(dāng)莫個(gè)Channel有對(duì)應(yīng)的請(qǐng)求的時(shí)候才會(huì)創(chuàng)建線程,可能說(shuō)1000個(gè)請(qǐng)求, 只有100個(gè)請(qǐng)求是有數(shù)據(jù)交互的, 這個(gè)時(shí)候可能server端就提供10個(gè)線程就能夠處理這些請(qǐng)求。這樣的話就可以避免了創(chuàng)建大量的線程。
NIO中的Buffer是個(gè)什么東西 ?
學(xué)習(xí)NIO,首當(dāng)其沖就是要了解所謂的Buffer緩沖區(qū),這個(gè)東西是NIO里比較核心的一個(gè)部分,一般來(lái)說(shuō),如果你要通過(guò)NIO寫數(shù)據(jù)到文件或者網(wǎng)絡(luò),或者是從文件和網(wǎng)絡(luò)讀取數(shù)據(jù)出來(lái)此時(shí)就需要通過(guò)Buffer緩沖區(qū)來(lái)進(jìn)行。Buffer的使用一般有如下幾個(gè)步驟:
寫入數(shù)據(jù)到Buffer,調(diào)用flip()方法,從Buffer中讀取數(shù)據(jù),調(diào)用clear()方法或者compact()方法。
capacity: 緩沖區(qū)容量的大小,就是里面包含的數(shù)據(jù)大小。
limit: 對(duì)buffer緩沖區(qū)使用的一個(gè)限制,從這個(gè)index開(kāi)始就不能讀取數(shù)據(jù)了。
position: 代表著數(shù)組中可以開(kāi)始讀寫的index, 不能大于limit。
mark: 是類似路標(biāo)的東西,在某個(gè)position的時(shí)候,設(shè)置一下mark,此時(shí)就可以設(shè)置一個(gè)標(biāo)記,后續(xù)調(diào)用reset()方法可以把position復(fù)位到當(dāng)時(shí)設(shè)置的那個(gè)mark上去,把position或limit調(diào)整為小于mark的值時(shí),就丟棄這個(gè)mark。如果使用的是Direct模式創(chuàng)建的Buffer的話,就會(huì)減少中間緩沖直接使用的是DirectorBuffer來(lái)進(jìn)行數(shù)據(jù)的存儲(chǔ)。
————————————————
NIO中,Channel是什么?
Channel是NIO中的數(shù)據(jù)通道,類似流,但是又有些不同,Channel即可從中讀取數(shù)據(jù),又可以從寫數(shù)據(jù)到通道中,但是流的讀寫通常是單向的。Channel可以異步的讀寫。Channel中的數(shù)據(jù)總是要先讀到一個(gè)Buffer中,或者從緩沖區(qū)中將數(shù)據(jù)寫到通道中。
FileChannel的作用是什么 Buffer有不同的類型,同樣Channel也有好幾個(gè)類型。 FileChannel,DatagramChannel,SocketChannel,ServerSocketChannel。這些通道涵蓋了UDP 和 TCP 網(wǎng)絡(luò)IO,以及文件IO。而FileChannel就是文件IO對(duì)應(yīng)的管道, 在讀取文件的時(shí)候會(huì)用到這個(gè)管道。 golang的NIO
TML標(biāo)簽
基本標(biāo)簽
HTML頁(yè)面中內(nèi)容是由HTML標(biāo)簽組織起來(lái)的,如頁(yè)面中的文本、圖像、Flash視頻文件等都是通過(guò)HTML標(biāo)簽合理地顯示在頁(yè)面的各個(gè)位置。
1 標(biāo)題標(biāo)簽<h1>~<h6>
標(biāo)題標(biāo)簽表示一段文字的標(biāo)題(主題),并且支持多層次的內(nèi)容結(jié)構(gòu)。HTNL.共提供了6級(jí)標(biāo)題,分別為<h1>~<h6>,并賦予了標(biāo)題一定的外觀,所有標(biāo)題字體加粗,其中山<h1>字號(hào)最大,<h6>字號(hào)最小.
2.圖像標(biāo)簽<img>
在網(wǎng)頁(yè)中常用的圖像格式有4種,即JPG、GIF、BMP.PNG,其中使用比較多的是JPG、GIF和PNG,大多數(shù)瀏覽器都可以顯示這些圖像。
顯示圖像的語(yǔ)法:
< img src="ur1" alt="文本" width="x" height="y"/>
在語(yǔ)法中:
a、SrC屬性:表示顯示圖像的地址。
b、alt屬性:指定圖像的替代文本,當(dāng)圖像無(wú)法顯示時(shí)(如圖片路徑錯(cuò)誤或網(wǎng)速太慢等)替代顯示的文本,這樣,即使圖像無(wú)法顯示,用戶還可以看到網(wǎng)頁(yè)丟失的信息,所以為頁(yè)面上的圖像都加上替換文本屬性是個(gè)好習(xí)慣,這樣有助于更好地顯示信息,并且對(duì)于那些使用純文本瀏覽器的人來(lái)說(shuō)是非常有幫助的。
c、width屬性:表示圖像寬度.
d、height屬性:表示圖像高度。
3.段落標(biāo)簽<p>
顧名思義,段落標(biāo)簽表示將一段文字組成一系列段落內(nèi)容,這樣做的目的是內(nèi)容應(yīng)用某些格式和布局,使各個(gè)段落的邏輯更清晰明了。在HTML文檔中,段落通過(guò)<p>標(biāo)簽定義。段落標(biāo)簽<p>表示段落的開(kāi)始,</p >表示段落的結(jié)束。
4.換行標(biāo)簽<br/>
在希望不產(chǎn)生一個(gè)新段落的情況下進(jìn)行換行,則使用<br>標(biāo)簽。<br>是一個(gè)空的HTML標(biāo)簽,由于關(guān)閉標(biāo)簽沒(méi)有任何意義,因此它沒(méi)有結(jié)束標(biāo)簽。
注意:
使用<b>和<br>的結(jié)果一樣,在XHTML以及未來(lái)的HTML版本中,根據(jù)W3C規(guī)范,不允許使用沒(méi)有結(jié)束標(biāo)簽的HTML元素,因此使用<br>頁(yè)面更規(guī)范,有更長(zhǎng)遠(yuǎn)的保障。
5.水平線標(biāo)簽<hr/>
水平線標(biāo)簽表示一條水平線,注意該標(biāo)簽與<br>標(biāo)簽一樣,沒(méi)有結(jié)束標(biāo)簽,直接使用<hr/>表示標(biāo)簽的開(kāi)始和結(jié)束。
使用以上講解的基本標(biāo)簽,就可以進(jìn)行網(wǎng)頁(yè)內(nèi)容排版了。
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。