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
相关产品推荐
相关产品推荐

