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

Go TCP简易聊天服务器中partner channel的连接传递逻辑疑问

嘿,我来帮你把这段TCP聊天服务器里的partner channel逻辑掰明白~首先我先把你没写完的代码补全(这是这类配对聊天服务器的标准实现),方便咱们对照着讲:

package main

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

const listenAddr = "localhost:4000"

func main() {
	listener, err := net.Listen("tcp", listenAddr)
	if err != nil {
		log.Fatalf("Failed to listen: %v", err)
	}
	log.Printf("Listening on %s", listenAddr)

	// 这就是你说的"partner channel",用来给连接做配对的"中间人"
	partnerChan := make(chan net.Conn)

	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Printf("Failed to accept connection: %v", err)
			continue
		}
		// 每个新连接单独开一个goroutine处理,避免阻塞主循环
		go handleConnection(conn, partnerChan)
	}
}

func handleConnection(conn net.Conn, partnerChan chan net.Conn) {
	defer conn.Close()

	select {
	// 尝试把当前连接发送到channel,找配对
	case partnerChan <- conn:
		// 走到这里说明:我是第一个来的,现在得等着被别人"领走"
		log.Printf("User %s waiting for partner", conn.RemoteAddr())
		// 这里会阻塞,直到第二个用户把它的连接发过来
		partner := <-partnerChan
		defer partner.Close()
		log.Printf("Paired %s with %s", conn.RemoteAddr(), partner.RemoteAddr())
		// 双向转发消息,同时开两个goroutine实现全双工聊天
		go io.Copy(conn, partner)
		io.Copy(partner, conn)

	// 尝试从channel里捞一个正在等配对的连接
	case partner := <-partnerChan:
		// 走到这里说明:我是第二个来的,刚好捡到了第一个用户的连接
		log.Printf("User %s found partner %s", conn.RemoteAddr(), partner.RemoteAddr())
		// 把自己的连接发回给第一个用户,让它也拿到我的连接
		partnerChan <- conn
		defer partner.Close()
		// 同样双向转发消息
		go io.Copy(conn, partner)
		io.Copy(partner, conn)
	}
}

接下来针对你的疑问逐个拆解:

1. 第一个用户连接时,select在等什么?

当第一个用户的handleConnection goroutine启动后,它进入select语句:

  • 第一个case partnerChan <- conn:因为partnerChan是无缓冲channel,发送操作必须等有接收者才能完成,所以这个发送会卡住。
  • 第二个case partner := <-partnerChan:此时channel是空的,接收操作也会卡住。

所以整个select会停在这里,直到有第二个连接进来,打破这个阻塞。

2. 第二个用户连接时,代码怎么通过channel传递连接?

当第二个用户的handleConnection goroutine启动后,同样进入select:

  • 此时第一个用户的连接正卡在partnerChan <- conn的发送操作里(等着被接收)。
  • 第二个goroutine的第二个case partner := <-partnerChan:现在channel里有一个“待接收”的连接,所以这个case会被立即触发,partner变量就拿到了第一个用户的连接。
  • 接着第二个goroutine执行partnerChan <- conn,把自己的连接发送到channel。这时候第一个goroutine的partnerChan <- conn发送操作终于有了接收者(第二个goroutine刚才接收了它),所以第一个goroutine会继续往下走,执行partner := <-partnerChan,就拿到了第二个用户的连接。

到这里,两个用户的goroutine都拿到了对方的连接,就可以通过io.Copy互相转发消息,实现聊天了。

3. select是怎么确定执行哪个case的?

Go的select有个很明确的规则:

  • 如果多个case都能立即执行(没有阻塞),会随机选一个执行。
  • 如果只有一个case能执行,就执行那个case。
  • 如果所有case都阻塞,就一直等,直到有一个case能执行。

在这个场景里:

  • 第一个用户进来时,两个case都阻塞,所以select等待。
  • 第二个用户进来时,它的接收case能立即执行(因为第一个用户在等着被接收),所以会选中这个case;同时第一个用户的发送case因为有了接收者,也会完成,继续执行后续逻辑。

核心逻辑总结

这个partner channel本质就是一个无缓冲的配对“中转站”:

  • 第一个连接进来,变成“等待配对”的对象,卡在发送操作。
  • 第二个连接进来,“取走”等待的对象,再把自己放进去,完成双向配对。
  • 配对完成后,两个连接就可以互相转发数据,实现一对一聊天。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 10:00:24