You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go语言TCP服务器ReadString循环阻塞问题排查求助

问题根源

服务器循环阻塞的核心原因是TCP流式协议的特性:bufio.Reader.ReadString('\n')会一直阻塞,直到读取到指定分隔符(这里是\n)或连接关闭并收到EOF。

你的客户端发送完所有文件内容后,并未主动关闭连接写端,而是停在scanner.Scan()等待服务器回复。此时服务器的ReadString既读不到下一个换行符,也收不到EOF,因此会无限阻塞在循环内。另外,你虽然读取了客户端发送的文件大小,但完全没利用这个值控制读取终止条件——老师的服务器应该是通过该文件大小判断数据是否接收完毕,而非依赖换行符或EOF。

修复方案

提供两种可行修复思路,任选其一即可:

方案一:利用文件大小控制读取终止(推荐,更可靠)

既然客户端已发送文件总大小,服务器可统计已读取字节数,达到文件大小时主动退出循环,无需等待EOF。

修改后的server.go关键代码:

package main
import (
    "fmt"
    "bufio"
    "net"
    "os"
    "strconv"
    "strings"
    "io"
)

func check(e error) {
    if e != nil {
        panic(e)
    }
}
func main() {
    fmt.Println("Launching server...")
    ln, err := net.Listen("tcp", ":12008")
    check(err) // 不要忽略Listen的错误
    conn, err := ln.Accept()
    check(err) // 不要忽略Accept的错误
    defer ln.Close()
    defer conn.Close()

    outputFile, errOutput := os.Create("whatever.txt")
    check(errOutput)
    defer outputFile.Close()

    filewriter := bufio.NewWriter(outputFile)
    reader := bufio.NewReader(conn)
    length, errr := reader.ReadString('\n')
    check(errr)
    
    // 解析文件大小,去除换行符
    lengthStr := strings.TrimSpace(length)
    fileSize, err := strconv.ParseInt(lengthStr, 10, 64)
    check(err)
    fmt.Printf("Upload file size: %d bytes\n", fileSize)

    i := 1
    totalRead := int64(0)
    for {
        line, err := reader.ReadString('\n')
        if err != nil {
            // EOF且已读够大小则正常退出,否则抛出错误
            if err == io.EOF && totalRead >= fileSize {
                break
            }
            check(err)
        }
        lineLen := int64(len(line))
        totalRead += lineLen
        // line本身已包含换行符,无需额外添加
        filewriter.WriteString(strconv.Itoa(i) + " " + line)
        i++
        // 已读取字节数达到文件大小,终止循环
        if totalRead >= fileSize {
            break
        }
    }
    filewriter.Flush()
    writer := bufio.NewWriter(conn)
    fileStat,e := outputFile.Stat()
    check(e)
    fmt.Printf("Output file size: %d bytes\n", fileStat.Size())
    newline := fmt.Sprintf("%d bytes received, %d bytes file generated\n", fileSize, fileStat.Size())
    _, errw := writer.WriteString(newline)
    check(errw)
    writer.Flush()
}

方案二:客户端发送完数据后关闭写端

让客户端在发送完所有数据后,主动关闭连接写端(不关闭读端,仍可接收服务器回复),这样服务器会收到EOF,ReadString返回错误从而退出循环。

修改后的client.go关键部分:

writer.Flush()
    // 关闭连接写端,告知服务器数据发送完毕
    if err := conn.CloseWrite(); err != nil {
        check(err)
    }
    scanner := bufio.NewScanner(conn)
    if scanner.Scan() {
        fmt.Printf("Server replies: %s\n", scanner.Text())
    }

同时修复客户端输入问题:原代码fmt.Scanf("%s\n", &inputFileName)会导致输入后需额外换行才能继续,改成fmt.Scanf("%s", &inputFileName)即可。

额外注意事项
  • 不要忽略net.Listen和net.Accept的错误,原代码用_忽略会隐藏潜在问题。
  • 原服务器代码中filewriter.WriteString(strconv.Itoa(i)+" "+line+"\n")会导致每行多一个换行符(line本身是ReadString('\n')读取的,已包含\n),需去掉额外的\n。

内容的提问来源于stack exchange,提问作者susanliu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 16:57:55