Golang中TCPConn类型的closeWrite方法关闭了双向连接而非单向连接

Golang中TCPConn类型的closeWrite方法关闭了双向连接而非单向连接 我正在阅读《Go程序设计语言》这本书,并在网上寻求帮助后,为练习8.3编写了代码。当我需要创建一个客户端,使其在按下Ctrl+D时关闭输入,并等待服务器写入其响应后再退出程序时,按下Ctrl+D后它并没有等待服务器。

以下是我的代码:

服务器代码如下:

package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
	"strings"
	"time"
)

func main() {
	listener, err := net.Listen("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Print(err)
			continue
		}
		go handleConn(conn)
	}
}
func echo(conn net.Conn, shout string, delay time.Duration){
	fmt.Fprintln(conn, "\t", strings.ToUpper(shout))
	time.Sleep(delay)
	fmt.Fprintln(conn, "\t", shout)
	time.Sleep(delay)
	fmt.Fprintln(conn, "\t", strings.ToLower(shout))

}

func handleConn(conn net.Conn) {
	input := bufio.NewScanner(conn)
	for input.Scan() {
		go echo(conn, input.Text(), 1*time.Second)
	}
	conn.Close()
}

客户端代码如下:

package main

import (
	"io"
	"log"
	"net"
	"os"
)

func main() {
	addr, err := net.ResolveTCPAddr("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	conn, err := net.DialTCP("tcp", nil, addr)
	if err != nil {
		log.Fatal(err)
	}
	done := make(chan struct{})
	go func() {
		io.Copy(os.Stdout, conn)
		log.Print("done")
		done<- struct{}{}
	}()
	mustCopy(conn, os.Stdin)
	err = conn.CloseWrite()
	if err != nil {
		log.Fatal(err)
	}
	<-done

}

func mustCopy(dst io.Writer, src io.Reader){
	_, err := io.Copy(dst, src)
	if err != nil {
		log.Fatal(err)
	}
}

以下是控制台输入和输出:

A A A a

B B B

A A b A a

A A

CTRL + D 2021/05/16 04:30:29 done

我使用的是Linux系统。我真的尝试过了,但我不明白为什么按下Ctrl+D后,它没有等待echo操作完成。


更多关于Golang中TCPConn类型的closeWrite方法关闭了双向连接而非单向连接的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中TCPConn类型的closeWrite方法关闭了双向连接而非单向连接的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


根据你的代码和描述,问题在于对CloseWrite()方法的理解。CloseWrite()方法确实只关闭了写入方向的连接,但服务器端的bufio.Scanner在检测到EOF时会立即退出循环,导致连接被完全关闭。

以下是关键问题的分析:

  1. 服务器端bufio.NewScanner(conn)会持续读取数据直到遇到EOF。当客户端调用CloseWrite()后,服务器端会收到EOF,导致input.Scan()返回false,从而退出循环并执行conn.Close()

  2. 客户端:调用CloseWrite()后,虽然只关闭了写入方向,但服务器端已经关闭了整个连接,所以io.Copy(os.Stdout, conn)会立即读取到EOF并退出。

要解决这个问题,需要修改服务器端代码,使其在读取到EOF后继续处理未完成的echo协程。以下是修改后的服务器代码示例:

package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
	"strings"
	"sync"
	"time"
)

func main() {
	listener, err := net.Listen("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Print(err)
			continue
		}
		go handleConn(conn)
	}
}

func echo(conn net.Conn, shout string, delay time.Duration, wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Fprintln(conn, "\t", strings.ToUpper(shout))
	time.Sleep(delay)
	fmt.Fprintln(conn, "\t", shout)
	time.Sleep(delay)
	fmt.Fprintln(conn, "\t", strings.ToLower(shout))
}

func handleConn(conn net.Conn) {
	var wg sync.WaitGroup
	input := bufio.NewScanner(conn)
	
	for input.Scan() {
		wg.Add(1)
		go echo(conn, input.Text(), 1*time.Second, &wg)
	}
	
	// 等待所有echo协程完成
	wg.Wait()
	conn.Close()
}

主要修改:

  1. 使用sync.WaitGroup来跟踪所有echo协程
  2. handleConn函数中,读取循环结束后调用wg.Wait()等待所有echo协程完成
  3. 在所有echo协程完成后才关闭连接

这样修改后,当客户端调用CloseWrite()时,服务器端会等待所有已启动的echo协程完成后再关闭连接,客户端就能收到完整的响应了。

回到顶部