golang shutdown分析

測試代碼

客戶端

package main

import (
	"net/http"
	log "github.com/sirupsen/logrus"
	"io/ioutil"
	"fmt"
	"bytes"
	"sync"
	_"time"
)

func main() {
	var wg sync.WaitGroup
	var count int
	var rw sync.RWMutex
TEST:
	for i := 0; i < 1; i++ {
		wg.Add(1)
		go func () {
			defer wg.Done()
			tr := http.Transport{DisableKeepAlives: false}
			client := &http.Client{Transport: &tr}
			for {
				f, err := ioutil.ReadFile("data")
				if err != nil {
					fmt.Println("read file err", err)
					return
				}
				fmt.Println(len(f))
				reader := bytes.NewReader(f)
				rw.Lock()
				count += 1
				index := count
				rw.Unlock()
				resp, err := client.Post("http://0.0.0.0:8888", "application/x-www-form-urlencoded", reader)
				if err != nil {
					rw.RLock()
					currentCount := count
					rw.RUnlock()
					log.Fatal(err, index, currentCount)
				}
				defer resp.Body.Close()
				data, err := ioutil.ReadAll(resp.Body)
				if err != nil {
					log.Fatal(err)
				}
				log.Printf("data[%s]", string(data))
				// time.Sleep(time.Second)
			}
		}()
	}
	wg.Wait()
	goto TEST
}複製代碼

服務端

package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"time"
	"context"
)

type myHandler struct {

}

func (h myHandler)ServeHTTP(w http.ResponseWriter, r *http.Request) {
	//print header
	// fmt.Println("header", r.Header)
	//debug body
	_, err := ioutil.ReadAll(r.Body)
	if err != nil {
		fmt.Println("read body error", err)
		io.WriteString(w, "read you body error!")
		return
	}
	// fmt.Println("data len", len(data))

	io.WriteString(w, "goad it")
	return
}

func main() {
	// http.HandleFunc("/", myHandler)
	// err := http.ListenAndServe("0.0.0.0:8888", nil)
	// if err != nil {
	// fmt.Println("ListenAndServe error", err)
	// return
	// }
	server := &http.Server {
		Addr: "0.0.0.0:8888",
		Handler: myHandler{},
	}

	d := time.Duration(time.Second*10)
	t := time.NewTimer(d)
	defer t.Stop()
	go func (){
			<- t.C
			shutdown(server)
		}()
	server.ListenAndServe()
	for {
		fmt.Println(1)
		time.Sleep(time.Second)
	}
	fmt.Println(2)
	return
}

func shutdown(server *http.Server) {
	ctx, cancel := context.WithTimeout(context.TODO(), 3600)
	defer cancel()
	server.Shutdown(ctx)
}
複製代碼

實驗
代碼:
服務端執行位置:
執行完這個以後會影響keepalive的執行(其實抓包可發現每15秒服務端會發送一次tcp的keepalive)

func (s *Server) doKeepAlives() bool {        return atomic.LoadInt32(&s.disableKeepAlives) == 0 && !s.shuttingDown()}func (s *Server) shuttingDown() bool {        return atomic.LoadInt32(&s.inShutdown) != 0}複製代碼

上邊doKeepAlives()在服務端handler完本次請求以後會執行:

//在go1.10 net/http/server.go 1845行 if !w.conn.server.doKeepAlives() { // We're in shutdown mode. We might've replied // to the user without "Connection: close" and // they might think they can send another // request, but such is life with HTTP/1.1. return }複製代碼
服務端就是上邊調試截圖的位置
客戶端就是在等待POST響應的數據

抓包現象


能夠看到服務端發送的鏈接斷開,客戶端也斷開了鏈接,而後從新發起請求,黑色的區域忽略,那是後邊調試程序中止致使的。
在closeIdles中
程序執行狀態以下,右上圖是server 端的調試,左下是client的狀態
對應的包:
因此能夠分析下若是shutdown已經執行到循環關閉當前全部鏈接的時候對應不一樣鏈接狀態的效果
1.準備建連
拒絕鏈接
2.active
等待idle而後關閉鏈接
3.idle
直接發起關閉
4.closed
忽略

複製代碼

真的沒有任何問題嗎?
其實shutdown裏邊在執行空閒鏈接關閉的時候我跟了下,一直跟到asm的彙編上實在看不懂了,我看着Wireshark的監控,此時在剛進入系統調用的時候,服務端和客戶端尚未包交換信息,除了keepalive,
在執行:
圖中的系統調用以後

上邊看到的包每沒有RST是由於我在調試,client沒有發包,我放一張不調試的

因此在懷疑close的方式致使出現這種狀況,因此針對長鏈接,若是客戶端持續的發送數據可能會出現這種狀況。git

爲了驗證下,我改了closeIdles的代碼,改爲了只關閉服務端的寫,讓客戶端能把數據發送過來github


穩定是有四次揮手的,可是客戶端仍是收到了EOF,服務端不是優雅的。app

再改動下,讓shutdown退出以前sleep了500毫秒tcp


紅色的RST忽略,那是客戶端發起的從新鏈接測試

因此初步結論就是server沒法作到真正的graceful。ui

相關文章
相關標籤/搜索