Go 实现群聊天室- goroutine 和 channel 应用
经典的多线程程序群聊聊天室,利用goroutine和channel来实现。
实现这段程序,对理解go的goroutine和channel也非常有帮助。
服务端代码:
package main
import (
  "net"
  "fmt"
  "os"
  "bufio"
)
func main() {
  listener, err := net.Listen("tcp", "localhost:8098")
  if err != nil {
    fmt.Fprintf(os.Stderr, "%v\n", err)
  }
  go broadcaster()
  for {
    conn , err := listener.Accept()
    if err != nil {
      fmt.Fprintf(os.Stderr, "%v\n", err)
    }
    go handleConn(conn)
  }
}
type client chan<- string
var (
  entering = make(chan client)
  leaving = make(chan client)
  message = make(chan string)
)
func broadcaster(){
  clients := make(map[client]bool)
  for {
    select {
      // 消息写到cli 注意这个cli是entering里的chan
      // 而这个chan是 handleConn()里的ch
      // 这就把msg通过clientWriter()写到了conn连接里
      case msg := <-message :
        for cli := range clients{
          cli <- msg
        }
      case cli := <- entering :
        clients[cli] = true
      case cli := <-leaving :
        delete(clients, cli)
        close(cli)
    }
  }
}
func handleConn(conn net.Conn){
  // 无缓冲通道
  ch := make(chan string)
  //
  go clientWriter(conn, ch)
  who := conn.RemoteAddr().String()
  // 该消息通过clientWriter() 输出
  ch <- "You are " + who
  // 这条消息发给已经在entering的客户端里,不会发给刚进来的。
  // 因为当前ch还没写到entering
  message <- who + " has arrived"
  // 此时才把当前ch放到entering
  // 注意ch是作为通道类型放到entering的通道里,区别于msg
  entering <- ch
  input := bufio.NewScanner(conn)
  for input.Scan() {
    message <- who + " says: " + input.Text()
  }
  // for循环结束 表示退出
  leaving <- ch
  message <- who + " has left"
  conn.Close()
}
/**
文案消息写到连接
 */
func clientWriter(conn net.Conn, ch <-chan string){
  for msg := range ch {
    fmt.Fprintln(conn, msg)
  }
}
客户端代码:
package main
import (
  "fmt"
  "io"
  "net"
  "os"
)
func main() {
  conn, err := net.Dial("tcp", "localhost:8098")
  if err != nil {
    fmt.Println("dial error")
  }
  go func() {
    // 把连接返回值打印到屏幕
    io.Copy(os.Stdout, conn)
  }()
  // 把标准输入描述符重定向到连接里
  if _, err := io.Copy(conn, os.Stdin); err != nil {
    fmt.Println("copy error")
  }
  fmt.Printf("%v conn", conn)
  conn.Close()
}