即时通信Demo

V0.1 基础Server构建

实现新用户连接成功后,在server端提醒的功能

server.go

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 (对象类型参数)方法名(参数列表)(返回值列表){ }
// 如果想通过方法修改对象,那么建议传递对象的地址 (结构体是值传递,通过结构体的指针修改结构体(地址传递))  (也可以通过返回值修改对象)
//func (obj *MyInt) add() { }  // 对象调用时,会自动将对象的地址传给obj
func (this *Server) Start() {
    //socket listen
    listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d",this.Ip,this.Port))
    if err != nil {
        fmt.Println("net.listen tcp err:", err)
    }
    //close listen socket
    defer listener.Close()
    for {
        //accept
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("listen accept err:", err)
            continue
        }
        //do handler
        go this.Handler(conn)    
    }
}
//链接成功,执行业务
func (this *Server) Handler(conn net.Conn) {
    fmt.Println("Connection Success...")
}

main.go

package main
func main()  {
    server := NewServer("127.0.0.1", 8888)
    server.Start()
}

编译

go build -o server main.go server.go

开启server

./server

测试连接

nc 127.0.0.1 8888

V0.2 用户上线及广播

实现新用户连接成功后,向全体在线用户广播的功能

server.go

package main
import (
    "fmt"
    "net"
    "sync"
)
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广播,有消息则发送给全部在线User (在start方法中加载)
func (this *Server) ListenMessager() {
    for {
        msg := <-this.Message
        this.mapLock.Lock()
        for _, user := range this.OnlineMap {
            user.C <- msg
        }
        this.mapLock.Unlock()
    }
}
//消息广播
func (this *Server) BroadCast(user *User, msg string) {
    sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg
    this.Message <- sendMsg
}

//链接成功,执行业务
func (this *Server) Handler(conn net.Conn) {
    user := NewUser(conn)
    //用户上线,将用户加入OnlineMap中
    this.mapLock.Lock()
    this.OnlineMap[user.Name] = user
    this.mapLock.Unlock()
    //广播用户上线消息
    this.BroadCast(user, "上线了")
    //阻塞当前Handler(防止当前及子goroutine死亡)
    select {}
}
//启动服务器的接口
func (this *Server) Start() {
    //socket listen
    listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.Ip, this.Port))
    if err != nil {
        fmt.Println("net.listen tcp err:", err)
    }
    //close listen socket
    defer listener.Close()
    //启动监听Message的goroutine
    go this.ListenMessager()
    for {
        //accept
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("listen accept err:", err)
            continue
        }
        //do handler
        go this.Handler(conn)
    }
}

user.go

package main
import "net"
type User struct {
    Name string
    Addr string
    C    chan string
    conn net.Conn
}
//创建用户
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 (this *User) ListenMessage() {
    for {
        msg := <-this.C
        this.conn.Write([]byte(msg + "\n"))
    }
}

main.go

package main
func main()  {
    server := NewServer("127.0.0.1", 8888)
    server.Start()
}

编译运行

go build -o server main.go server.go user.go

连接测试

nc 127.0.0.1 8888

V0.3用户消息广播

server.go

func (this *Server) Handler(conn net.Conn) {
    user := NewUser(conn)
    //用户上线,将用户加入OnlineMap中
    this.mapLock.Lock()
    this.OnlineMap[user.Name] = user
    this.mapLock.Unlock()
    //广播用户上线消息
    this.BroadCast(user, "上线了")
    //接收用户消息
    go func() {
        buf := make([]byte, 4096)
        for {
            //将conn中数据读取到buf中
            n, err := conn.Read(buf)
            if n == 0 {
                this.BroadCast(user, "下线")
                return
            }
            if err != nil && err != io.EOF {
                fmt.Println("Conn Read err:", err)
                return
            }
            //提取用户的消息(去除\n)
            msg := string(buf[:n-1])
            //将得到的消息进行广播
            this.BroadCast(user, msg)
        }
    }()
    //阻塞当前Handler(防止当前及子goroutine死亡)
    select {}
}

查看端口占用:

lsof -i:8888

V0.4用户业务封装

Server.go

package main

import (
    "fmt"
    "io"
    "net"
    "sync"
)

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广播,有消息则发送给全部在线User (在start方法中加载)
func (this *Server) ListenMessager() {
    for {
        msg := <-this.Message
        this.mapLock.Lock()
        for _, user := range this.OnlineMap {
            user.C <- msg
        }
        this.mapLock.Unlock()
    }
}

//消息广播
func (this *Server) BroadCast(user *User, msg string) {
    sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg
    this.Message <- sendMsg
}

//链接成功,执行业务
func (this *Server) Handler(conn net.Conn) {
    user := NewUser(conn, this)
    //用户上线,将用户加入OnlineMap中
    user.Online()
    //接收用户消息
    go func() {
        buf := make([]byte, 4096)
        for {
            //将conn中数据读取到buf中
            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(防止当前及子goroutine死亡)
    select {}
}

//启动服务器的接口
func (this *Server) Start() {
    //socket listen
    listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.Ip, this.Port))
    if err != nil {
        fmt.Println("net.listen tcp err:", err)
    }
    //close listen socket
    defer listener.Close()
    //启动监听Message的goroutine
    go this.ListenMessager()
    for {
        //accept
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("listen accept err:", err)
            continue
        }
        //do handler
        go this.Handler(conn)
    }
}

user.go

package main

import "net"

type User struct {
    Name   string
    Addr   string
    C      chan string
    conn   net.Conn
    server *Server
}

//创建用户
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
}

//监听当前User channel,有新消息则发送到客户端
func (this *User) ListenMessage() {
    for {
        msg := <-this.C
        this.conn.Write([]byte(msg + "\n"))
    }
}

//用户上线
func (this *User) Online() {
    this.server.mapLock.Lock()
    this.server.OnlineMap[this.Name] = this
    this.server.mapLock.Unlock()
    this.server.BroadCast(this, "上线了")
}

//用户下线
func (this *User) Offline() {
    this.server.mapLock.Lock()
    delete(this.server.OnlineMap, this.Name)
    this.server.mapLock.Unlock()
    this.server.BroadCast(this, "下线了")
}

//用户消息
func (this *User) DoMessage(msg string) {
    this.server.BroadCast(this, msg)
}

V0.5在线用户查询

user.go

//用户消息
func (this *User) DoMessage(msg string) {
    if msg == "who" {
        this.server.OnlineMap.Lock()
        for _, user := range this.server.OnlineMap {
            olMsg := "[" + user.Addr + "]" + user.Name + ":在线...\n"
            this.conn.Write([]byte(olMsg))
        }
        this.server.OnlineMap.Unlock()
    } else {
        this.server.BroadCast(this, msg)
    }
}

V0.6修改用户名

user.go

//向客户端输出消息
func (this *User) sendMsg(msg string) {
    this.conn.Write([]byte(msg))
}
//用户消息
func (this *User) DoMessage(msg string) {
    if msg == "who" {
        this.server.mapLock.Lock()
        for _, user := range this.server.OnlineMap {
            olMsg := "[" + user.Addr + "]" + user.Name + ":在线...\n"
            this.sendMsg(olMsg)
        }
        this.server.mapLock.Unlock()
    } else if len(msg) > 7 && msg[:7] == "rename|" {
        newName := strings.Split(msg, "|")[1]
        _, 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("您已经更名为:" + newName + "\n")
        }
    } else {
        this.server.BroadCast(this, msg)
    }
}

V0.7 超时强踢

server.go

//链接成功,执行业务
func (this *Server) Handler(conn net.Conn) {
    user := NewUser(conn, this)
    //用户上线,将用户加入OnlineMap中
    user.Online()
    //监听用户是否活跃的channel
    isLive := make(chan bool)
    //接收用户消息
    go func() {
        buf := make([]byte, 4096)
        for {
            //将conn中数据读取到buf中
            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)
            //用户任意消息
            isLive <- true
        }
    }()
    //阻塞当前Handler(防止当前及子goroutine死亡)
    for {
        select {
        case <-isLive:
            //当前用户是活跃的,激活select重置定时器
        case <-time.After(time.Second * 10):
            //倒计时10秒强制退出
            user.sendMsg("你被踢了")
            close(user.C)
            conn.Close()
            return
        }
    }
}

V0.8私聊功能

//用户消息
func (this *User) DoMessage(msg string) {
    ...else if len(msg) > 4 && msg[:3] == "to|" {
        remoteName := strings.Split(msg, "|")[1]
        if remoteName == "" {
            this.sendMsg("消息格式不正确\n")
            return
        }
        //获取对方User对象
        remoteUser, ok := this.server.OnlineMap[remoteName]
        if !ok {
            this.sendMsg("用户不存在\n")
            return
        }
        //消息内容发送给指定User
        content := strings.Split(msg, "|")[2]
        if content == "" {
            this.sendMsg("消息不能为空\n")
            return
        }
        remoteUser.sendMsg(this.Name + "对您说:" + content + "\n")
    }...
}

V0.9 客户端实现 client.go

建立连接
package main
import (
    "fmt"
    "net"
)
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,
    }
    conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIp, serverPort))
    if err != nil {
        fmt.Println("net.Dial err:", 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 {}
}

测试

go build -o client client.go
./client
解析命令行
package main

import (
    "flag"
    "fmt"
    "net"
)

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

var serverIp string
var serverPort int

// ./client -ip 127.0.0.1 -port 8888
func init() {
    flag.StringVar(&serverIp, "ip", "127.0.0.1", "设置服务器IP")
    flag.IntVar(&serverPort, "port", 8888, "设置服务器端口")
}

func NewClient(serverIp string, serverPort int) *Client {
    client := &Client{
        ServerIp:   serverIp,
        ServerPort: serverPort,
    }
    conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIp, serverPort))
    if err != nil {
        fmt.Println("net.Dial err:", err)
        return nil
    }
    client.conn = conn
    return client
}

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

测试

go build -o client client.go
./client -ip 127.0.0.1 -port 8888
菜单显示
package main

import (
    "flag"
    "fmt"
    "net"
)

type Client struct {
    ServerIp   string
    ServerPort int
    Name       string
    conn       net.Conn
    flag       int //当前client的模式
}

func (client *Client) Run() {
    //除非输入0否则一直循环
    for client.flag != 0 {
        //除非输入0-3,否则一直循环
        for client.menu() != true {
        }
        //根据flag执行不同业务
        switch client.flag {
        case 1:
            fmt.Println("选择公聊...")
        case 2:
            fmt.Println("选择私聊...")
        case 3:
            fmt.Println("选择改名...")
        }
    }
}
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
    }
}

var serverIp string
var serverPort int

// ./client -ip 127.0.0.1 -port 8888
func init() {
    flag.StringVar(&serverIp, "ip", "127.0.0.1", "设置服务器IP")
    flag.IntVar(&serverPort, "port", 8888, "设置服务器端口")
}

func NewClient(serverIp string, serverPort int) *Client {
    client := &Client{
        ServerIp:   serverIp,
        ServerPort: serverPort,
        flag:       999,
    }
    conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIp, serverPort))
    if err != nil {
        fmt.Println("net.Dial err:", err)
        return nil
    }
    client.conn = conn
    return client
}

func main() {
    flag.Parse()
    client := NewClient(serverIp, serverPort)
    if client == nil {
        fmt.Println(">>>>>连接服务器失败")
        return
    }
    fmt.Println(">>>>>连接服务器成功")
    //启动客户端业务
    client.Run()
}

测试

go build -o client client.go
./client -ip 127.0.0.1 -port 8888
更新用户名
package main

import (
    "flag"
    "fmt"
    "io"
    "net"
    "os"
)

type Client struct {
    ServerIp   string
    ServerPort int
    Name       string
    conn       net.Conn
    flag       int //当前client的模式
}

func (client *Client) Run() {
    //除非输入0否则一直循环
    for client.flag != 0 {
        //除非输入0-3,否则一直循环
        for client.menu() != true {
        }
        //根据flag执行不同业务
        switch client.flag {
        case 1:
            fmt.Println("选择公聊...")
        case 2:
            fmt.Println("选择私聊...")
        case 3:
            client.UpdateName()
        }
    }
}
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
    }
}

var serverIp string
var serverPort int

// ./client -ip 127.0.0.1 -port 8888
func init() {
    flag.StringVar(&serverIp, "ip", "127.0.0.1", "设置服务器IP")
    flag.IntVar(&serverPort, "port", 8888, "设置服务器端口")
}

func NewClient(serverIp string, serverPort int) *Client {
    client := &Client{
        ServerIp:   serverIp,
        ServerPort: serverPort,
        flag:       999,
    }
    conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIp, serverPort))
    if err != nil {
        fmt.Println("net.Dial err:", err)
        return nil
    }
    client.conn = conn
    return client
}
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
}

//处理server回应的消息,直接显示到标准输出即可
func (client *Client) DealResponse() {
    //一旦client有数据,就直接copy到stdout标准输出上,永久阻塞监听
    io.Copy(os.Stdout, client.conn)
}
func main() {
    flag.Parse()
    client := NewClient(serverIp, serverPort)
    if client == nil {
        fmt.Println(">>>>>连接服务器失败")
        return
    }
    fmt.Println(">>>>>连接服务器成功")
    //单独开启一个goroutine处理server的回执消息
    go client.DealResponse()
    //启动客户端业务
    client.Run()
}
公聊模式
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)
        }
    }
}
私聊模式
package main

import (
    "flag"
    "fmt"
    "io"
    "net"
    "os"
)

type Client struct {
    ServerIp   string
    ServerPort int
    Name       string
    conn       net.Conn
    flag       int //当前client的模式
}

func (client *Client) Run() {
    //除非输入0否则一直循环
    for client.flag != 0 {
        //除非输入0-3,否则一直循环
        for client.menu() != true {
        }
        //根据flag执行不同业务
        switch client.flag {
        case 1:
            client.PublicChat()
        case 2:
            client.PrivateChat()
        case 3:
            client.UpdateName()
        }
    }
}
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
    }
}

var serverIp string
var serverPort int

// ./client -ip 127.0.0.1 -port 8888
func init() {
    flag.StringVar(&serverIp, "ip", "127.0.0.1", "设置服务器IP")
    flag.IntVar(&serverPort, "port", 8888, "设置服务器端口")
}

func NewClient(serverIp string, serverPort int) *Client {
    client := &Client{
        ServerIp:   serverIp,
        ServerPort: serverPort,
        flag:       999,
    }
    conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIp, serverPort))
    if err != nil {
        fmt.Println("net.Dial err:", err)
        return nil
    }
    client.conn = conn
    return client
}
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)
        }
    }
}
func (client *Client) SelectUsers() {
    sendMsg := "who\n"
    _, err := client.conn.Write([]byte(sendMsg))
    if err != nil {
        fmt.Println("conn.Write err:", err)
        return
    }
}

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)
    }
}

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
}

//处理server回应的消息,直接显示到标准输出即可
func (client *Client) DealResponse() {
    //一旦client有数据,就直接copy到stdout标准输出上,永久阻塞监听
    io.Copy(os.Stdout, client.conn)
}
func main() {
    flag.Parse()
    client := NewClient(serverIp, serverPort)
    if client == nil {
        fmt.Println(">>>>>连接服务器失败")
        return
    }
    fmt.Println(">>>>>连接服务器成功")
    //单独开启一个goroutine处理server的回执消息
    go client.DealResponse()
    //启动客户端业务
    client.Run()
}

参考资料

8小时转职Golang工程师


IT小马
1.2k 声望166 粉丝

Php - Go - Vue - 云原生