服务端代码

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

import (
"encoding/json"
"fmt"
"net"
"sync"
)

var (
Clients = make(map[string]net.Conn)
ClientMu = &sync.Mutex{}
MessageCh = make(chan []byte)
)

func HandlerConn(conn net.Conn) {
// 注册连接到map
ClientMu.Lock()
nickname := conn.RemoteAddr().String()
Clients[nickname] = conn
ClientMu.Unlock()
defer func() {
ClientMu.Lock()
fmt.Println("删除", nickname)
delete(Clients, nickname)
ClientMu.Unlock()
}()
systemMessage := MessageInfo{
From: "系统",
Text: fmt.Sprintf("欢迎%s加入聊天室", conn.RemoteAddr().String()),
}
message, _ := json.Marshal(systemMessage)
MessageCh <- message
ReadMessage(conn, MessageCh)
}

func HandlerMessage() {
for {
message := <-MessageCh
// 解码数据
messageInfo := MessageToStruct(message)
for addr, otherConn := range Clients {
if addr == messageInfo.From {
continue
}
SendMessage(otherConn, message)
}
}
}
func main() {

tcp, err := net.Listen("tcp", ":8080")
if err != nil {
fmt.Println(err)
}
fmt.Println("服务器启动,监听 8080 端口")
go HandlerMessage()
for {
// 等待被连接
conn, err := tcp.Accept()
if err != nil {
fmt.Println("连接失败:", err)
continue
}
fmt.Println("连接成功:", conn.RemoteAddr())
go HandlerConn(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
43
44
45
46
47
48
49
50
51
package main

import (
"bufio"
"encoding/json"
"fmt"
"net"
"os"
"strings"
"time"
)

func InputAndSend(conn net.Conn) {
var text string
for {
fmt.Print("发送信息: ")
reader := bufio.NewReader(os.Stdin)
text, _ = reader.ReadString('\n')
text = strings.TrimSpace(text)
messageInfo := MessageInfo{
From: conn.LocalAddr().String(),
Text: text,
}
message, _ := json.Marshal(messageInfo)
SendMessage(conn, message)
}
}

func main() {
var MessageCh = make(chan []byte)
conn, err := net.Dial("tcp", "127.0.0.1:8080")
if err != nil {
fmt.Println("连接服务器失败:", err)
return
}
//defer conn.Close()
fmt.Println("连接服务器成功:", conn.LocalAddr())
// 启动一个goroutine来读取服务器的消息
go ReadMessage(conn, MessageCh)

// 发送初始消息
go InputAndSend(conn)
for {
message := <-MessageCh
time.Sleep(100 * time.Millisecond)
fmt.Print("\r\033[K")
messageInfo := MessageToStruct(message)
fmt.Printf("[%s]: %s\n", messageInfo.From, messageInfo.Text)
fmt.Print("发送信息: ")
}
}

公共代码

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

import (
"encoding/json"
"fmt"
"net"
)

type MessageInfo struct {
From string `json:"from"`
Text string `json:"text"`
}

func ReadMessage(conn net.Conn, ch chan []byte) {
defer conn.Close()
for {
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
fmt.Print("\r\033[K")
fmt.Println("断开连接:", conn.RemoteAddr())
systemMessage := MessageInfo{
From: "系统",
Text: fmt.Sprintf("%s 退出了聊天室", conn.RemoteAddr().String()),
}
message, _ := json.Marshal(systemMessage)
ch <- message
return
}

message := buf[:n]
ch <- message
}
}

func SendMessage(conn net.Conn, message []byte) {
_, err := conn.Write(message)
if err != nil {
fmt.Println("发送消息失败:", err)
return
}
}

func MessageToStruct(message []byte) MessageInfo {
var messageInfo MessageInfo
err := json.Unmarshal(message, &messageInfo)
if err != nil {
fmt.Println("解码失败", string(message))
}
return messageInfo
}

启动代码

1
2
go run server.go utils.go # 服务端
go run client.go utils.go # 客户端

核心逻辑

服务端收到连接后异步调用HandlerConn,把连接对象存储到Clients中,并广播欢迎信息到其他客户端,使用From排除消息来源者。

HandlerConn中同步调用ReadMessage收消息,ReadMessage返回后关闭连接, 在Clients中删除连接对象。

客户端异步调用InputAndSend,监听输入并发送,同步监听MessageCh,打印收到的消息。

项目使用MessageCh通道来处理消息,能输入消息的同时打印新消息。有个bug:消息打一半来消息就会清除掉正在输入的消息。