go进行http请求偶发EOF问题分析

 更新时间:2025年01月09日 08:49:44   作者:刀山羊  
go使用连接池进行http请求,一般都能请求成功,但偶然会出现请求失败返回EOF错误的情况,本文主要来带大家分析一下为什么会出现这样的问题并提供解决方法,需要的可以参考下

简介

go使用连接池进行http请求,一般都能请求成功,但偶然会出现请求失败返回EOF错误的情况;类似java的org.apache.http.NoHttpResponseException

分析

客户端通过keep alive机制保障性能,简单理解就是复用tcp五元会话,用于进行多次http请求;但如果服务端的空闲保活时间是10s,在第一次请求完的10s进行了第二次请求,此时客户端认为连接仍然有效继续发起请求,但服务端发出了FIN报文不再对此连接进行响应,从而导致客户端请求失败并出现EOF错误。

偶发就是因为两个时间要恰好碰到一起才可能触发这个问题

  • 服务器发送了FIN报文,但是客户端还没有收到,但是客户端已经发送了请求数据包
  • 如果在服务器超时前发起了请求,那连接此时还可用,正常
  • 如果在服务器超时后发起了请求,那连接已经完成FIN关闭流程,请求会触发新的会话,正常

解决方式:

  • 在出现EOF的时候,进行重试,此时会触发新的五元组连接进行请求(推荐)
  • 设置客户端的空闲保活时间小于服务端的空闲保活时间
    • IdleConnTimeout 此时客户端会在超时时主动向服务端发送RST进行连接重置

代码

package main

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"time"
)

func main() {
	// 创建自定义的 Transport,设置连接池参数
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: true, // 忽略 TLS 证书验证
		},
		MaxIdleConns:        1,                // 限制最大空闲连接数为1
		MaxIdleConnsPerHost: 1,                // 限制每个host最大空闲连接数为1
		IdleConnTimeout:     20 * time.Second, // 本地空闲连接超时设置为20s
		DisableKeepAlives:   false,            // 启用 keep-alive
		MaxConnsPerHost:     1,                // 限制每个host的最大连接数为1,强制复用连接
		ForceAttemptHTTP2:   false,            // 禁用 HTTP/2
	}

	// 创建 HTTP 客户端
	client := &http.Client{
		Transport: tr,
		Timeout:   5 * time.Second, // 设置请求超时时间
	}

	// 准备请求参数
	url := "https://192.168.24.70:2018/api/zguard/sysmng/syscfg/basecfg/sysname/651d5b9f-225b-4c8c-9f06-80bfad3fa977"
	cookie := "session-id=f416b188c91bc72a06853b362d5cb7b3a6b68a43"

	// 准备请求体数据
	requestBody := map[string]string{
		"sys_name": "N-GUARD",
	}
	// 将 map 转换为 JSON
	jsonBody, err := json.Marshal(requestBody)
	if err != nil {
		fmt.Printf("JSON 编码失败: %v\n", err)
	}

	// 循环发送请求,模拟使用已关闭的连接
	for i := 0; i < 5; i++ { // 只测试两次请求即可

		// 每次请求都创建新的 bytes.Buffer,确保 Body 可以重复读取
		bodyReader := bytes.NewBuffer(jsonBody)

		req, err := http.NewRequest("PUT", url, bodyReader)
		if err != nil {
			fmt.Printf("创建请求失败: %v\n", err)
			continue
		}

		// 设置 Content-Length
		req.ContentLength = int64(len(jsonBody))
		// 设置请求头
		req.Header.Set("Cookie", cookie)
		req.Header.Set("Content-Type", "application/json")

		fmt.Printf("发送第 %d 个请求...\n", i+1)
		// 发送请求
		resp, err := client.Do(req)
		if err != nil {
			fmt.Printf("请求失败: %v\n", err)
			if errors.Is(err, io.EOF) {
				fmt.Printf("连接不再可用: 重试:新的五元重新发起连接\n")
				bodyReader := bytes.NewBuffer(jsonBody)

				reqretry, err := http.NewRequest("PUT", url, bodyReader)
				if err != nil {
					fmt.Printf("创建请求失败: %v\n", err)
					continue
				}

				// 设置 Content-Length
				reqretry.ContentLength = int64(len(jsonBody))
				// 设置请求头
				reqretry.Header.Set("Cookie", cookie)
				reqretry.Header.Set("Content-Type", "application/json")

				resp, err = client.Do(reqretry)
				if err != nil {
					fmt.Printf("err:\n", err)
					continue
				}
			} else {
				continue
			}
		}

		// 读取响应
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			fmt.Printf("读取响应失败: %v\n", err)
		}
		resp.Body.Close()

		fmt.Printf("请求 %d - 状态码: %d, 响应: %s\n", i+1, resp.StatusCode, string(body))

		fmt.Println("等待10秒后发送第二个请求...")
		time.Sleep(10 * time.Second) // 等待10秒,此时服务端已经关闭连接(10s)
		time.Sleep(500 * time.Millisecond)
	}
}

运行

[xiaofeng@localhost httpkeepalive]$ go run main.go 
发送第 1 个请求...
请求 1 - 状态码: 200, 响应: {"code":0,"result":"0","message":"成功"}
等待10秒后发送第二个请求...
发送第 2 个请求...
请求 2 - 状态码: 200, 响应: {"code":0,"result":"0","message":"成功"}
等待10秒后发送第二个请求...
发送第 3 个请求...
请求失败: Put "https://192.168.24.70:2018/api/zguard/sysmng/syscfg/basecfg/sysname/651d5b9f-225b-4c8c-9f06-80bfad3fa977": EOF
连接不再可用: 重试:新的五元重新发起连接
请求 3 - 状态码: 200, 响应: {"code":0,"result":"0","message":"成功"}
等待10秒后发送第二个请求...
发送第 4 个请求...
请求失败: Put "https://192.168.24.70:2018/api/zguard/sysmng/syscfg/basecfg/sysname/651d5b9f-225b-4c8c-9f06-80bfad3fa977": EOF
连接不再可用: 重试:新的五元重新发起连接
请求 4 - 状态码: 200, 响应: {"code":0,"result":"0","message":"成功"}
等待10秒后发送第二个请求...
发送第 5 个请求...
请求 5 - 状态码: 200, 响应: {"code":0,"result":"0","message":"成功"}
等待10秒后发送第二个请求...

报文

到此这篇关于go进行http请求偶发EOF问题分析的文章就介绍到这了,更多相关go http请求偶发EOF内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Go设计模式之迭代器模式讲解和代码示例

    Go设计模式之迭代器模式讲解和代码示例

    迭代器是一种行为设计模式, 让你能在不暴露复杂数据结构内部细节的情况下遍历其中所有的元素,本文将为大家详细介绍Go 迭代器模式,文中详细的代码示例,需要的朋友可以参考下
    2023-07-07
  • Go使用chan或context退出协程示例详解

    Go使用chan或context退出协程示例详解

    这篇文章主要为大家介绍了Go使用chan或context退出协程示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • 使用Go语言进行安卓开发的详细教程

    使用Go语言进行安卓开发的详细教程

    本文将介绍如何使用Go语言进行安卓开发,我们将探讨使用Go语言进行安卓开发的优点、准备工作、基本概念和示例代码,通过本文的学习,你将了解如何使用Go语言构建高效的安卓应用程序,需要的朋友可以参考下
    2023-11-11
  • Golang使用crypto/ed25519实现数字签名和验证

    Golang使用crypto/ed25519实现数字签名和验证

    本文将深入探讨如何在 Golang 中使用 crypto/ed25519 进行数字签名和验证,我们将从基本原理开始,逐步引导读者了解生成密钥对、进行数字签名,以及验证签名的具体过程,希望对大家有所帮助
    2024-02-02
  • Go语言如何高效的进行字符串拼接(6种方式对比分析)

    Go语言如何高效的进行字符串拼接(6种方式对比分析)

    本文主要介绍了Go语言如何高效的进行字符串拼接(6种方式对比分析),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • Go语言协程通道使用的问题小结

    Go语言协程通道使用的问题小结

    本文主要介绍了Go语言协程通道使用的问题小结,详细的介绍了使用的一些重要问题,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-08-08
  • 浅谈Go语言中的接口类型

    浅谈Go语言中的接口类型

    Go语言中接口是一种抽象的类型,本文主要介绍了浅谈Go语言中的接口类型,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-08-08
  • golang实现RPC模块的示例

    golang实现RPC模块的示例

    本文详细介绍了在Go语言中如何实现RPC模块,包括RPC服务端和客户端的构建及代码实现,同时提到了使用JSON-RPC的方法,通过简单的步骤,可以实现跨进程的远程过程调用,感兴趣的可以了解一下
    2024-09-09
  • golang 如何替换掉字符串里面的换行符\n

    golang 如何替换掉字符串里面的换行符\n

    这篇文章主要介绍了golang 替换掉字符串里面的换行符\n操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-03-03
  • Go Module依赖管理的实现

    Go Module依赖管理的实现

    Go Module是Go语言的官方依赖管理解决方案,其提供了一种简单、可靠的方式来管理项目的依赖关系,本文主要介绍了Go Module依赖管理的实现,感兴趣的可以了解一下
    2024-06-06

最新评论