学习视频:8 小时转职 Golang 工程师,这门课很适合有一定开发经验的小伙伴,强推!
【Golang 快速入门4】项目实战:即时通信系统
即时通信系统 - 服务端
项目架构图:
9个小版本迭代:
- 版本一:构建基础 Server
- 版本二:用户上线功能
- 版本三:用户消息广播机制
- 版本四:用户业务层封装
- 版本五:在线用户查询
- 版本六:修改用户名
- 版本七:超时强踢功能
- 版本八:私聊功能
- 版本九:客户端实现
版本一:构建基础 Server
server.go,其中包含以下内容:
- 定义 Server 结构体,包含 IP、Port 字段
NewServer(ip string, port int)
创建 Server 对象的方法(s *Server) Start()
启动 Server 服务的方法(s *Server) Handler(conn net.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
| type Server struct { Ip string Port int }
func NewServer(ip string, port int) *Server { server := &Server{ Ip: ip, Port: port, } return server }
func (this *Server) Handler(conn net.Conn) { fmt.Println("链接建立成功") }
func (this *Server) Start() { listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.Ip, this.Port)) if err != nil { fmt.Println("net.Listen err:", err) return } defer listener.Close() for { conn, err := listener.Accept() if err != nil { fmt.Println("Listener accept err:", err) continue } go this.Handler(conn) }
}
|
main.go,启动我们编写的 Server:
1 2 3 4 5 6 7 8
| package main
func main() { server := NewServer("127.0.0.1", 8888) server.Start() }
|
window下编译运行:
同时编译编写的两个文件:go build -o server.exe
然后运行编译出的文件:server.exe
使用命令侦听我们构建的服务:nc 127.0.0.1 8888
版本二:用户上线+广播功能
user.go:
NewUser(conn net.Conn) *User
创建一个 user 对象(u *User) ListenMessage()
监听 user 对应的 channel 消息
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
| 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, } go user.ListenMessage()
return user }
func (this *User) ListenMessage() { for { msg := <-this.C
this.conn.Write([]byte(msg + "\n")) } }
|
Server.go:
- 新增 OnlineMap 和 Message 属性
- 在处理客户端上线的 Handler 创建并添加用户
- 新增广播消息方法
- 新增监听广播消息 channel 方法
- 用一个 goroutine 单独监听 Message
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
| type Server struct { Ip string Port int
OnlineMap map[string]*User mapLock sync.RWMutex
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 (this *Server) ListenMessager() { for { msg := <-this.Message
this.mapLock.Lock() for _, cli := range this.OnlineMap { cli.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) { fmt.Println("链接建立成功")
user := NewUser(conn)
this.mapLock.Lock() this.OnlineMap[user.Name] = user this.mapLock.Unlock()
this.BroadCast(user, "已上线")
select {} }
func (this *Server) Start() { listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.Ip, this.Port)) if err != nil { fmt.Println("net.Listen err:", err) return } defer listener.Close()
go this.ListenMessager()
for { conn, err := listener.Accept() if err != nil { fmt.Println("Listener accept err:", err) continue } go this.Handler(conn) } }
|
学习到的编程思路:
- 结构体中的 channel 基本都需要开个循环去监听其变化(尝试取出值,发送给其他 channel)
总结:就是每个用户上线的时候,就会新建一个用户的对象(结构体),对象中自带一个goroutine,然后每个用户上线的时候,都把这个消息遍历发送给每个用户的goroutine
一、user.go 后端服务器用来当前用户的类型
1.创建一个user对象
- 监听user对应的channel消息
二、server.go
- 结构体新增OnlineMap和Message属性
- 在处理客户端上线的Handler创建并添加用户,并广播消息(消息送入chan)
- 新增广播消息的方法
- 新增监听广播消息channel的方法,得到消息发给OnlineMap中每一个User的chan中
- 在Start方法中用一个goroutine单独监听Message
版本三:用户消息广播机制
server.go:完善 handle 处理业务方法,启动一个针对当前客户端的读 routine
server.go
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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
| package main
import ( "fmt" "io" "net" "sync" )
type Server struct { Ip string Port int
OnlineMap map[string]*User mapLock sync.RWMutex
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 (this *Server) ListenMessager() { for { msg := <-this.Message
this.mapLock.Lock() for _, cli := range this.OnlineMap { cli.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) { fmt.Println("链接建立成功")
user := NewUser(conn)
this.mapLock.Lock() this.OnlineMap[user.Name] = user this.mapLock.Unlock()
this.BroadCast(user, "Online")
go func() { buff := make([]byte, 4096) for { n, err := conn.Read(buff) if n == 0 { this.BroadCast(user, "Offline") return } if err != nil && err != io.EOF { fmt.Println("Conn Read err:", err) } msg := string(buff[:n-1]) fmt.Println("Receive message:" + msg + " from:{" + user.Addr + "}" + user.Name) this.BroadCast(user, msg) } }()
select {} }
func (this *Server) Start() { listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.Ip, this.Port)) if err != nil { fmt.Println("net.Listen err:", err) return } defer listener.Close()
go this.ListenMessager()
for { conn, err := listener.Accept() if err != nil { fmt.Println("Listener accept err:", err) continue } go this.Handler(conn) } }
|
版本四:用户业务层封装
用户上线、用户下线、用户处理消息都是User的业务,不应该是Server的业务
user.go:
- user 类型新增 server 关联
- 新增 Online、Offline、DoMessage 方法
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
| 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, } go user.ListenMessage()
return user }
func (this *User) Online() { this.server.mapLock.Lock() this.server.OnlineMap[this.Name] = this this.server.mapLock.Unlock()
this.server.BroadCast(this, "Online") }
func (this *User) Offline() { this.server.mapLock.Lock() delete(this.server.OnlineMap, this.Name) this.server.mapLock.Unlock()
this.server.BroadCast(this, "Offline") }
func (this *User) DoMessage(msg string) { this.server.BroadCast(this, msg) }
func (this *User) ListenMessage() { for { msg := <-this.C
this.conn.Write([]byte(msg + "\n")) } }
|
server.go:
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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
| package main
import ( "fmt" "io" "net" "sync" )
type Server struct { Ip string Port int
OnlineMap map[string]*User mapLock sync.RWMutex
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 (this *Server) ListenMessager() { for { msg := <-this.Message
this.mapLock.Lock() for _, cli := range this.OnlineMap { cli.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) { fmt.Println("链接建立成功")
user := NewUser(conn, this)
user.Online()
go func() { buff := make([]byte, 4096) for { n, err := conn.Read(buff) if n == 0 { user.Offline() return } if err != nil && err != io.EOF { fmt.Println("Conn Read err:", err) } msg := string(buff[:n-1]) fmt.Println("Receive message:" + msg + " from:{" + user.Addr + "}" + user.Name)
user.DoMessage(msg) } }()
select {} }
func (this *Server) Start() { listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.Ip, this.Port)) if err != nil { fmt.Println("net.Listen err:", err) return } defer listener.Close()
go this.ListenMessager()
for { conn, err := listener.Accept() if err != nil { fmt.Println("Listener accept err:", err) continue } go this.Handler(conn) } }
|
总结:每个模块处理各自的事情,因此将用户上线、用户下线、用户发送消息封装到user.go中去
user.go:结构体新增server关联
- 新增Online方法
- 新增Offline方法
- 新增DoMessage方法
server.go:将之前user的业务进行替换
版本五:在线用户查询
若某个用户输入的消息为 who
则查询当前在线用户列表。
user.go:
- 提供 SendMsg 向对象客户端发送消息 API
1 2 3 4 5
| func (this *User) SendMsg(msg string) { this.conn.Write([]byte(msg)) }
|
- 在 DoMessage() 方法中,加上对 “who” 指令的处理,返回在线用户信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| func (this *User) DoMessage(msg string) { if msg == "who" { this.server.mapLock.Lock() for _, user := range this.server.OnlineMap { onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "Online\n" this.SendMsg(onlineMsg) } this.server.mapLock.Unlock() } else { this.server.BroadCast(this, msg) } }
|
版本六:修改用户名
若某个用户输入的消息为 rename|张三
则将自己的 Name 修改为张三。
user.go:
- 在 DoMessage() 方法中,加上对 “rename|张三” 指令的处理
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
| func (this *User) DoMessage(msg string) { if msg == "who" { this.server.mapLock.Lock() for _, user := range this.server.OnlineMap { onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "Online\n" this.SendMsg(onlineMsg) } this.server.mapLock.Unlock() } else if len(msg) > 7 && msg[:7] == "rename|" { newname := strings.Split(msg, "|")[1] if _, ok := this.server.OnlineMap[newname]; ok { this.SendMsg("this name has been used\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("Update username sucessful : " + newname + "\n") } } else { this.server.BroadCast(this, msg) } }
|
这里存在漏洞,rename||123,用户名会为空,应当将用户名中的|进行转义
版本七:超时强踢功能
用户的任意消息表示用户为活跃,长时间不发消息认为超时,就才一强制关闭用户连接。
server.go:
- 在用户 Handler() goroutine 中,添加活跃用户 channel,一旦用户有消息,就向该 channel 发送数据
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
| func (this *Server) Handler(conn net.Conn) { fmt.Println("链接建立成功")
user := NewUser(conn, this)
user.Online()
isLive := make(chan bool)
go func() { buff := make([]byte, 4096) for { n, err := conn.Read(buff) if n == 0 { fmt.Println("{" + user.Addr + "}" + user.Name + " : Offline") user.Offline() return } if err != nil && err != io.EOF { fmt.Println("Conn Read err:", err) } msg := string(buff[:n-1]) fmt.Println("Receive message:" + msg + " from:{" + user.Addr + "}" + user.Name)
user.DoMessage(msg)
isLive <- true } }()
for { select { case <-isLive: case <-time.After(time.Second * 10): user.SendMsg("timeout,you have been offlined") this.mapLock.Lock() delete(this.OnlineMap, user.Name) this.mapLock.Unlock()
close(user.C) conn.Close() return } } }
|
版本八:私聊功能
消息格式:to|张三|你好啊,我是...
user.go,在 DoMessage() 方法中,加上对 “to|张三|你好啊” 指令的处理:
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
| func (this *User) DoMessage(msg string) { if msg == "who" { this.server.mapLock.Lock() for _, user := range this.server.OnlineMap { onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "Online\n" this.SendMsg(onlineMsg) } this.server.mapLock.Unlock() } else if len(msg) > 7 && msg[:7] == "rename|" { newname := strings.Split(msg, "|")[1] if _, ok := this.server.OnlineMap[newname]; ok { this.SendMsg("this name has been used\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("Update username sucessful : " + newname + "\n") } } else if len(msg) > 4 && msg[:3] == "to|" { remoteName := strings.Split(msg, "|")[1] if remoteName == "" { this.SendMsg("message format is invalid\n") return } remoteUser, ok := this.server.OnlineMap[remoteName] if !ok { this.SendMsg("User:" + remoteName + "is not existed\n") return } content := strings.Split(msg, "|")[2] if content == "" { this.SendMsg("content must not be blanked\n") return } remoteUser.SendMsg(this.Name + " send to you : " + content) } else { this.server.BroadCast(this, msg) } }
|
版本九:客户端实现
即时通信系统 - 客户端
以下代码都是在 client.go 文件中
客户端类型定义与链接
client.go:
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
| 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() { clint := NewClient("127.0.0.1", 8888) if clint == nil { fmt.Println(">>>>>> Connect Server failed") return } fmt.Println(">>>>>> Connect Server success")
select {} }
|
编译指令:go build -o client.exe client.go
运行编译后的文件:./client
解析命令行 flag
在 init 函数中初始化命令行参数并解析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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(">>>>>> Connect Server failed") return } fmt.Println(">>>>>> Connect Server success")
select {} }
|
然后在运行客户端时可以通过 命令行传参运行:
1
| ./client -ip 127.0.0.1 -port 8888
|
菜单显示
给 Client 新增 flag 属性:
1 2 3 4 5 6 7
| type Client struct { ServerIp string ServerPort int Name string conn net.Conn flag int }
|
新增 menu() 方法,获取用户输入的模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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 } }
|
新增 Run() 主业务循环:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| func (client *Client) Run() { for client.flag != 0 { for client.menu() != true { }
switch client.flag { case 1: fmt.Println("公聊模式") case 2: fmt.Println("私聊模式") case 3: fmt.Println("更新用户名")
} } fmt.Println("退出") }
|
client.go
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 88 89 90 91 92 93
| 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: 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) 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 } }
func (client *Client) Run() { for client.flag != 0 { for client.menu() != true { }
switch client.flag { case 1: fmt.Println("公聊模式") case 2: fmt.Println("私聊模式") case 3: fmt.Println("更新用户名")
} } fmt.Println("退出") }
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(">>>>>> Connect Server failed") return } fmt.Println(">>>>>> Connect Server success")
client.Run() }
|
更新用户名
新增 UpdateName() 更新用户名:
1 2 3 4 5 6 7 8 9 10 11 12
| 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 回执消息方法 DealResponse()
1 2 3 4 5 6 7 8 9 10 11
| func (client *Client) DealResponse() { io.Copy(os.Stdout, client.conn) }
|
在 main 中开启一个 goroutine,去承载 DealResponse() 流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| func main() { flag.Parse() client := NewClient(serverIp, serverPort) if client == nil { fmt.Println(">>>>>> Connect Server failed") return } fmt.Println(">>>>>> Connect Server success")
go client.DealResponse() client.Run() }
|
公聊模式
新增 PublicChat() 公聊模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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
| 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 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 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) return } chatMsg = "" fmt.Println(">>>>请输入消息内容,exit退出:") fmt.Scanln(&chatMsg) } } client.SelectUsers() fmt.Println(">>>>请输入聊天对象的[用户名], exit退出: ") fmt.Scanln(&remoteName) } }
|
三个文件完整代码
server.go
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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
| package main
import ( "fmt" "io" "net" "sync" "time" )
type Server struct { Ip string Port int
OnlineMap map[string]*User mapLock sync.RWMutex
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 (this *Server) ListenMessager() { for { msg := <-this.Message
this.mapLock.Lock() for _, cli := range this.OnlineMap { cli.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) { fmt.Println("链接建立成功")
user := NewUser(conn, this)
user.Online()
isLive := make(chan bool)
go func() { buff := make([]byte, 4096) for { n, err := conn.Read(buff) if n == 0 { fmt.Println("{" + user.Addr + "}" + user.Name + " : Offline") user.Offline() return } if err != nil && err != io.EOF { fmt.Println("Conn Read err:", err) } msg := string(buff[:n-1]) fmt.Println("Receive message:" + msg + " from:{" + user.Addr + "}" + user.Name)
user.DoMessage(msg)
isLive <- true } }()
for { select { case <-isLive: case <-time.After(time.Second * 1000): user.SendMsg("timeout,you have been offlined") this.mapLock.Lock() delete(this.OnlineMap, user.Name) this.mapLock.Unlock()
close(user.C) conn.Close() return } } }
func (this *Server) Start() { listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.Ip, this.Port)) if err != nil { fmt.Println("net.Listen err:", err) return } defer listener.Close()
go this.ListenMessager()
for { conn, err := listener.Accept() if err != nil { fmt.Println("Listener accept err:", err) continue } go this.Handler(conn) } }
|
main.go
1 2 3 4 5 6 7
| package main
func main() { server := NewServer("127.0.0.1", 8888) server.Start() }
|
client.go
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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
| 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: 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) DealResponse() { io.Copy(os.Stdout, client.conn) }
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 } }
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 }
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) return } chatMsg = "" fmt.Println(">>>>请输入消息内容,exit退出:") fmt.Scanln(&chatMsg) } } client.SelectUsers() fmt.Println(">>>>请输入聊天对象的[用户名], exit退出: ") fmt.Scanln(&remoteName) } }
func (client *Client) Run() { for client.flag != 0 { for client.menu() != true { }
switch client.flag { case 1: client.PublicChat() case 2: client.PrivateChat() case 3: client.UpdateName() } } fmt.Println("退出") }
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(">>>>>> Connect Server failed") return } fmt.Println(">>>>>> Connect Server success")
go client.DealResponse() client.Run() }
|