【go語言實現伺服器接收http請求以及出現泄漏時的解決方案】

來源:互聯網
上載者:User

標籤:select   drive   customer   尋找   now()   next   2.3   最大串連數   star   

一、關於基礎的程式的實現

 剛開始的時候程式是這樣實現的:

// Hellopackage mainimport (    "database/sql"    "fmt"    "io/ioutil"    "log"    "net/http"    "strings"    "time"    _ "github.com/Go-SQL-Driver/MySQL")func main() {    fmt.Println(time.Now().Format("2006-01-02 15:04:05"))    openHttpListen()    //saveToDb()}func openHttpListen() {    http.HandleFunc("/monkeytest", receiveClientRequest)    fmt.Println("go server start running...")    err := http.ListenAndServe("1.2.3.4:5555", nil)                //這裡監聽的地址要換成你自己的IP和連接埠;比如說你通過ifconfig查看自己的IP是15.34.67.23,則這裡就要替換成這個IP,不能是其他的IP,要不然會報錯    if err != nil {        log.Fatal("ListenAndServe: ", err)    }}func receiveClientRequest(w http.ResponseWriter, r *http.Request) {    r.ParseForm()    fmt.Println("收到用戶端請求: ", r.Form)    fmt.Println("method:", r.Method)    fmt.Println("path:", r.URL.Path)    fmt.Println("scheme:", r.URL.Scheme)    fmt.Println("url", r.URL)    for k, v := range r.Form {        fmt.Printf("----------\n")        fmt.Println("key:", k)        fmt.Println("value:", strings.Join(v, ", "))    }    var className string    var pkgName string    var pkgVer string    var leakRoot string    var leakDetail string    var pkgbuildtime string    if len(r.Form["className"]) > 0 {        className = r.Form["className"][0]    }    if len(r.Form["pkgName"]) > 0 {        pkgName = r.Form["pkgName"][0]    }    if len(r.Form["pkgVer"]) > 0 {        pkgVer = r.Form["pkgVer"][0]    }    if len(r.Form["leakRoot"]) > 0 {        leakRoot = r.Form["leakRoot"][0]    }    if len(r.Form["leakDetail"]) > 0 {        leakDetail = r.Form["leakDetail"][0]    }    if len(r.Form["buildtime"]) > 0 {        pkgbuildtime = r.Form["buildtime"][0]    }    body, _ := ioutil.ReadAll(r.Body)    //r.Body.Close()    body_str := string(body)    fmt.Println("body_str:", body_str)    fmt.Println("header", r.Header)    //fmt.Println("Customerid", r.Header.Get("Customerid"))    w.Header().Set("Access-Control-Allow-Origin", "origin")    var result string    if len(leakDetail) != 0 {        result = saveToDb(className, pkgName, pkgVer, leakRoot, leakDetail, pkgbuildtime)    } else {        result = "error"    }    fmt.Fprintf(w, result)}func saveToDb(className string, pkgName string, pkgVer string, leakRoot string, leakDetail string, pkgbuildtime string) string {    db, err := sql.Open("mysql", "username:[email protected](2.3.4.5:3306)/myku?charset=utf8") //這裡的資料庫要換成自己的使用者名稱:密碼@資料庫地址:連接埠/資料庫名    checkErr(err)    if err != nil {        return "error"    }    //插入資料    stmt, err := db.Prepare("insert mytable SET className=?,pkgName=?,pkgVer=?,leakRoot=?,leakDetail=?,leakDate=?,pkgbuildtime=?")  //這裡的mytable換成自己的table表名    //stmt, err := db.Prepare("insert into mytable(className,pkgName,pkgVer,leakRoot,leakDetail,leakDate,pkg) values (?,?,?,?,?,?)")    //rows, err := db.Query("select * from mytable")    checkErr(err)    if err != nil {        return "error"    }    /*fmt.Println("res.", rows)    for rows.Next() {        var className string        rows.Columns()        err = rows.Scan(&className)        checkErr(err)        fmt.Println(className)    }*/    //checkErr(err)    res, err := stmt.Exec(className, pkgName, pkgVer, leakRoot, leakDetail, time.Now().Format("2006-01-02 15:04:05"), pkgbuildtime)    fmt.Println("res.", res)    if err != nil {        return "error"    } else {        return "success"    }}func checkErr(err error) {    if err != nil {        fmt.Println("error.")        //panic(err)    }}

後來因為提示存在too many open files的問題,就做了一版修改,改成了:

// Hellopackage mainimport (    "database/sql"    "fmt"    "io/ioutil"    "log"    "net/http"    "strings"    "time"    _ "github.com/Go-SQL-Driver/MySQL")func main() {    fmt.Println(time.Now().Format("2006-01-02 15:04:05"))    openHttpListen()    //saveToDb()}func openHttpListen() {    srv := &http.Server{      Addr:         "1.2.3.4:5555",    ReadTimeout: 5 * time.Second,    WriteTimeout: 10 * time.Second,    }        http.HandleFunc("/monkeytest", receiveClientRequest)    fmt.Println("go server start running...")    err := srv.ListenAndServe()    if err != nil {        log.Fatal("ListenAndServe: ", err)    }}func receiveClientRequest(w http.ResponseWriter, r *http.Request) {    r.ParseForm()    fmt.Println("收到用戶端請求: ", r.Form)    fmt.Println("method:", r.Method)    fmt.Println("path:", r.URL.Path)    fmt.Println("scheme:", r.URL.Scheme)    fmt.Println("url", r.URL)    for k, v := range r.Form {        fmt.Printf("----------\n")        fmt.Println("key:", k)        fmt.Println("value:", strings.Join(v, ", "))    }    var className string    var pkgName string    var pkgVer string    var leakRoot string    var leakDetail string    var pkgbuildtime string    if len(r.Form["className"]) > 0 {        className = r.Form["className"][0]    }    if len(r.Form["pkgName"]) > 0 {        pkgName = r.Form["pkgName"][0]    }    if len(r.Form["pkgVer"]) > 0 {        pkgVer = r.Form["pkgVer"][0]    }    if len(r.Form["leakRoot"]) > 0 {        leakRoot = r.Form["leakRoot"][0]    }    if len(r.Form["leakDetail"]) > 0 {        leakDetail = r.Form["leakDetail"][0]    }    if len(r.Form["buildtime"]) > 0 {        pkgbuildtime = r.Form["buildtime"][0]    }        defer r.Body.Close()    body, _ := ioutil.ReadAll(r.Body)    //r.Body.Close()    body_str := string(body)    fmt.Println("body_str:", body_str)    fmt.Println("header", r.Header)    //fmt.Println("Customerid", r.Header.Get("Customerid"))    w.Header().Set("Access-Control-Allow-Origin", "origin")    var result string    if len(leakDetail) != 0 {        result = saveToDb(className, pkgName, pkgVer, leakRoot, leakDetail, pkgbuildtime)    } else {        result = "error"    }    fmt.Fprintf(w, result)}func saveToDb(className string, pkgName string, pkgVer string, leakRoot string, leakDetail string, pkgbuildtime string) string {    db, err := sql.Open("mysql", "username:[email protected](2.3.4.5:3306)/myku?charset=utf8")        defer db.Close()    checkErr(err)    if err != nil {        return "error"    }    //插入資料    stmt, err := db.Prepare("insert mytable SET className=?,pkgName=?,pkgVer=?,leakRoot=?,leakDetail=?,leakDate=?,pkgbuildtime=?")    //stmt, err := db.Prepare("insert into mytable (className,pkgName,pkgVer,leakRoot,leakDetail,leakDate,pkg) values (?,?,?,?,?,?)")    //rows, err := db.Query("select * from mytable")    checkErr(err)    if err != nil {        return "error"    }    /*fmt.Println("res.", rows)    for rows.Next() {        var className string        rows.Columns()        err = rows.Scan(&className)        checkErr(err)        fmt.Println(className)    }*/    //checkErr(err)    res, err := stmt.Exec(className, pkgName, pkgVer, leakRoot, leakDetail, time.Now().Format("2006-01-02 15:04:05"), pkgbuildtime)    fmt.Println("res.", res)    if err != nil {        return "error"    } else {        return "success"    }}func checkErr(err error) {    if err != nil {        fmt.Println("error.")        //panic(err)    }}

 

然後在linux下通過nohup go run Hello.go &之後,程式正式跑起來,(注意:伺服器的IP一定是本地的IP地址才可以)就可以在瀏覽器裡面輸入:

http://1.2.3.4:5555/monkeytest,然後就能將請求提交到go的伺服器端

二、關於go中出現的問題的解決(包括髮現問題、解決問題的過程)

 

將一中的程式,在linux下運行之後,通過nohup go run Hello.go &運行之後,會將即時的資訊全部列印到nohup.out中,查看這個檔案,會出現這個提示:

2017/07/28 01:51:35 http: Accept error: accept tcp 10.252.149.243:9090: accept4: too many open files; retrying in 5ms

2017/07/28 01:51:35 http: Accept error: accept tcp 10.252.149.243:9090: accept4: too many open files; retrying in 10ms

2017/07/28 01:51:35 http: Accept error: accept tcp 10.252.149.243:9090: accept4: too many open files; retrying in 20ms

2017/07/28 01:51:35 http: Accept error: accept tcp 10.252.149.243:9090: accept4: too many open files; retrying in 40ms

2017/07/28 01:51:35 http: Accept error: accept tcp 10.252.149.243:9090: accept4: too many open files; retrying in 80ms

2017/07/28 01:51:35 http: Accept error: accept tcp 10.252.149.243:9090: accept4: too many open files; retrying in 160ms

2017/07/28 01:51:35 http: Accept error: accept tcp 10.252.149.243:9090: accept4: too many open files; retrying in 320ms

2017/07/28 01:51:35 http: Accept error: accept tcp 10.252.149.243:9090: accept4: too many open files; retrying in 640ms

2017/07/28 01:51:36 http: Accept error: accept tcp 10.252.149.243:9090: accept4: too many open files; retrying in 1s

2017/07/28 01:51:37 http: Accept error: accept tcp 10.252.149.243:9090: accept4: too many open files; retrying in 1s

 

這種情況下就會導致該寫入到資料庫中的內容無法寫入。

 

因為:在linux下一切都是檔案,所有不管是nohup.out還是socket串連都是檔案,所以這裡就可以進行尋找在當前pid下的檔案數有幾個了,可以通過下面2的(2)中的方式查看某個pid下的檔案數及詳情

 

經過尋找之後,發現這種情況可以通過以下的方式先緩解,首先通過:

1、ulimit -n查看最大串連數,如果是1024的話,可以嘗試將其修改為4096

 

2、這樣無法根本上解決問題,繼續查:

(1)開啟檔案太多,是否說明檔案控制代碼出現了泄漏,或者是:db操作出現了泄漏,那麼是否程式中沒有關閉呢?

查看之後,確實沒有關閉,因此增加:defer db.close()和defer f.close()的處理

 

這裡defer的含義是:代表在return之前執行關閉,但是有弊端,尤其是跟帶命名的返回參數一起使用時,具體是:https://tiancaiamao.gitbooks.io/go-internals/content/zh/03.4.html 文章中描述的這樣:

函數返回的過程是這樣的:先給傳回值賦值,然後調用defer運算式,最後才是返回到調用函數中。

其實原因就是:return xxx並不是一條原子指令

 

但是因為close的操作中沒有增加這個返回參數,所以影響不大可以這樣用

 

(2)然後修改之後,重新啟動程式,不斷創造串連資料庫及開啟檔案的處理操作:

通過下面的命令:

首先擷取go的pid值:ps aux | grep go,例如得到的結果是:29927

然後再執行:ls -l /proc/29927/fd/ | wc -l  ,得到的結果就是:6,說明socket的數目沒有增長

然後再執行:ls -l /proc/29927/fd/  ,得到的結果就是:當前進程開啟的串連數的資訊詳情

 

備忘:在(1)中未增加defer close()操作之前, socket的數目會隨著http的請求和資料庫的串連增多

 

後面會繼續關注這裡,查看問題是否完全解決了。

 

【go語言實現伺服器接收http請求以及出現泄漏時的解決方案】

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.