go语言如何实现即时通讯聊天室

实现的功能:公聊,私聊,修改用户名

只用到了四个类:

main.go:用来启动服务器

server.go:服务器相关代码

client.go:客户端相关代码,用户可以直接操作的可视化界面

user.go:用户类,用来封装用户的业务逻辑

架构图


完整代码

server.go

package main

import (

        "fmt"

        "io"

        "net"

        "sync"

        "time"

)

type Server struct {

        Ip  string

        Port int

        //在线用户列表

        OnlineMap map[string]*User

        mapLock  sync.RWMutex

        //消息广播的Channel

        Message chan string

}

func NewServer(ip string, port int) *Server {

        server := &Server{

                Ip:        ip,

                Port:      port,

                OnlineMap: make(map[string]*User),

                Message:  make(chan string),

        }

        return server

}

func (s *Server) Handler(conn net.Conn) {

        //业务逻辑

        //fmt.Println("链接建立成功")

        user := NewUser(conn, s)

        user.Online()

        //监听用户是否活跃

        isLive := make(chan bool)

        go func() {

                buf := make([]byte, 4096)

                for {

                                n, error := conn.Read(buf)

                                if n == 0 {

                                        user.Offline()

                                        return

                                }

                                if error != nil && error != io.EOF {

                                        fmt.Println("read error")

                                }

                                msg := string(buf[:n-1])

                                user.DoMessage(msg)

                                //表示用户活跃

                                isLive <- true

                        }

        }()

        for {

                select {

                                case <-isLive:

                                //当前用户活跃,不做任何时,激活select,重置定时器

                                case <-time.After(time.Second * 300):

                                //超时,将user强制关闭

                                user.SendMsg("你被踢了")

                                close(user.C)

                                conn.Close()

                                return

                }

        }

}

func (s *Server) ListenMessager() {

        for {

                msg := <-s.Message

                s.mapLock.Lock()

                for _, user := range s.OnlineMap {

                        user.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) Start() {

        listener, error := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Ip, s.Port))

        if error != nil {

                fmt.Println("listener error...")

                return

        }

        defer listener.Close()

        go s.ListenMessager()

        for {

                conn, error := listener.Accept()

                if error != nil {

                        fmt.Println("accept error...")

                        continue

                }

                go s.Handler(conn)

        }

}


client.go

package main

import (

        "flag"

        "fmt"

        "io"

        "net"

        "os"

)

type Client struct {

        ServerIp  string

        ServerPort int

        Name      string

        conn      net.Conn

        flag      int

}

func NewClient(serverIp string, serverPort int) *Client {

        client := &Client{

                ServerIp:  serverIp,

                ServerPort: serverPort,

                flag:      9999,

        }

        conn, error := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIp, serverPort))

        if error != nil {

                fmt.Println("net dial error...")

                return nil

        }

        client.conn = conn

        return client

}

func (c *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 {

                c.flag = flag

                return true

        } else {

                fmt.Println(">>>>请输入合法数字<<<<")

                return false

        }

}

//修改用户名

func (c *Client) UpdateName() bool {

        fmt.Println(">>>>请输入用户名")

        fmt.Scanln(&c.Name)

        sendMsg := "rename|" + c.Name + "\n"

        _, error := c.conn.Write([]byte(sendMsg))

        if error != nil {

                fmt.Println("conn.write error...")

                return false

        }

        return true

}

//公聊

func (c *Client) PublicChat() {

        var chatMsg string

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

        fmt.Scanln(&chatMsg)

        for chatMsg != "exit" {

                if len(chatMsg) != 0 {

                        msg := chatMsg + "\n"

                        _, error := c.conn.Write([]byte(msg))

                        if error != nil {

                                fmt.Println("conn.Write error....")

                                break

                        }

                }

                chatMsg = ""

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

                fmt.Scanln(&chatMsg)

        }

}

//私聊

func (c *Client) PrivateChat() {

        var remoteUser string

        var chatMsg string

        c.SelectUsers()

        fmt.Println(">>>>请输入聊天对象的用户名,输入exit退出")

        fmt.Scanln(&remoteUser)

        for remoteUser != "exit" {

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

                fmt.Scanln(&chatMsg)

                for chatMsg != "exit" {

                        if len(chatMsg) != 0 {

                        msg := "to|" + remoteUser + "|" + chatMsg + "\n\n"

                        _, error := c.conn.Write([]byte(msg))

                        if error != nil {

                                fmt.Println("conn.Write error....")

                                break

                        }

                }

                chatMsg = ""

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

                fmt.Scanln(&chatMsg)

        }

        c.SelectUsers()

        remoteUser = ""

        fmt.Println(">>>>请输入聊天对象的用户名,输入exit退出")

        fmt.Scanln(&remoteUser)

        }

}

//查询在线用户

func (c *Client) SelectUsers() {

        sendMsg := "who\n"

        _, error := c.conn.Write([]byte(sendMsg))

        if error != nil {

                fmt.Println("conn.Write error....")

                return

        }

}

//处理server返回的消息

func (c *Client) DealResponse() {

        io.Copy(os.Stdout, c.conn)

}

func (c *Client) Run() {

        for c.flag != 0 {

                for c.menu() != true {

                }

                switch c.flag {

                        case 1:

                        //公聊

                        c.PublicChat()

                        case 2:

                        //私聊

                        c.PrivateChat()

                        case 3:

                        //修改用户名

                        c.UpdateName()

               }

        }

}

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

}

func main() {

        flag.Parse()

        client := NewClient(serverIp, serverPort)

        if client == nil {

                fmt.Println(">>>>链接服务器失败")

                return

        }

        go client.DealResponse()

        fmt.Println(">>>>链接服务器成功")

        client.Run()

}


user.go

package main

import (

        "net"

        "strings"

)

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,

        }

        go user.ListenMessage()

        return user

}

//用户上线

func (u *User) Online() {

        u.server.mapLock.Lock()

        u.server.OnlineMap[u.Name] = u

        u.server.mapLock.Unlock()

        u.server.BroadCast(u, "上线")

}

//用户下线

func (u *User) Offline() {

        u.server.mapLock.Lock()

        delete(u.server.OnlineMap, u.Name)

        u.server.mapLock.Unlock()

        u.server.BroadCast(u, "下线")

}

//给当前user的客户端发送消息

        func (u *User) SendMsg(msg string) {

        u.conn.Write([]byte(msg))

}

//处理消息

        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|xxx

                newName := strings.Split(msg, "|")[1]

                //判断名字是否已经存在

                _, ok := u.server.OnlineMap[newName]

                if ok {

                        u.SendMsg("用户名已存在\n")

                } else {

                        u.server.mapLock.Lock()

                        delete(u.server.OnlineMap, u.Name)

                        u.server.OnlineMap[newName] = u

                        u.server.mapLock.Unlock()

                        u.Name = newName

                        u.SendMsg("用户名成功修改为:" + newName + "\n")

                 }

        } else if len(msg) > 4 && msg[:3] == "to|" {

                //私聊  to|zhangsan|你好

                //获取对方用户名

                remoteName := strings.Split(msg, "|")[1]

                if remoteName == "" {

                        u.SendMsg("用户名格式不对\n")

                        return

                }

                //获取对方user

                remoteUser, ok := u.server.OnlineMap[remoteName]

                if !ok {

                        u.SendMsg("用户不存在\n")

                        return

                }

                //获取消息

                msg := strings.Split(msg, "|")[2]

                if msg == "" {

                        u.SendMsg("无消息内容,重新发送\n")

                }

                //发送消息

                remoteUser.SendMsg(u.Name + "对您说:" + msg)

        } else {

                u.server.BroadCast(u, msg)

        }

}

func (u *User) ListenMessage() {

        for {

                msg := <-u.C

                u.conn.Write([]byte(msg + "\n"))

        }

}


main.go

package main

func main() {

        server := NewServer("127.0.0.1", 8888)

        server.Start()

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容