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

