Golang读取TCP套接字时卡住怎么办

Golang读取TCP套接字时卡住怎么办 大家好,我对Go语言以及套接字的底层细节都非常不熟悉。希望大家能帮助我。

我有一个在计算机集群上运行的客户端-服务器应用程序。服务器运行在任意一个系统上,它会连接到多个进程,并在应用程序的整个生命周期内保持这些连接处于打开状态。每当客户端连接到服务器时,服务器会从该连接读取一行数据。这个过程在连接到130个客户端进程时运行顺畅。但当有131个客户端进程时,大多数情况下都会失败。服务器会卡在尝试读取那一行数据的地方。相关代码如下所示。

服务器

func main() {
// 一些代码
	port := 8080
	host := "0.0.0.0"

	ln, err := net.Listen("tcp", fmt.Sprintf("%s:%d", host, port))
	utils.CheckError(err)
	log.Printf("Server running on port %d\n", port)
	for {
		conn, err := ln.Accept()
		utils.CheckError(err)
		handleConnection(conn)
	}
// 其他代码
}

func handleConnection(c net.Conn) {
	status, err := bufio.NewReader(c).ReadString('\n') // 服务器就是在这里卡住了!!!
	utils.CheckError(err)
// 其他代码
}

客户端

func main() {
// 一些代码
	conn, err := net.Dial("tcp", os.Args[1])
	utils.CheckError(err)
	filename := os.Args[2]
// 一些代码
	f, err := os.Open(pdFilename)
	utils.CheckError(err)
	line, err := bufio.NewReader(f).ReadString('\n')
	utils.CheckError(err)
	fmt.Fprintf(conn, "%s", line)
// 其他代码
}

更多关于Golang读取TCP套接字时卡住怎么办的实战教程也可以访问 https://www.itying.com/category-94-b0.html

10 回复

该示例仅在 goroutine 中处理单个请求,以便更快地响应请求。我不认为串行处理会导致我遇到的这种行为。

更多关于Golang读取TCP套接字时卡住怎么办的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


@samvidmistry 别忘了关闭连接 conn.close()。 同时要确保客户端确实在发送数据——你是否监控了客户端和服务器端的所有错误(如果有的话)?

在服务器编程中,实际上并不存在任何串行操作(即使看起来像),而是并发的,因为无法保证请求会按顺序发生。因此,在您未来的所有开发中都必须考虑到这一点。

正如以下示例所示,在服务器端的并发环境中,您应该使用一个 goroutine 来处理结果。

https://golang.org/src/net/example_test.go

感谢您的帮助。问题出在客户端。它卡在了其他一些代码中,无法向服务器发送数据,因此服务器也卡住了。这个错误非常严重,我甚至不知道程序是如何在两个进程下运行的,更不用说130个了。再次重申,一个串行接收的服务器本身应该不是问题。另外,我该如何关闭这个问题?

你好,Samvid,也许可以尝试添加类似这样的代码:

import "runtime/debug"
/* ... */
func main() {
    /* ... */
    go func() {
        time.Sleep(5 * time.Second)
        debug.PrintStack()
    }()
    /* ... */
}

我认为这应该会打印出 goroutine 的堆栈跟踪信息,这样你就能看到它们在做什么了。

好的,感谢你的专业建议。我确实在 handleConnection 函数后面启动了一个 goroutine。我原本希望能了解一下这段代码可能有什么问题。我认为当进程数量超过130个时,串行处理请求并不是导致代码行为异常的原因。我所说的串行,并不是指任何特定的顺序。我知道请求可以以任何顺序到达。但我是一个接一个地处理它们的。

我无法理解为什么在大约130个请求时会发生这种情况,但这并不是重点。基本上,并发意味着同时运行。因此,如果两个或更多请求几乎在同一时间触及 ln.Accept(),那么在接下来的时刻,你必须为每个请求跳转到一个新的goroutine来处理结果。如果没有这些goroutine,你将会有多个请求同时调用相同的代码,导致不可预测的结果。

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

如果没有这些 goroutine,同一时间许多请求将调用相同的代码,导致不可预测的结果。

这种说法不正确。非并发的 TCP 服务器并非坏事,它应该可以正常工作。是的,这有缺点,但它不会产生“不可预测的结果”。如果因为服务器无法跟上负载而导致太多客户端在等待 .Accept(),那么它们可能会被取消。

请再次检查所有返回的错误,并确保 utils.CheckError(err) 在出现错误时能让进程失败。别忘了关闭所有连接。

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

问题出在服务器没有为每个连接启动独立的goroutine,导致并发处理能力受限。当连接数超过一定数量时,会阻塞在单个连接的读取操作上。

解决方案是使用goroutine并发处理连接:

func handleConnection(c net.Conn) {
    go func(conn net.Conn) {
        defer conn.Close()
        status, err := bufio.NewReader(conn).ReadString('\n')
        if err != nil {
            log.Printf("Read error: %v", err)
            return
        }
        // 处理数据
        fmt.Printf("Received: %s", status)
    }(c)
}

更完整的服务器示例:

func main() {
    port := 8080
    host := "0.0.0.0"

    ln, err := net.Listen("tcp", fmt.Sprintf("%s:%d", host, port))
    utils.CheckError(err)
    defer ln.Close()
    
    log.Printf("Server running on port %d\n", port)
    
    for {
        conn, err := ln.Accept()
        if err != nil {
            log.Printf("Accept error: %v", err)
            continue
        }
        go handleConnection(conn)
    }
}

func handleConnection(conn net.Conn) {
    defer conn.Close()
    
    reader := bufio.NewReader(conn)
    for {
        status, err := reader.ReadString('\n')
        if err != nil {
            if err != io.EOF {
                log.Printf("Read error: %v", err)
            }
            return
        }
        // 处理接收到的数据
        fmt.Printf("Received: %s", status)
    }
}

客户端也需要确保正确关闭连接:

func main() {
    conn, err := net.Dial("tcp", os.Args[1])
    utils.CheckError(err)
    defer conn.Close()
    
    filename := os.Args[2]
    f, err := os.Open(filename)
    utils.CheckError(err)
    defer f.Close()
    
    line, err := bufio.NewReader(f).ReadString('\n')
    utils.CheckError(err)
    
    _, err = fmt.Fprintf(conn, "%s", line)
    utils.CheckError(err)
}

关键点:

  1. 每个连接使用独立的goroutine处理
  2. 确保连接在使用后正确关闭
  3. 添加错误处理避免goroutine泄漏
  4. 使用循环读取以处理多个消息
回到顶部