zoukankan      html  css  js  c++  java
  • 【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("----------
    ")
            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("----------
    ")
            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的请求和数据库的连接增多

    后面会继续关注这里,查看问题是否完全解决了。

  • 相关阅读:
    Spring MVC学习03页面跳转
    Spring Boot学习07配置加载顺序
    Spring MVC学习01从空白Maven项目搭建环境
    Spring MVC学习05JSON序列化
    剑指Offer 44 数字序列中某一位的数字
    Spring MVC学习06异常解析器
    MSSQL·查看DB中所有表及列的相关信息
    MSSQL·查询数据库中所有索引的相关信息
    MSSQL·最长输出长度限制之解决方案
    .Net Core·热加载的实现及测试
  • 原文地址:https://www.cnblogs.com/keke-xiaoxiami/p/7484228.html
Copyright © 2011-2022 走看看