• 广播:函数将需要发送的数据发送到server管道,然后通过一个server协程通过管道传输给每个用户的私聊子协程发送消息。
  • 私聊:写入对应用户的管道中

main.go

main.go一般是服务器配置启动

1
2
3
4
5
6
package main

func main() {
Server := NewServer("127.0.0.1", 8080)
Server.Start()
}

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
package main

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

type Server struct {
IP string
Port int
UserMap map[string]*User

maplock sync.RWMutex
//消息广播的channel
Message chan string
}

func NewServer(ip string, port int) *Server {
server := &Server{ //指向server指针
IP: ip,
Port: port,
UserMap: make(map[string]*User),
Message: make(chan string),
}
return server
}

// 将消息发送给所有用户的管道
func (server *Server) ListenMessage() {
fmt.Println("广播消息协程启动")
for {
msg := <-server.Message
server.maplock.Lock()
for _, user := range server.UserMap {
user.C <- msg
}
server.maplock.Unlock()
}
}

func (server *Server) BroadCast(user *User, msg string) {
sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg
//fmt.Printf("广播消息: %s\n", sendMsg)
server.Message <- sendMsg
}

func (server *Server) Handler(conn net.Conn) {
fmt.Println("链接建立成功 ip:", conn.RemoteAddr().String())
user := NewUser(conn, server)
//fmt.Println(user) 原来这里发生了空指针异常,打印user结构体发现是为给server字段赋值
user.Online()

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
}
msg := string(buf[:n-1])
fmt.Println("收到客户端", user.Name, "发来的消息:", msg)
user.DoMessage(msg)
islive <- true
}
}()
for {
select {
case <-islive:
case <-time.After(time.Second * 100):
user.SendMsg("服务器100秒没有收到你的消息,强制关闭连接")
close(user.C)
conn.Close()
//user.Offline() 不用退出,协程在handler函数退出后依旧会执行,检测
return //runtime.Goexit()
}
}
}

func (server *Server) Start() {
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", server.IP, server.Port))
if err != nil {
fmt.Println("net.Listen err:", err)
return
}
defer listener.Close()
go server.ListenMessage() //启动一个goroutine来发送广播消息到每个用户
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("listener.Accept err:", err)
continue //当前链接有问题,跳过直接接收下一链接
}
go server.Handler(conn)
}

}


user.go

这里主要是处理用户发送过来的消息(who、rename私聊),并将服务器消息返回给用户。

这里我使用nanoid替换了以name为索引的map,使得每次更新map不用delete。

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
package main

import (
"fmt"
"net"
"strings"

gonanoid "github.com/matoous/go-nanoid/v2"
)

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

func NewUser(conn net.Conn, server *Server) *User {
userAddr := conn.RemoteAddr().String()
id, err := gonanoid.New()
if err != nil {
fmt.Println("生成id失败")
}
user := &User{
Name: userAddr,
Addr: userAddr,
C: make(chan string),
conn: conn,
server: server,
nanoid: id,
}
go user.ListenMessage()
fmt.Println("成功创建一个用户:", user.Name, "Nanoid", user.nanoid)
return user
}

func (user *User) Online() {
user.server.maplock.Lock()
user.server.UserMap[user.nanoid] = user
user.server.maplock.Unlock()
user.server.BroadCast(user, "已上线") //这里类似函数指针的调用方式,参数接收一个指针
}

func (user *User) Offline() {
user.server.maplock.Lock()
delete(user.server.UserMap, user.nanoid)
user.server.maplock.Unlock()
user.server.BroadCast(user, "已下线")
}

// 用户将消息发送给服务器
func (this *User) DoMessage(msg string) {
if msg == "who" {
this.SendMsg("当前在线用户列表:\n")
this.server.maplock.Lock()
for _, user := range this.server.UserMap {
send := "[" + user.Addr + "]" + user.Name + ":" + "在线\n"
this.SendMsg(send)
//fmt.Println(user.Name)
}
this.server.maplock.Unlock()
} else if len(msg) > 7 && msg[:7] == "rename|" {
newName := strings.Split(msg, "rename|")[1]
this.server.maplock.Lock()
for _, user := range this.server.UserMap {
if user.Name == newName {
this.SendMsg("该用户名已存在")
this.server.maplock.Unlock()
return
}
}
this.Name = newName
this.server.maplock.Unlock()
} else if len(msg) > 4 && msg[:3] == "to|" {
remoterName := strings.Split(msg, "|")[1] //注意分割
if remoterName == "" {
this.SendMsg("请输入用户名")
return
}
content := strings.Split(msg, "|")[2]
if content == "" {
this.SendMsg("请输入内容")
return
}
fmt.Println(remoterName, content)
for _, user := range this.server.UserMap {
if user.Name == remoterName {
//fmt.Println("私聊")
user.SendMsg(this.Name + "对您说:" + content)
return
}
}

} else {
this.server.BroadCast(this, msg)
}

}

// 监听当前用户的channel将消息发向该用户
func (user *User) ListenMessage() {
fmt.Println("用户", user.Name, "监听消息开始")
for {
msg := <-user.C
user.conn.Write([]byte(msg + "\n"))
}
}

func (user *User) SendMsg(msg string) {
user.conn.Write([]byte(msg))
}

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
197
198
199
200
201
202
203
204
205
package main

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

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

func NewClient(serverIp string, serverPort int) *Client {
//创建客户端对象
client := &Client{
ServerIp: serverIp,
ServerPort: serverPort,
flag: 999,
}

//链接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
}

// 处理server回应的消息, 直接显示到标准输出即可
func (client *Client) DealResponse() {
//一旦client.conn有数据,就直接copy到stdout标准输出上, 永久阻塞监听
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) 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) 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) 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) Run() {
for client.flag != 0 {
for client.menu() != true { //确保成功进入一种模式
}

//根据不同的模式处理不同的业务
switch client.flag {
case 1:
//公聊模式
client.PublicChat()
break
case 2:
//私聊模式
client.PrivateChat()
break
case 3:
//更新用户名
client.UpdateName()
break
}
}
}

var serverIp string
var serverPort int

// ./client -ip 127.0.0.1 -port 8888
// init()程序启动时自动被调用。
// y
func init() { //
flag.StringVar(&serverIp, "ip", "127.0.0.1", "设置服务器IP地址(默认是127.0.0.1)") // -ip 127.0.0.1
flag.IntVar(&serverPort, "port", 8080, "设置服务器端口(默认是8080)") // -port 8080
}

func main() {
//命令行解析
flag.Parse()

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

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

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

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