golang微服务网关一:网络基础知识扫盲(温故而知新)
golang微服務(wù)網(wǎng)關(guān)之網(wǎng)絡(luò)基礎(chǔ)知識
面試中被面試官經(jīng)常問到的一些關(guān)于tcp網(wǎng)絡(luò)知識問題 今天依依給大家分析下(毫無保留分享:)
三次握手
四次揮手
為啥time_wait需要等待2MSL?
為啥會出現(xiàn)大量的close_wait?
什么時候會出現(xiàn)FIN-WAIT?
TCP為啥需要流量控制?
如何調(diào)整網(wǎng)絡(luò)負(fù)載?
tcp為啥需要擁塞控制?
慢開始和擁塞避免?
快速重傳和快速恢復(fù)?
為什么出現(xiàn)粘包/拆包?
為啥time_wait需要等待2MSL?
1,MSL:Maximum Segment Lifetime,30秒-1分鐘
2,保證TCP協(xié)議的全雙工連接能夠可靠關(guān)閉
3,保證這次連接的重復(fù)數(shù)據(jù)段從網(wǎng)絡(luò)中消失
為啥會出現(xiàn)大量的close_wait?
1,首先close_wait一般書現(xiàn)在被動方關(guān)閉
2,并發(fā)請求太多導(dǎo)致
3,被動關(guān)閉方未及時釋放端口資源導(dǎo)致
CLOSE_WAIT產(chǎn)生原因
close_wait是被動關(guān)閉連接是形成的,根據(jù)TCP狀態(tài)機(jī),服務(wù)器端收到客戶端發(fā)送的FIN,TCP協(xié)議棧會自動發(fā)送ACK,鏈接進(jìn)入close_wait狀態(tài)。但如果服務(wù)器端不執(zhí)行socket的close()操作,狀態(tài)就不能由close_wait遷移到last_ack,則系統(tǒng)中會存在很多close_wait狀態(tài)的連接;
說白的就是并發(fā)可能有點大,io不能及時切換過去,I/O線程被意外阻塞,I/O操作處理不及時,鏈路不能被及時釋放
TCP為啥需要流量控制?
如何調(diào)整網(wǎng)絡(luò)負(fù)載,tcp為啥需要擁塞控制?
慢開始和擁塞避免
快速重傳和快速恢復(fù)
所謂慢開始,tcp剛開始一點點傳遞試探一下網(wǎng)絡(luò)的承受能力,以免擾亂網(wǎng)絡(luò)通道的秩序
上圖中,圖標(biāo)3處遇到網(wǎng)絡(luò)擁塞,就把擁塞的窗口直接降為1了,然后重新開始慢開始,一點點遞增
為了優(yōu)化慢開始所以對算法進(jìn)行了優(yōu)化:快重傳和快恢復(fù)
快速重傳;當(dāng)收到3個重復(fù)ACK 執(zhí)行快重傳:
會把當(dāng)前擁塞窗口降為原來的一般。然后把擁塞避免的預(yù)值降為原來的一半,進(jìn)入一個快速恢復(fù)的階段
快速恢復(fù):因為受到3次重復(fù)ack,丟包,只要是在這個階段丟的包,會重復(fù)發(fā)送一遍,直到把所有丟失的包重新發(fā)送完畢后就會退出快速恢復(fù)階段,
然后進(jìn)入擁塞避免階段
為什么出現(xiàn)粘包/拆包?
上圖:
發(fā)送方由應(yīng)用程序發(fā)送應(yīng)用的報文,根據(jù)應(yīng)用數(shù)據(jù)報文大小的不同,它會占用2個或者1個,應(yīng)用的數(shù)據(jù)實際會發(fā)送到tcp的緩沖區(qū)里面(發(fā)送緩沖區(qū))。真正發(fā)送是由linux內(nèi)核走tcp連接發(fā)送;
tcp根據(jù)緩沖區(qū)大小來決定是否要粘包,粘包:多次請求合并到一個tcp報文中,拆包:一次請求拆到多個tcp報文里面,至于數(shù)據(jù)如何被包裝都是由tcp底層去完成的。
因為我運(yùn)用的其實是應(yīng)用層,不需要關(guān)心它的細(xì)節(jié),數(shù)據(jù)會流入接收方的接收緩沖區(qū),接收方通過socket的reverve方法去獲取到數(shù)據(jù)。
我們是在應(yīng)用層通過socket 直接從bufer緩沖區(qū)拿取數(shù)據(jù)
如何獲取完整應(yīng)用的數(shù)據(jù)報文?
如何獲取完整的數(shù)據(jù)報文?
實例代碼:
package unpack
import (
"encoding/binary"
"errors"
"io"
)
const Msg_Header = "12345678"
func Encode(bytesBuffer io.Writer, content string) error {
//msg_header+content_len+content
//8+4+content_len
if err := binary.Write(bytesBuffer, binary.BigEndian, []byte(Msg_Header)); err != nil {
return err
}
clen := int32(len([]byte(content)))
if err := binary.Write(bytesBuffer, binary.BigEndian, clen); err != nil {
return err
}
if err := binary.Write(bytesBuffer, binary.BigEndian, []byte(content)); err != nil {
return err
}
return nil
}
func Decode(bytesBuffer io.Reader) (bodyBuf []byte, err error) {
MagicBuf := make([]byte, len(Msg_Header))
if _, err = io.ReadFull(bytesBuffer, MagicBuf); err != nil {
return nil, err
}
if string(MagicBuf) != Msg_Header {
return nil, errors.New("msg_header error")
}
lengthBuf := make([]byte, 4)
if _, err = io.ReadFull(bytesBuffer, lengthBuf); err != nil {
return nil, err
}
length := binary.BigEndian.Uint32(lengthBuf)
bodyBuf = make([]byte, length)
if _, err = io.ReadFull(bytesBuffer, bodyBuf); err != nil {
return nil, err
}
return bodyBuf, err
}
D:gocode1.14.3gocodegateway_demodemoaseunpackunpackcodec.go
package main
import (
"fmt"
"github.com/e421083458/gateway_demo/demo/base/unpack/unpack"
"net"
)
func main() {
conn, err := net.Dial("tcp", "localhost:9090")
defer conn.Close()
if err != nil {
fmt.Printf("connect failed, err : %v
", err.Error())
return
}
unpack.Encode(conn, "hello world 0!!!")
}
D:gocode1.14.3gocodegateway_demodemoaseunpack cp_clientmain.go
package main
import (
"fmt"
"github.com/e421083458/gateway_demo/demo/base/unpack/unpack"
"net"
)
func main() {
//simple tcp server
//1.監(jiān)聽端口
listener, err := net.Listen("tcp", "0.0.0.0:9090")
if err != nil {
fmt.Printf("listen fail, err: %v
", err)
return
}
//2.接收請求
for {
conn, err := listener.Accept()
if err != nil {
fmt.Printf("accept fail, err: %v
", err)
continue
}
//3.創(chuàng)建協(xié)程
go process(conn)
}
}
func process(conn net.Conn) {
defer conn.Close()
for {
bt, err := unpack.Decode(conn)
if err != nil {
fmt.Printf("read from connect failed, err: %v
", err)
break
}
str := string(bt)
fmt.Printf("receive from client, data: %v
", str)
}
}
D:gocode1.14.3gocodegateway_demodemoaseunpack cp_servermain.go
golang創(chuàng)建udp服務(wù)和客戶端
package main
import (
"fmt"
"net"
)
func main() {
//step 1 連接服務(wù)器
conn, err := net.DialUDP("udp", nil, &net.UDPAddr{
IP: net.IPv4(127, 0, 0, 1),
Port: 9090,
})
if err != nil {
fmt.Printf("connect failed, err: %v
", err)
return
}
for i := 0; i < 100; i++ {
//step 2 發(fā)送數(shù)據(jù)
_, err = conn.Write([]byte("hello server!"))
if err != nil {
fmt.Printf("send data failed, err : %v
", err)
return
}
//step 3 接收數(shù)據(jù)
result := make([]byte, 1024)
n, remoteAddr, err := conn.ReadFromUDP(result)
if err != nil {
fmt.Printf("receive data failed, err: %v
", err)
return
}
fmt.Printf("receive from addr: %v data: %v
", remoteAddr, string(result[:n]))
}
}
D:gocode1.14.3gocodegateway_demodemoaseudp_clientmain.go
package main
import (
"fmt"
"net"
)
func main() {
//step 1 監(jiān)聽服務(wù)器
listen, err := net.ListenUDP("udp", &net.UDPAddr{
IP: net.IPv4(0, 0, 0, 0),
Port: 9090,
})
if err != nil {
fmt.Printf("listen failed, err:%v
", err)
return
}
//step 2 循環(huán)讀取消息內(nèi)容
for {
var data [1024]byte
n, addr, err := listen.ReadFromUDP(data[:])
if err != nil {
fmt.Printf("read failed from addr: %v, err: %v
", addr, err)
break
}
go func() {
//todo sth
//step 3 回復(fù)數(shù)據(jù)
fmt.Printf("addr: %v data: %v count: %v
", addr, string(data[:n]), n)
_, err = listen.WriteToUDP([]byte("received success!"), addr)
if err != nil {
fmt.Printf("write failed, err: %v
", err)
}
}()
}
}
D:gocode1.14.3gocodegateway_demodemoaseudp_servermain.go
golang創(chuàng)建tcp服務(wù)器和客戶端
package main
import (
"fmt"
"net"
)
func main() {
//1、監(jiān)聽端口
listener, err := net.Listen("tcp", "0.0.0.0:9090")
if err != nil {
fmt.Printf("listen fail, err: %v
", err)
return
}
//2.建立套接字連接
for {
conn, err := listener.Accept()
if err != nil {
fmt.Printf("accept fail, err: %v
", err)
continue
}
//3. 創(chuàng)建處理協(xié)程
go process(conn)
}
}
func process(conn net.Conn) {
defer conn.Close() //思考題:這里不填寫會有啥問題?
for {
var buf [128]byte
n, err := conn.Read(buf[:])
if err != nil {
fmt.Printf("read from connect failed, err: %v
", err)
break
}
str := string(buf[:n])
fmt.Printf("receive from client, data: %v
", str)
}
}
服務(wù)端
package client
import (
"bufio"
"fmt"
"net"
"os"
"strings"
)
func main() {
doSend()
fmt.Print("doSend over")
doSend()
fmt.Print("doSend over")
//select {}
}
func doSend() {
//1、連接服務(wù)器
conn, err := net.Dial("tcp", "localhost:9090")
defer conn.Close() //思考題:這里不填寫會有啥問題?
if err != nil {
fmt.Printf("connect failed, err : %v
", err.Error())
return
}
//2、讀取命令行輸入
inputReader := bufio.NewReader(os.Stdin)
for {
// 3、一直讀取直到讀到
input, err := inputReader.ReadString('
')
if err != nil {
fmt.Printf("read from console failed, err: %v
", err)
break
}
// 4、讀取Q時停止
trimmedInput := strings.TrimSpace(input)
if trimmedInput == "Q" {
break
}
// 5、回復(fù)服務(wù)器信息
_, err = conn.Write([]byte(trimmedInput))
if err != nil {
fmt.Printf("write failed , err : %v
", err)
break
}
}
}
客戶端
客戶端:defer conn.Close() //思考題:這里不填寫會有啥問題?(連接一直在建立狀態(tài),除非tcp連接探測后才會關(guān)閉)
服務(wù)端:defer conn.Close() //思考題:這里不填寫會有啥問題?
客戶端發(fā)起了關(guān)閉,服務(wù)端沒有關(guān)閉,此時按照四次揮手圖分析:
客戶端是主動關(guān)閉方,客戶端此時處于FIN-WAIT-2;
服務(wù)端屬于被動關(guān)閉方,服務(wù)端處于CLOSE-WAIT狀態(tài);
golang創(chuàng)建http服務(wù)
服務(wù)端:
創(chuàng)建路由器;
設(shè)置路由規(guī)則;
創(chuàng)建服務(wù)器;
監(jiān)聽端口并提供服務(wù);
客戶端:
創(chuàng)建連接池:
創(chuàng)建客戶端;
請求數(shù)據(jù);
讀取內(nèi)容;
package main
import (
"fmt"
"io/ioutil"
"net"
"net/http"
"time"
)
func main() {
// 創(chuàng)建連接池
transport := &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second, //連接超時
KeepAlive: 30 * time.Second, //探活時間
}).DialContext,
MaxIdleConns: 100, //最大空閑連接
IdleConnTimeout: 90 * time.Second, //空閑超時時間
TLSHandshakeTimeout: 10 * time.Second, //tls握手超時時間
ExpectContinueTimeout: 1 * time.Second, //100-continue狀態(tài)碼超時時間
}
// 創(chuàng)建客戶端
client := &http.Client{
Timeout: time.Second * 30, //請求超時時間
Transport: transport,
}
// 請求數(shù)據(jù)
resp, err := client.Get("http://127.0.0.1:1210/bye")
if err != nil {
panic(err)
}
defer resp.Body.Close()
// 讀取內(nèi)容
bds, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(bds))
}
http客戶端
package main
import (
"log"
"net/http"
"time"
)
var (
Addr = ":1210"
)
func main() {
// 創(chuàng)建路由器
mux := http.NewServeMux()
// 設(shè)置路由規(guī)則
mux.HandleFunc("/bye", sayBye)
// 創(chuàng)建服務(wù)器
server := &http.Server{
Addr: Addr,
WriteTimeout: time.Second * 3,
Handler: mux,
}
// 監(jiān)聽端口并提供服務(wù)
log.Println("Starting httpserver at "+Addr)
log.Fatal(server.ListenAndServe())
}
func sayBye(w http.ResponseWriter, r *http.Request) {
time.Sleep(1 * time.Second)
w.Write([]byte("bye bye ,this is httpServer"))
}
http服務(wù)端
golang http服務(wù)器源碼分析:
在分析httpserver源碼之前,請看看此文章 ,了解下type func的用法,函數(shù)式一等公民概念,不然下面代碼可能難以理解。
type關(guān)鍵字的用法
從最簡單的例子開始:
package main
import (
"log"
"net/http"
"time"
)
var (
Addr = ":1210"
)
func main() {
// 創(chuàng)建路由器
mux := http.NewServeMux()
// 設(shè)置路由規(guī)則
mux.HandleFunc("/bye", sayBye)
// 創(chuàng)建服務(wù)器
server := &http.Server{
Addr: Addr,
WriteTimeout: time.Second * 3,
Handler: mux,
}
// 監(jiān)聽端口并提供服務(wù)
log.Println("Starting httpserver at "+Addr)
log.Fatal(server.ListenAndServe())
}
func sayBye(w http.ResponseWriter, r *http.Request) {
time.Sleep(1 * time.Second)
w.Write([]byte("bye bye ,this is httpServer"))
}
來看看HandleFunc是啥?
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
HandlerFunc(handler)
此處就是用到了type關(guān)鍵字 把一個函數(shù)轉(zhuǎn)成HandlerFunc 類型,并且實現(xiàn)了ServeHTTP方法
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
ServeHTTP方法又實現(xiàn)了Handler接口
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
通過回調(diào)思路最終執(zhí)行了sayBye()
mu:一把鎖
m:存放著路由和回調(diào)函數(shù)
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
h 注冊的函數(shù)
pattern 注冊的路由
type muxEntry struct {
h Handler
pattern string
}
注冊路由
mux.Handle(pattern, HandlerFunc(handler))
開啟服務(wù):
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(ln)
}
func (srv *Server) Serve(l net.Listener) error
處理鏈接:
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)
httpClient源碼簡單解析:
先看看一個簡單的例子:
package main
import (
"fmt"
"io/ioutil"
"net"
"net/http"
"time"
)
func main() {
// 創(chuàng)建連接池
transport := &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second, //連接超時
KeepAlive: 30 * time.Second, //探活時間
}).DialContext,
MaxIdleConns: 100, //最大空閑連接
IdleConnTimeout: 90 * time.Second, //空閑超時時間
TLSHandshakeTimeout: 10 * time.Second, //tls握手超時時間
ExpectContinueTimeout: 1 * time.Second, //100-continue狀態(tài)碼超時時間
}
// 創(chuàng)建客戶端
client := &http.Client{
Timeout: time.Second * 30, //請求超時時間
Transport: transport,
}
// 請求數(shù)據(jù)
resp, err := client.Get("http://127.0.0.1:1210/bye")
if err != nil {
panic(err)
}
defer resp.Body.Close()
// 讀取內(nèi)容
bds, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(bds))
}
分析以后繼續(xù)。。。。。。。。
下一篇網(wǎng)絡(luò)代理之HTTP代理
總結(jié)
以上是生活随笔為你收集整理的golang微服务网关一:网络基础知识扫盲(温故而知新)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。