首页 > 其他分享 >【Golang 快速入门】项目实战:即时通信系统

【Golang 快速入门】项目实战:即时通信系统

时间:2022-12-22 10:34:36浏览次数:55  
标签:入门 err fmt 即时 server Golang client user conn

 

Golang 快速入门

 

Golang 快速入门:

学习视频:8 小时转职 Golang 工程师,这门课很适合有一定开发经验的小伙伴,强推!
完整代码:https://gitee.com/szluyu99/golang-study

即时通信系统 - 服务端

项目架构图

版本迭代:

  • 版本一:构建基础 Server
  • 版本二:用户上线功能
  • 版本三:用户消息广播机制
  • 版本四:用户业务层封装
  • 版本五:在线用户查询
  • 版本六:修改用户名
  • 版本七:超时强踢功能
  • 版本八:私聊功能
  • 版本九:客户端实现

版本一:构建基础 Server

server.go,其中包含以下内容:

  • 定义 Server 结构体,包含 IP、Port 字段
  • NewServer(ip string, port int) 创建 Server 对象的方法
  • (s *Server) Start() 启动 Server 服务的方法
  • (s *Server) Handler(conn net.Conn) 处理连接业务
package main

import (
    "fmt"
    "net"
)

type Server struct {
    Ip   string
    Port int
}

// 创建一个server的接口
func NewServer(ip string, port int) *Server {
    server := &Server{
        Ip:   ip,
        Port: port,
    }
    return server
}

func (s *Server) Handler(conn net.Conn) {
    // 当前连接的业务
    fmt.Println("连接建立成功!")
}

// 启动服务器的接口
func (s *Server) Start() {
    // socket listen
    listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Ip, s.Port))
    if err != nil {
        fmt.Println("net.Listen err: ", err)
        return
    }
    // close listen socket
    defer listener.Close()

    for {
        // accpet
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("listener accept err: ", err)
            continue
        }
        // do handler
        go s.Handler(conn)
    }

}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

main.go,启动我们编写的 Server:

package main

func main() {
    server := NewServer("127.0.0.1", 8888)
    server.Start()
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

以下命令都是在 Linux 或 macOS 下运行,Windows 略有不同

同时编译编写的两个文件:go build -o server main.go server.go

然后运行编译出的文件:./server

使用命令侦听我们构建的服务:nc 127.0.0.1 8888

版本二:用户上线功能

user.go:

  • NewUser(conn net.Conn) *User 创建一个 user 对象
  • (u *User) ListenMessage() 监听 user 对应的 channel 消息
type User struct {
    Name string
    Addr string
    C    chan string
    conn net.Conn
}

// 创建一个用户的API
func NewUser(conn net.Conn) *User {
    userAddr := conn.RemoteAddr().String()

    user := &User{
        Name: userAddr,
        Addr: userAddr,
        C:    make(chan string),
        conn: conn,
    }

    // 启动监听当前user channel消息的goroutine
    go user.ListenMessage()

    return user
}

// 监听当前user channel的方法,一旦有消息,直接发送给客户端
func (u *User) ListenMessage() {
    for {
        msg := <-u.C
        u.conn.Write([]byte(msg + "\n"))
    }
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

Server.go:

  • 新增 OnlineMap 和 Message 属性
  • 在处理客户端上线的 Handler 创建并添加用户
  • 新增广播消息方法
  • 新增监听广播消息 channel 方法
  • 用一个 goroutine 单独监听 Message
type Server struct {
    Ip   string
    Port int

    // 在线用户的列表
    OnlineMap map[string]*User
    mapLock   sync.RWMutex

    // 消息广播的channel
    Message chan string
}

// 创建一个server的接口
func NewServer(ip string, port int) *Server {
    server := &Server{
        Ip:        ip,
        Port:      port,
        OnlineMap: make(map[string]*User),
        Message:   make(chan string),
    }
    return server
}

// 监听Message广播消息的channel的goroutine,一旦有消息就发送给全部的在线user
func (s *Server) ListenMessager() {
    for {
        msg := <-s.Message
        // 将msg发送给全部的在线user
        s.mapLock.Lock()
        for _, cli := range s.OnlineMap {
            cli.C <- msg
        }
        s.mapLock.Unlock()
    }
}

// 广播消息的方法
func (s *Server) BroadCast(user *User, msg string) {
    sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg

    s.Message <- sendMsg
}

func (s *Server) Handler(conn net.Conn) {
    // 当前连接的业务
    // fmt.Println("连接建立成功!")

    user := NewUser(conn)

    // 用户上线,将用户加入到onlineMap中
    s.mapLock.Lock()
    s.OnlineMap[user.Name] = user
    s.mapLock.Unlock()

    // 广播当前用户上线消息
    s.BroadCast(user, "已上线")

    // 当前handler阻塞
    select {}
}

// 启动服务器的接口
func (s *Server) Start() {
    // socket listen
    listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Ip, s.Port))
    if err != nil {
        fmt.Println("net.Listen err: ", err)
        return
    }
    // close listen socket
    defer listener.Close()

    // 启动监控Message的goroutine
    go s.ListenMessager()

    for {
        // accpet
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("listener accept err: ", err)
            continue
        }
        // do handler
        go s.Handler(conn)
    }

}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87

学习到的编程思路:

  • 结构体中的 channel 基本都需要开个循环去监听其变化(尝试取出值,发送给其他 channel)

版本三:用户消息广播机制

server.go:完善 handle 处理业务方法,启动一个针对当前客户端的读 routine

版本四:用户业务层封装

user.go:

  • user 类型新增 server 关联
  • 新增 Online、Offline、DoMessage 方法
type User struct {
    Name string
    Addr string
    C    chan string
    conn net.Conn

    server *Server
}

// 创建一个用户的API
func NewUser(conn net.Conn, server *Server) *User {
    userAddr := conn.RemoteAddr().String()

    user := &User{
        Name:   userAddr,
        Addr:   userAddr,
        C:      make(chan string),
        conn:   conn,
        server: server,
    }

    // 启动监听当前user channel消息的goroutine
    go user.ListenMessage()

    return user
}

// 用户的上线业务
func (u *User) Online() {
    // 用户上线,将用户加到onlineMap中
    u.server.mapLock.Lock()
    u.server.OnlineMap[u.Name] = u
    u.server.mapLock.Unlock()

    // 广播当前用户上线消息
    u.server.BroadCast(u, "已上线")
}

// 用户的下线业务
func (u *User) Offline() {
    // 用户下线,将用户从onlineMap中删除
    u.server.mapLock.Lock()
    delete(u.server.OnlineMap, u.Name)
    u.server.mapLock.Unlock()

    // 广播当前用户下线消息
    u.server.BroadCast(u, "已下线")
}

// 用户处理消息的业务
func (u *User) DoMessage(msg string) {
    u.server.BroadCast(u, msg)
}

// 监听当前user channel的方法,一旦有消息,直接发送给客户端
func (u *User) ListenMessage() {
    for {
        msg := <-u.C
        u.conn.Write([]byte(msg + "\n"))
    }
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

server.go:

  • 使用 user 封装好的业务替换之前的代码
func (s *Server) Handler(conn net.Conn) {
    // 当前连接的业务
    // fmt.Println("连接建立成功!")

    user := NewUser(conn, s)

  // 用户上线
    user.Online()

    // 接受客户端发送的消息
    go func() {
        buf := make([]byte, 4096)
        for {
            n, err := conn.Read(buf)
            if n == 0 {
        // 用户下线
                user.Offline()
                return
            }
            if err != nil && err != io.EOF {
                fmt.Println("Conn Read err:", err)
                return
            }

            // 提取用户的消息(去除'\n')
            msg := string(buf[:n-1])

            // 将得到的消息进行广播
            user.DoMessage(msg)
        }
    }()

    // 当前handler阻塞
    select {}
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

版本五:在线用户查询

若某个用户输入的消息为 who 则查询当前在线用户列表。

user.go:

  • 提供 SendMsg 向对象客户端发送消息 API
func (u *User) SendMsg(msg string) {
    u.conn.Write([]byte(msg))
}
 
  • 1
  • 2
  • 3
  • 在 DoMessage() 方法中,加上对 “who” 指令的处理,返回在线用户信息
func (u *User) DoMessage(msg string) {
    if msg == "who" {
        // 查询当前在线用户都有哪些
        u.server.mapLock.Lock()
        for _, user := range u.server.OnlineMap {
            onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "在线...\n"
            u.SendMsg(onlineMsg)
        }
        u.server.mapLock.Unlock()
    } else {
        u.server.BroadCast(u, msg)
    }
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

版本六:修改用户名

若某个用户输入的消息为 rename张三 则将自己的 Name 修改为张三。

user.go:

  • 在 DoMessage() 方法中,加上对 “rename|张三” 指令的处理
func (u *User) DoMessage(msg string) {
    if msg == "who" {
        // 查询当前在线用户都有哪些
        u.server.mapLock.Lock()
        for _, user := range u.server.OnlineMap {
            onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "在线...\n"
            u.SendMsg(onlineMsg)
        }
        u.server.mapLock.Unlock()
    } else if len(msg) > 7 && msg[:7] == "rename|" {
        // 消息格式:rename|张三
        newName := strings.Split(msg, "|")[1]
        // 判断name是否存在
        _, ok := u.server.OnlineMap[newName]
        if ok {
            u.SendMsg("当前用户名被使用\n")
        } else {
            u.server.mapLock.Lock()
            delete(u.server.OnlineMap, newName)
            u.server.OnlineMap[newName] = u
            u.server.mapLock.Unlock()

            u.Name = newName
            u.SendMsg("您已经更新用户名:" + u.Name + "\n")
        }
    } else {
        u.server.BroadCast(u, msg)
    }
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

版本七:超时强推功能

用户的任意消息表示用户为活跃,长实践不发消息认为超时,就才一强制关闭用户连接。

server.go:

  • 在用户 Handler() goroutine 中,添加活跃用户 channel,一旦用户有消息,就向该 channel 发送数据
func (s *Server) Handler(conn net.Conn) {
    // 当前连接的业务
    // fmt.Println("连接建立成功!")

    user := NewUser(conn, s)

    user.Online()

    // 监听用户是否活跃的channel
    isLive := make(chan bool)

    // 接受客户端发送的消息
    go func() {
        buf := make([]byte, 4096)
        for {
            n, err := conn.Read(buf)
            if n == 0 {
                user.Offline()
                return
            }
            if err != nil && err != io.EOF {
                fmt.Println("Conn Read err:", err)
                return
            }

            // 提取用户的消息(去除'\n')
            msg := string(buf[:n-1])

            // 用户针对msg进行消息处理
            user.DoMessage(msg)

            // 用户的任意消息,代表当前用户是活跃状态
            isLive <- true
        }
    }()

    // 当前handler阻塞
    for {
        select {
        case <-isLive:
            // 当前用户是活跃的,应该重置定时器
            // 不做任何事情,为了激活select,更新下面的定时器
        case <-time.After(time.Second * 10): // 10s后触发定时器
            // 已经超时
            // 将当前的user强制关闭
            user.SendMsg("你被踢了。")

            // 销毁资源
            close(user.C)

            // 关闭连接
            conn.Close()

            // 退出当前Handler
            // runtime.Goexit()
            return
        }
    }
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

版本八:私聊功能

消息格式:to|张三|你好啊,我是...

user.go,在 DoMessage() 方法中,加上对 “to|张三|你好啊” 指令的处理:

func (this *User) DoMessage(msg string) {
    if msg == "who" {
        //查询当前在线用户都有哪些

        this.server.mapLock.Lock()
        for _, user := range this.server.OnlineMap {
            onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "在线...\n"
            this.SendMsg(onlineMsg)
        }
        this.server.mapLock.Unlock()

    } else if len(msg) > 7 && msg[:7] == "rename|" {
        //消息格式: rename|张三
        newName := strings.Split(msg, "|")[1]

        //判断name是否存在
        _, ok := this.server.OnlineMap[newName]
        if ok {
            this.SendMsg("当前用户名被使用\n")
        } else {
            this.server.mapLock.Lock()
            delete(this.server.OnlineMap, this.Name)
            this.server.OnlineMap[newName] = this
            this.server.mapLock.Unlock()

            this.Name = newName
            this.SendMsg("您已经更新用户名:" + this.Name + "\n")
        }

    } else {
        this.server.BroadCast(this, msg)
    }
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

即时通信系统 - 客户端

以下代码都是在 client.go 文件中

客户端类型定义与链接

client.go:

type Client struct {
    ServerIp   string
    ServerPort int
    Name       string
    conn       net.Conn
}

func NewClient(serverIp string, serverPort int) *Client {
    // 创建客户端对象
    client := &Client{
        ServerIp:   serverIp,
        ServerPort: serverPort,
    }
    // 连接server
    conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIp, serverPort))
    if err != nil {
        fmt.Println("net.Dial error:", err)
        return nil
    }
    client.conn = conn
    // 返回对象
    return client
}

func main() {
    client := NewClient("127.0.0.1", 8888)
    if client == nil {
        fmt.Println(">>>>> 连接服务器失败")
        return
    }
    fmt.Println(">>>>> 连接服务器成功")

    // 启动客户端业务
    select {}
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

编译指令:go build -o client client.go

运行编译后的文件:./client

解析命令行

在 init 函数中初始化命令行参数并解析:

var serverIp string
var serverPort int

func init() {
    flag.StringVar(&serverIp, "ip", "127.0.0.1", "设置服务器IP地址(默认是127.0.0.1)")
    flag.IntVar(&serverPort, "port", 8888, "设置服务器端口(默认是8888)")

    // 命令行解析
    flag.Parse()
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

然后在运行客户端时可以通过 命令行传参运行:

./client -ip 127.0.0.1 -port 8888
 
  • 1

菜单显示

给 Client 新增 flag 属性:

type Client struct {
	ServerIp   string
	ServerPort int
	Name       string
	conn       net.Conn
	flag       int // 当前客户端的模式
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

新增 menu() 方法,获取用户输入的模式:

// 菜单
func (client *Client) menu() bool {
	var flag int

	fmt.Println("1.公聊模式")
	fmt.Println("2.私聊模式")
	fmt.Println("3.更新用户名")
	fmt.Println("0.退出")

	fmt.Scanln(&flag)

	if flag >= 0 && flag <= 3 {
		client.flag = flag
		return true
	} else {
		fmt.Println(">>>>请输入合法范围内的数字<<<<")
		return false
	}
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

新增 Run() 主业务循环:

func (client *Client) Run() {
	for client.flag != 0 {
		for !client.menu() {
		}

		// 根据不同的模式处理不同的业务
		switch client.flag {
		case 1:
			// 公聊模式
			fmt.Println("公聊模式")
		case 2:
			// 私聊模式
			fmt.Println("私聊模式")
		case 3:
			// 更新用户名
			fmt.Println("更新用户名")
		}
	}
	fmt.Println("退出!")
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

更新用户名

新增 UpdateName() 更新用户名:

func (client *Client) UpdateName() bool {
	fmt.Println(">>>>请输入用户名:")
	fmt.Scanln(&client.Name)

	sendMsg := "rename|" + client.Name + "\n" // 封装协议
	_, err := client.conn.Write([]byte(sendMsg))
	if err != nil {
		fmt.Println("conn.Write err: ", err)
		return false
	}

	return true
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

添加 server 回执消息方法 DealResponse()

// 处理server回应的消息,直接显示到标准输出
func (client *Client) DealResponse() {
	// 一旦client.conn有数据,直接copy到stdout标准输出上,永久阻塞监听
	io.Copy(os.Stdout, client.conn)
}
 
  • 1
  • 2
  • 3
  • 4
  • 5

在 main 中开启一个 goroutine,去承载 DealResponse() 流程:

func main() {
	client := NewClient(serverIp, serverPort)
	if client == nil {
		fmt.Println(">>>>> 连接服务器 失败")
		return
	}
	fmt.Println(">>>>> 连接服务器成功")

	// 单独开启一个goroutine去处理server的回执消息
	go client.DealResponse()

	// 启动客户端业务
	client.Run()
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

公聊模式

新增 PublicChat() 公聊模式:

func (client *Client) PublicChat() {
	// 提示用户输入消息
	var chatMsg string

	fmt.Println(">>>>请输入聊天内容,exit退出.")
	fmt.Scanln(&chatMsg)

	for chatMsg != "exit" {
		// 发给服务器
		// 消息不为空立即发送
		if len(chatMsg) != 0 {
			sendMsg := chatMsg + "\n"
			_, err := client.conn.Write([]byte(sendMsg))
			if err != nil {
				fmt.Println("conn Write err: ", err)
				break
			}
		}
		chatMsg = ""
		fmt.Println(">>>>请输入聊天内容,exit退出.")
		fmt.Scanln(&chatMsg)
	}
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

私聊模式

查询当前有哪些用户在线:

func (client *Client) SelectUsers() {
	sendMsg := "who\n"
	_, err := client.conn.Write([]byte(sendMsg))
	if err != nil {
		fmt.Println("conn Write err: ", err)
		return
	}
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

新增私聊业务:

func (client *Client) PrivateChat() {
	var remoteName string
	var chatMsg string

	client.SelectUsers()
	fmt.Println(">>>>请输入聊天对象的[用户名], exit退出: ")
	fmt.Scanln(&remoteName)

	for remoteName != "exit" {
		fmt.Println(">>>>请输入消息内容,exit退出:")
		fmt.Scanln(&chatMsg)

		for chatMsg != "exit" {
			// 消息不为空则发送
			if len(chatMsg) != 0 {
				sendMsg := "to|" + remoteName + "|" + chatMsg + "\n\n"
				_, err := client.conn.Write([]byte(sendMsg))
				if err != nil {
					fmt.Println("conn Write err: ", err)
					break
				}
			}
			chatMsg = ""
			fmt.Println(">>>>请输入消息内容,exit退出:")
			fmt.Scanln(&chatMsg)
		}

		client.SelectUsers()
		fmt.Println(">>>>请输入聊天对象的[用户名], exit退出: ")
		fmt.Scanln(&remoteName)

	}

}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  文章知识点与官方知识档案匹配,可进一步学习相关知识 Go技能树快速上手Go安装与配置1787 人正在系统学习中

标签:入门,err,fmt,即时,server,Golang,client,user,conn
From: https://www.cnblogs.com/python9090/p/16997834.html

相关文章

  • Golang实战项目-B2C电商平台项目(3)
    Golang实战项目-B2C电商平台项目(3)总体页面的显示由于在main中声明的全局对象无法被其他包调用,所以在commons文件夹下新建CommonVars.go,保证整个项目任何包都可以......
  • Golang 项目部署实战
    一直认为不懂部署的开发工程师不是好的开发工程师,以下以一些实例讲解自己在项目中的Golang后端部署的情况。一般部署脚本应该具有构建、启动、停止、回滚已经查看记录......
  • 即时通讯音视频开发视频编码基础
    音视频即时通讯是最为时尚、流行的通讯方式。可以实现一对一、一对多、多对多的通讯。而各种各样的即时通讯软件也层出不穷;服务提供商越来越丰富的通讯服务功能。随着社会的......
  • Activity7学习入门(七)
    官网地址:https://activiti.gitbook.io/activiti-7-developers-guide/components/activiti-cloud-modelingActivitiCloudModelingActiviti云建模服务提供后端和前端功能......
  • Vuex从入门到精通
    一、vuex介绍目标什么是Vuex为什么学习Vuex通信方案组件关系数据通信父子关系父传子:props;子传父:$emit非父子关系vuex(一种组件通信方案)Vuex......
  • 时间老去,Ruby不死,Ruby语言基础入门教程之Ruby3全平台开发环境搭建EP00
    如果说电子游戏是第九艺术,那么,编程技术则配得上第十艺术的雅称。艺术发展的普遍规律就是要给与人们对于艺术作品的更高层感受,而Matz的Ruby语言则正是这样一件艺术品。无论......
  • JavaSE-day01-Java入门
    Java的三大使用平台Java有三大平台:JavaSE,JavaEE,JavaMEJavaSE介绍JavaSE是Java语言的标准版,用于桌面应用的开发,是其他两个版本的基础。桌面应用用户只要打......
  • 微服务系列:分布式文件存储之 MinIO 入门指南
    经过前面多篇文章我们学习了服务网关、服务调用、服务注册、服务监控等微服务系列的的相关知识,今天开始我们来学习一下分布式文件的相关知识。首先我们从学习​​MinIO​​......
  • Netty入门
    1.简介异步,基于事件驱动的网络应用框架TCP/IP=>JDK原生=>NIO===>Netty《netty实战》《netty权威指南》2.应用基于网络的高并发或者网络的通信RPC框架远程服务调用,......
  • 普通人或者门外汉该怎样入门编程?
    继我关注很久一位编程的大牛发布了他的那篇文章——<探究:普通人都是怎么入门编程>https://www.cnblogs.com/liuyangfirst/p/16991386.html我先去再相关评论里看到了有人说......