用Golang实现 简单TCP长连接 服务器/客户端

用Golang实现 文本广播式聊天服务器/客户端

本节, 我们将一步一步的把上一节完成的echo服务器/客户端改造成一个文本信息的聊天室

服务端的改动

  1. 服务器为了实现聊天信息的群体广播, 需要记录所有连接到服务器的客户端信息, 所以, 我们需要添加一个集合来保存所有客户端的连接:

    var ConnMap map[string]*net.TCPConn

  2. 接着, 每次当有新的客户端连接到服务器时, 需要把这个客户端连接行信息加入集合:

    ConnMap[tcpConn.RemoteAddr().String()] = tcpConn

  3. 当服务器收到客户端的聊天信息时, 需要广播到所有客户端, 所以我们需要利用上面保存TCPConn的map来遍历所有TCPConn进行广播, 用以下方法实现:

     func boradcastMessage(message string) {
         b := []byte(message)
         for _, conn := range ConnMap {
         conn.Write(b)
         }
     }
    

客户端代码改动

客户端代码改动相对简单, 只是加入了用户自己输入聊天信息的功能, 在连接成功并且 启动了消息接收的gorountine后, 加入以下代码:

    for {
        var msg string
        fmt.Scanln(&msg)
        if msg == "quit" {
            break
        }
        b := []byte(msg + "\n")
        conn.Write(b)
    }

完整的服务端代码如下:

server.go

package main

import (
    "bufio"
    "fmt"
    "net"
)

// 用来记录所有的客户端连接
var ConnMap map[string]*net.TCPConn

func main() {
    var tcpAddr *net.TCPAddr
    ConnMap = make(map[string]*net.TCPConn)
    tcpAddr, _ = net.ResolveTCPAddr("tcp", "127.0.0.1:9999")

    tcpListener, _ := net.ListenTCP("tcp", tcpAddr)

    defer tcpListener.Close()

    for {
        tcpConn, err := tcpListener.AcceptTCP()
        if err != nil {
            continue
        }

        fmt.Println("A client connected : " + tcpConn.RemoteAddr().String())
        // 新连接加入map
        ConnMap[tcpConn.RemoteAddr().String()] = tcpConn
        go tcpPipe(tcpConn)
    }

}

func tcpPipe(conn *net.TCPConn) {
    ipStr := conn.RemoteAddr().String()
    defer func() {
        fmt.Println("disconnected :" + ipStr)
        conn.Close()
    }()
    reader := bufio.NewReader(conn)

    for {
        message, err := reader.ReadString('\n')
        if err != nil {
            return
        }
        fmt.Println(conn.RemoteAddr().String() + ":" + string(message))
        // 这里返回消息改为了广播
        boradcastMessage(conn.RemoteAddr().String() + ":" + string(message))
    }
}


func boradcastMessage(message string) {
    b := []byte(message)
    // 遍历所有客户端并发送消息
    for _, conn := range ConnMap {
        conn.Write(b)
    }
}

客户端完整代码如下:

client.go

package main

import (
    "bufio"
    "fmt"
    "net"
)

func main() {
    var tcpAddr *net.TCPAddr
    tcpAddr, _ = net.ResolveTCPAddr("tcp", "127.0.0.1:9999")

    conn, _ := net.DialTCP("tcp", nil, tcpAddr)
    defer conn.Close()
    fmt.Println("connected!")

    go onMessageRecived(conn)

    // 控制台聊天功能加入
    for {
        var msg string
        fmt.Scanln(&msg)
        if msg == "quit" {
            break
        }
        b := []byte(msg + "\n")
        conn.Write(b)
    }
}

func onMessageRecived(conn *net.TCPConn) {
    reader := bufio.NewReader(conn)
    for {
        msg, err := reader.ReadString('\n')
        fmt.Println(msg)
        if err != nil {
            quitSemaphore <- true
            break
        }
    }
}

最后分别编译server与client试试效果吧!

go build server.go

go build client.go

先启动server端, 然后新开两个个终端, 启动客户端, 在其中一个客户端里键入聊天信息后回车, 会发现另外一个客户端收到了刚刚发送的聊下天信息

完事大吉!