GO TCP | 青训营笔记

72 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天。

这篇文章主要记录 GO TCP 编程的实践,总共分成两个部分:

  1. 实现简单的服务端和客户端
  2. 建造一个简单的聊天室

实现简单的服务端和客户端

主要通过 GO 中的 net 包进行 TCP 链接

服务端

服务端通过 net.Listen() 来监听某个端口,第一个参数是协议,第二个参数是 IP 和 端口

net.Listener.Accept() 可以等待并返回下一个客户端的连接

package main

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

func solve(c net.Conn) {
    defer c.Close()
    buf := make([]byte, 1024)
    for {
        n, err := c.Read(buf)
        if err != nil {
            log.Fatal(err)
        }
        if strings.EqualFold(string(buf[:n]), "exit") {
            return
        }
        // 注意长度的限定
        fmt.Println(c.RemoteAddr().String(), ": ", string(buf[:n]))
    }
}

func main() {
    servers, err := net.Listen("tcp", "127.0.0.1:8080")
    if err != nil {
        log.Fatal(nil)
    }
    fmt.Println("服务器启动")
    defer servers.Close()
    for {
        work, err := servers.Accept()
        if err != nil {
            log.Fatal(nil)
        }
        fmt.Println(work.RemoteAddr().String(), "成功连接至服务器")
        go solve(work)
    }
}

客户端

客户端通过 bufio 包,读入终端输入

通过 net.Dial() 对某个 IP 端口建立连接,参数与 net.Listen() 一致

package main

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

func main() {
    rd := bufio.NewReader(os.Stdin)
    // fmt.Println(string(buf))
    conn, err := net.Dial("tcp", "127.0.0.1:8080")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("成功连接服务器", conn.RemoteAddr().String())
    for {
        fmt.Println("请输入:")
        buf, err := rd.ReadString('\n')
        if err != nil {
            log.Fatal(err)
        }
        buf = strings.Trim(buf, "\n\r ")
        conn.Write([]byte(buf))
    }
}

结果展示

图片.png

建造一个简单的聊天室

服务端

整体上用一个 map[string]net.Conn 来储存已经和服务端进行 TCP 连接的客户端,keyIP 和 端口

每当有用户发送聊天信息的时候,服务端通过 sentMesGroup() 将信息发送给其他在聊天室的用户

在客户端选择断开后,继续进行读入操作时,会返回一个 EOF 错误。当出现 EOF 错误时,将当前的客户端从 map[string]net.Conn 中删除

package main

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

var UserTotal map[string]net.Conn = make(map[string]net.Conn, 0)

type Message struct {
    MesConn net.Conn
    data    string
}

func process(conn net.Conn) {
    defer conn.Close()
    defer delete(UserTotal, conn.RemoteAddr().String())
    for {
        buf := make([]byte, 1024)
        n, err := conn.Read(buf)
        if err == io.EOF {
            break
        }
        if err != nil {
            fmt.Println("用户 ", conn.RemoteAddr(), " 传输异常,断开连接")
            fmt.Println("错误:", err)
            break
        }
        now := Message{data: string(buf[:n]), MesConn: conn}
        now.sentMesGroup()

    }
}

func (this Message) sentMesGroup() {
    for key, User := range UserTotal {
        if key == this.MesConn.RemoteAddr().String() {
            continue
        }
        _, err := User.Write([]byte(fmt.Sprintf("用户 %s 发送消息:%s", this.MesConn.RemoteAddr().String(), this.data)))
        if err != nil {
            log.Fatal(err)
        }
    }
}

func enterInfo(conn net.Conn) {
    for _, User := range UserTotal {
        _, err := User.Write([]byte(fmt.Sprintf("用户 %s 进入聊天室", conn.RemoteAddr().String())))
        if err != nil {
            log.Fatal(err)
        }
    }
}

func main() {
    ser, err := net.Listen("tcp", "127.0.0.1:8080")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("聊天室开启")
    defer ser.Close()
    for {
        conn, err := ser.Accept()
        if err != nil {
            log.Fatal(err, 111111)
        }
        UserTotal[conn.RemoteAddr().String()] = conn
        enterInfo(conn)
        go process(conn)
    }
}

客户端

客户端除了发送信息以外,还创建了一个 goroutine 来接收信息

当输入的信息为 exit 时,会直接退出聊天室

package main

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

func input(conn net.Conn) {
    buf := make([]byte, 1024)
    for {
        n, err := conn.Read(buf)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println(string(buf[:n]))
    }
}

func main() {
    conn, err := net.Dial("tcp", "127.0.0.1:8080")
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()
    fmt.Println("成功进入聊天室")
    rd := bufio.NewReader(os.Stdin)
    go input(conn)
    for {
        buf, err := rd.ReadString('\n')
        if err != nil {
            log.Fatal(err)
        }
        buf = strings.Trim(buf, "\n\r ")
        if strings.EqualFold(buf, "exit") {
            break
        }
        _, err = conn.Write([]byte(buf))
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println("成功输入内容:", buf)
    }
}

结果展示

图片.png