【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:[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的问题,就做了一版修改,改成了:

// 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:[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的请求和数据库的连接增多

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

时间: 2024-10-12 23:56:58

【go语言实现服务器接收http请求以及出现泄漏时的解决方案】的相关文章

Struts2 Action接收POST请求JSON数据及其实现解析

一.认识JSON JSON是一种轻量级.基于文本.与语言无关的数据交换格式,可以用文本格式的形式来存储或表示结构化的数据. 二.POST请求与Content-Type: application/json 常用的HTTP请求方法有GET, POST, PUT, DELETE等.在提交POST请求时,请求数据放在消息体(Body)中,请求数据的格式及编码方式用Content-Type来指定.如我们常用的表单<form>提交,其Content-Type默认为application/x-www-for

PHP 是怎么接收到请求的?

本篇文章主要描述一下几点 ● nginx 怎么转发请求 给 PHPFPM? ● CGI 和 FastCGI 到底是个什么玩意? ● PHPFPM 是什么?有什么作用? 简单场景描述 在浏览器上访问一个 php+nginx+mysql 构建的商城,并且购买一件商品. 分析 (这里访问的有两种资源) ● 静态资源(网站的一些图片,图标等) ● 动态资源 (购买商品的价格,商品的简介等) 浏览器发起请求 --> web_server(nginx)分发处理 --> php 执行代码返回结果 (这是大概

IIS接收浏览器请求报文并将请求报文转给asp.net框架的过程(初级版)

网站的页面大致分为两类: 一.静态页面 二.动态页面 所谓的静态页面与动态页面,并不是依据页面内容的动与静来加以区分的,而是看服务器对页面的处理方式来判断的. 通过浏览器访问服务器上的一个html页面,IIS服务器接收到来自浏览器的http请求,判断请求的页面类型,发现是html文件,IIS直接读取硬盘里的html文件 返回给浏览器,浏览器拿到页面后直接渲染展示. 通过浏览器访问服务器上的一个.ashx或者.aspx后缀的页面时,IIS服务器接收到来自浏览器的http请求,发现后缀是.ashx或

无法向会话状态服务器发出会话状态请求(转)

无法向会话状态服务器发出会话状态请求.请确保 ASP.NET State Service (ASP.NET 状态服务)已启动,并且客户端端口与服务器端口相同.如果服务器位于远程计算机上,请检查 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\aspnet_state\Parameters\AllowRemoteConnection 的值,确保服务器接受远程请求.如果服务器位于本地计算机上,并且上面提到的注册表值不存在或者设置为 0,则状态

[NIO]用dawn发送接收HTTP请求

HTTP协议的下层使用的是tcp,所以我们建立一个tcp连接就能发送接收http请求.dawn底层使用了nio,但是经过dawn的封装之后,我们在编写代码的时候,就和使用普通的阻塞式socket一样 ,不需要关注nio的api.可以把我们的精力放在业务逻辑的处理上.举例如下,下例的功能就是取回baidu首页: package zhmt.dawn.nio; import java.nio.charset.Charset; import zhmt.dawn.nio.buffer.ScalableDi

无法向会话状态服务器发出会话状态请求

生产环境: 操作系统:win2003 web: IIS 6.0 程序语言: asp.net 运行问题: 无法向会话状态服务器发出会话状态请求.请确保已启动ASP.NET State service,并且客户端和服务器端口是相同的.如果服务器位于远程计算机上,请检查HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/aspnet_state/Parameters/AllowRemoteConnection的值,确保服务器接受远程请求. 说明:

spring,spring mvc ,mina,接收http请求然后调用mina给客户端发送信息

这里比较复杂的莫过于spring的配置了那就先上配置applicationContext.xml mina的配置在最下面 <spring  -- mina >这个 之上的 配置有velocity和memcache还有springjdbc,自动装配,json对象支持(注解@ResponseBody), 切面事务管理 ,  当然这些大家能用的上的 拿走  用不上删掉(跟没说一样) <?xml version="1.0" encoding="UTF-8"

服务器如何处理http请求

1.需求 了解服务端如何处理http请求,了解基本的处理流程 2.实战 处理http请求分为7个步骤 2.1 Tcp连接 建立一条tcp链接,(若之前不存在持久链接keep-alive),把客户端的ip和port,服务端的ip和port数据放到web服务器连接表中.服务器随时监听链接表中的链接,看有没有数据变化 2.2 接收http请求 一旦我们发送http请求了,这条tcp链接就开始工作了.因为web服务器链接表中有许多链接需要被处理,处理的方式有单线程,多线程这些(这些涉及操作系统的知识).

利用HttpURLConnecion通过Nginx向代理邮件服务器发送POST请求

第一步:获取邮件各种参数,通过URLencode和Base64编码之后发送请求参数. 请求参数中,有邮件附件这样的大件,如何当做请求发送呢? 首先,将邮件内容转为字节数组,转为字节数组之后可以当做二进制操作了,保持了附件最原始的面貌,不会被任何其他因素影响. byte[] att= attachment.getContent(); //附件内容 //利用Base64进行加密传输,虽然加密的不够 Base64 base64 = new Base64(); //org.apache.commons.