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


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

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

// Hello
package main

import (
    "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:passwd@tcp(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的問題,就做了一版修改,改成了:

// Hello
package main

import (
    "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:passwd@tcp(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的請求和數據庫的連接增多

 

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM