一、关于基础的程序的实现

刚开始的时候程序是这样实现的:

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

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

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

  1. 图解HTTP权威指南(三)| Web服务器对HTTP请求的处理和响应

    作者简介   李先生(Lemon),高级运维工程师(自称),SRE专家(目标),梦想在35岁买一辆保时捷.喜欢钻研底层技术,认为底层基础才是王道.一切新技术都离不开操作系统(CPU.内存.磁盘).网络 ...

  2. 4xx错误的本质:服务器已经接收到请求

    4xx错误的本质:服务器已经接收到请求, 路径错误! { URL: http://10.100.138.32:8046/3-0/app/account/maxin } { status code: 4 ...

  3. http400错误基本都是http请求参数与服务器接收参数不匹配

    http400错误基本都是http请求参数与服务器接收参数不匹配造成的, 如:1)post请求,你发了个get请求 2)content-type指定不匹配致使参数无法读出来

  4. url请求时,参数中的+在服务器接收时为空格,导致AES加密报出javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher

    报错的意思的是使用该种解密方式出入长度应为16bit的倍数,但实际的错误却不是这个,错误原因根本上是因为在http请求是特殊字符编码错误,具体就是base64生成的+号,服务器接收时成了空格,然后导致 ...

  5. TCP网络编程-----客户端请求连接服务器、向服务器发数据、从服务器接收数据、关闭连接

    SOCKET m_sockClient; unsigned short portNum; ------------------------------------------------------- ...

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

    一.认识JSON JSON是一种轻量级.基于文本.与语言无关的数据交换格式,可以用文本格式的形式来存储或表示结构化的数据. 二.POST请求与Content-Type: application/jso ...

  7. PHP 是怎么接收到请求的?

    本篇文章主要描述一下几点 ● nginx 怎么转发请求 给 PHPFPM? ● CGI 和 FastCGI 到底是个什么玩意? ● PHPFPM 是什么?有什么作用? 简单场景描述 在浏览器上访问一个 ...

  8. 转:无法向会话状态服务器发出会话状态请求请。确保 ASP.NET State Service (ASP.NET 状态服务)已启动

    今天看到一篇文章感觉不错,收藏转载下. 原文地址:http://blog.csdn.net/sntyy/article/details/2090347 版权为原作者所有 无法向会话状态服务器发出会话状 ...

  9. 服务器如何处理http请求

    1.需求 了解服务端如何处理http请求,了解基本的处理流程 2.实战 处理http请求分为7个步骤 2.1 Tcp连接 建立一条tcp链接,(若之前不存在持久链接keep-alive),把客户端的i ...

随机推荐

  1. js页面百分比缩放

    <script> var docEl = document.documentElement, resizeEvt = 'orientationchange' in window ? 'or ...

  2. 微信、企业微信和支付窗 SDK 三合一,JeeWx-api 1.2.0 版本发布

    摘要: JEEWX-API 是第一款JAVA版微信极速SDK,同时集成企业微信SDK,支付窗SDK,可以快速的基于她进行微信公众号.企业微信.支付窗应用开发.基于 jeewx-api 开发可以立即拥有 ...

  3. mui longtap 事件无效

    1.mui  的部分事件默认是关闭的 需要在init中单独配置事件开关 mui.init({ gestureConfig: { longtap: true, //默认为false } })

  4. 使用dig或nslookup指定dns服务器查询域名解析

    一般来说linux下查询域名解析有两种选择,nslookup或者dig,而在使用上我觉得dig更加方便顺手.如果是在linux下的话,只要装上dnsutils这个包就可以使用dig命令, 安装bind ...

  5. delphi注册热键方法(一)

    uses windows,menus; ..... //声明 HotKey_Key: Word; HotKey_Shift: Word; procedure WMHotKey(var msg : Tm ...

  6. java实现excel表格导出

    Java 实现导出excel表 POI 1.首先下载poi-3.6-20091214.jar,下载地址如下: http://download.csdn.net/detail/evangel_z/389 ...

  7. 29.Junit测试框架.md

    目录 作用 使用 单个对象的测试 有步骤的测试 注意 作用 用于简化测试,可以对方法,类,包等范围测试 使用 单个对象的测试 在需要测试的方法上加注解@Test,选中方法,运行里选择junit执行 同 ...

  8. 16.1 用auth0服务 实现用登录和管理 使用auth版本的2个大坑。

    这是三周内容,实现用户登录和管理 回到master分支 切换到 han分支 更新一下 然后工作 开始工作写代码了 安装2个angular端的auth0的lib,也可不安装,后边有不安装的做法 不安装的 ...

  9. 使用__all__限制模块可被导入对象

    经常我们会编写自定义模块,用于被别的脚本调用;有时候为了方便,会使用from module_name import *的方式导入,这样会把模块中全部对象导入进来; 使用__all__结合列表,可以控制 ...

  10. 网络层——IP报文头介绍

    IP数据包也叫IP报文分组,传输在ISO网络7层结构中的网络层,它由IP报文头和IP报文用户数据组成,IP报文头的长度一般在20到60个字节之间,而一个IP分组的最大长度则不能超过65535个字节.  ...