Golang网络故障时如何中止程序运行

Golang网络故障时如何中止程序运行 我在循环中向网站发送GET请求,一旦遇到错误程序就会停止运行,循环中的后续工作也无法继续。请问在Go语言中通常如何处理异常情况?谢谢

15 回复

如果网站不可用,我会收到一个错误,并且应用程序在请求时退出循环…

更多关于Golang网络故障时如何中止程序运行的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你说得对,但我该如何捕获这个网络错误?

panic: runtime error: index out of range

你遇到了什么错误?

我从未遇到过在 defer 调用 Close() 时出现任何问题…

func main() {
    fmt.Println("hello world")
}

我在网上找到了一个解决方案,它运行正常,循环不会被中断

defer func() {
if r := recover(); r != nil {
    log.Println("Recovered in custom function", r)
}
}()

在你未展示的代码片段中,存在导致数组越界的操作。

与其使用 recover 来忽略问题,更应该在之前检查代码,确保正确校验所有返回的 error 值。

我认为你犯了一个非常大的错误。你必须检查代码中的问题,而不是从panic中恢复。程序中出现panic是不正常的。panic是非常严重的错误,只能在特殊情况下进行恢复,否则你必须找出导致错误的原因并进行修复(在你的具体案例中是索引越界问题)。

但如果我不需要在错误后返回呢?目前任何错误都会中断循环……应用程序停止……

我遇到这个错误,并包含了布尔值(真/假)检查,但还是出现错误

panic: runtime error: index out of range

我的循环停止了(在其他语言中我处理了异常,程序会继续运行……但在这里却不行。

在 Go 语言中,没有异常机制。

当函数执行失败时,要么通过返回值返回 error 类型,要么在发生严重错误时触发 panic。您可以使用 defer 从 panic 中恢复,具体方法参见 Defer, Panic, and Recover

如需进一步帮助,建议尝试编写一个能复现问题的最小代码示例。

func main() {
    fmt.Println("hello world")
}

NobbZ:

我不清楚在你展示的代码中什么会导致数组越界恐慌,因为没有任何通过索引访问切片的操作,因此我们已知的代码不可能产生这种恐慌。

在 -http.postform() 请求之后

但问题甚至不在于 resp==nil…,我尝试手动处理这些错误原因,但什么都没发现。我采用了恢复机制来处理,对我来说这个方法有效

// 代码示例
func example() {
    // 此处添加代码
}

NobbZ:

失败函数要么在其返回值中包含 error,要么在出现严重问题时触发 panic。你可以使用 defer 从 panic 中恢复,如 Defer, Panic, and Recover 所述。

我使用 defer 关闭了响应体(defer rest.Body()),但随后遇到了错误,导致应用程序停止工作。如何确保循环不会因任何错误而中断,并保持继续运行?

可能最简单的做法是:如果出现错误,不要关闭响应体。

if err != nil {
  checkerror(err) // 或者直接打印错误
  return
}

或者,你可以让 checkerror() 返回一个布尔值,然后利用这个返回值:

func checkerror(err error) bool {
  if err != nil {
    fmt.Println(err)
    return true
  }
  return false
}

…

if checkerror(err) { return }

附言:众所周知我不太喜欢 Go 的错误处理方式,但我更不赞同使用那些错误处理函数。错误应该在返回时就地处理。

以下是示例

func main(){
    for i := 0; i < 10; i++ {

        Simplereq()
	}
}

func Simplereq()  {
var s, _ = url.ParseQuery("name=Golberg&email=mymail+ffggff@mail.com&locale=en&terms_accepted=on")

apiUrl := "https://www.activityinfo.org/signUp"
resp, err := http.PostForm(apiUrl,s)
checkerror(err)

defer resp.Body.Close()

fmt.println(resp.StatusCode)
}

func check error(err error){
    if err!=nil{
    fmt.println(err)
    }
}

ext:

但如果我不需要在错误后执行返回操作呢?

那么将 return 替换为应该执行的操作。

无论如何,如果 http.PostForm() 返回了错误,那么 resp 将会是 nil,因此任何尝试访问其字段的操作都会导致程序崩溃。

在你展示的代码中,当 err != nil 时,对 resp 进行任何有意义的操作都是不可能的。

ext:

我遇到了这个错误,并且包含了布尔值(真/假)的检查,但我还是收到了错误信息

panic: runtime error: index out of range

我不清楚在你展示的代码中是什么导致了数组越界崩溃,因为代码中没有通过索引访问切片的部分,因此已知的代码不会产生这种崩溃。

ext:

我在网上找到了一个解决方案,[使用 deferrecover]

这种情况确实非常少见。是的,这是一种处理错误的方式,但到目前为止,我总是能够通过其他方式处理错误,从而完全避免程序崩溃的发生。

而且,这种方式与在打印 err 值后立即执行 return 的效果基本相同。因为在目前展示的代码中,checkerror 之后的所有操作都会因为空指针而导致程序崩溃。

@NobbZ 说得对,我也高度怀疑这不是导致问题的代码。这只是一些概念验证。你提供的代码如果不修改一些错误(比如把 fmt.println 改成 fmt.Println,把 check error 改成 checkerror)是无法运行的。

当出现 panic 时,会显示堆栈跟踪信息,最上面一行显示了导致问题的代码行。例如运行以下代码:

package main

import (
	"fmt"
	"net/http"
	"net/url"
)

func main() {
	for i := 0; i < 10; i++ {

		Simplereq()
	}
}

func Simplereq() {
	var s, _ = url.ParseQuery("name=Golberg&email=mymail+ffggff@mail.com&locale=en&terms_accepted=on")

	apiUrl := "https://www.activityinfo.org/signUp"
	resp, err := http.PostForm(apiUrl, s)
	checkerror(err)

	defer resp.Body.Close()
	panic("Ouch!")
	fmt.Println(resp.StatusCode)
}

func checkerror(err error) {
	if err != nil {
		fmt.Println(err)
	}
}

将产生以下输出:

$ go run main.go 
panic: Ouch!

goroutine 1 [running]:
main.Simplereq()
        /Users/xxx/yyy/main.go:24 +0x184
main.main()
        /Users/xxx/yyy/main.go:12 +0x2a
exit status 2

你可以看到导致 panic 的是 main.go 文件的第 24 行。如果 panic 发生在库函数内部,你必须沿着堆栈跟踪向下查找,找到来自你代码的调用。

在Go语言中处理网络请求异常时,通常不会直接中止整个程序,而是通过错误处理机制来优雅地管理异常情况。以下是几种常见的处理方式:

1. 使用错误检查与重试机制

通过检查error返回值,可以决定是否继续执行或进行重试。

package main

import (
    "fmt"
    "net/http"
    "time"
)

func main() {
    urls := []string{
        "https://httpbin.org/get",
        "https://invalid-url",
        "https://httpbin.org/status/200",
    }

    for _, url := range urls {
        err := makeRequest(url)
        if err != nil {
            fmt.Printf("请求 %s 失败: %v\n", url, err)
            // 可以选择继续执行下一个请求
            continue
        }
    }
}

func makeRequest(url string) error {
    client := &http.Client{Timeout: 5 * time.Second}
    resp, err := client.Get(url)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return fmt.Errorf("非200状态码: %d", resp.StatusCode)
    }

    fmt.Printf("请求 %s 成功\n", url)
    return nil
}

2. 使用带重试的请求函数

对于临时性网络故障,可以实现重试逻辑:

func makeRequestWithRetry(url string, maxRetries int) error {
    client := &http.Client{Timeout: 5 * time.Second}
    
    for i := 0; i < maxRetries; i++ {
        resp, err := client.Get(url)
        if err != nil {
            if i == maxRetries-1 {
                return err
            }
            time.Sleep(time.Duration(i+1) * time.Second)
            continue
        }
        defer resp.Body.Close()

        if resp.StatusCode == http.StatusOK {
            fmt.Printf("请求 %s 成功 (第%d次尝试)\n", url, i+1)
            return nil
        }
        
        if i == maxRetries-1 {
            return fmt.Errorf("最终失败,状态码: %d", resp.StatusCode)
        }
        time.Sleep(time.Duration(i+1) * time.Second)
    }
    return nil
}

3. 使用context实现超时控制

通过context可以设置请求超时,避免长时间阻塞:

func makeRequestWithContext(url string, timeout time.Duration) error {
    ctx, cancel := context.WithTimeout(context.Background(), timeout)
    defer cancel()

    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return err
    }

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return fmt.Errorf("状态码错误: %d", resp.StatusCode)
    }

    fmt.Printf("请求 %s 成功\n", url)
    return nil
}

4. 并发请求的错误处理

当使用goroutine并发请求时,可以通过channel收集错误:

func concurrentRequests(urls []string) {
    errCh := make(chan error, len(urls))
    
    for _, url := range urls {
        go func(u string) {
            err := makeRequest(u)
            errCh <- err
        }(url)
    }

    for range urls {
        if err := <-errCh; err != nil {
            fmt.Printf("请求失败: %v\n", err)
        }
    }
}

在Go中,推荐的做法是检查每个可能返回错误的操作,并根据业务需求决定是继续执行、重试还是记录错误。直接调用os.Exit()panic()通常只在遇到不可恢复的错误时使用。

回到顶部