0%

golang学习-即时通讯系统demo

简易的即时通讯系统的模拟demo

基本项目结构如下

项目地址: https://github.com/Vector-DY/Instant-Messaging-demo

server构建

定义基本server类型及服务器接口

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
type Server struct {
Ip string
Port int
}

func NewServer(ip string, port int) *Server {
server := &Server{
Ip: ip,
Port: port,
}

return server
}

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 {
//accept
conn, err := listener.Accept()
if err != nil {
fmt.Println("listener accept err:", err)
continue
}

///do handler
go s.Handler(conn)
}

}

用户模块

用户结构构建

用户板块结构如下图所示
使用Map记录在线用户信息,用户上线后向服务器发出请求,服务器为每一个用户分配单独的channel传递消息

用户对象及方法定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
}

服务器端需要增添存储用户数据的数据结构及处理用户业务的方法

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
type Server struct {
Ip string
Port int

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

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

func (s *Server) Handler(conn net.Conn) {}
//广播消息的方法
func (s *Server) BroadCast(user *User, msg string) {
sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg
s.Message <- sendMsg
}

// 监听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()
}
}

在服务器监听程序中,可以单独开辟一个goroutine监听Message广播消息

1
2
//启动监听Message的goroutine
go s.ListenMessager()

用户业务层封装

一些消息方法我们之前运行在了server当中,所以需要将在server中处理的用户业务封装入用户模块,降低程序的耦合性。
在用户对象中加入server属性,表示所关联的服务器,处理用户在服务端的业务。

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
type User struct {
Name string
Addr string
C chan string
conn net.Conn

server *Server
}

// 用户的上线业务
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) {}

用户功能扩展

1
2
3
4
5
// 给当前User对应的客户端发送消息
// 通过指令处理用户请求
func (u *User) SendMsg(msg string) {
u.conn.Write([]byte(msg))
}

在线用户查询功能。

1
2
3
4
5
6
7
8
9
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()
}

改名功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if len(msg) > 7 && msg[:7] == "rename|" {
//消息格式:rename|XX
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, u.Name)
u.server.OnlineMap[newName] = u
u.server.mapLock.Unlock()

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

超时离线功能
在服务器的阻塞执行进程中判断用户是否在线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//监听用户是否活跃的channel
isLive := make(chan bool)

select {
case <-isLive:
//当前用户是活跃的,应该重置定时器
//不做任何事情,激活select,更新下边定时器
case <-time.After((time.Second * 120)):
//已经超时
//将当前Use强制关闭
user.SendMsg("登录已超时")
//注销资源
close(user.C)

//关闭连接
conn.Close()

私聊功能
获取对方的用户对象信息并发送信息

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
if len(msg) > 4 && msg[:3] == "to|" {
//消息格式 to|XX|msg

//1 获取对方用户名
remoteName := strings.Split(msg, "|")[1]
if remoteName == "" {
u.SendMsg("usage : to|XX|msg \n")
return
}

//2 根据用户名得到User对象
remoteUser, ok := u.server.OnlineMap[remoteName]
if !ok {
u.SendMsg("该用户名不存在\n")
return
}

//3 获取消息内容,通过对方的User对象将消息内容发送给过去
content := strings.Split(msg, "|")[2]
if content == "" {
u.SendMsg("无内容,请重发\n")
return
}
remoteUser.SendMsg(u.Name + "对你说:" + content + "\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
type Client struct {
ServerIp string
ServerPort int
Name string
conn net.Conn
flag 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
}
//单独开启一个goroutine处理server的回执消息
go client.DealResponse()

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

//启动客户端的业务
client.Run()
}