- 广播:函数将需要发送的数据发送到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 Message chan string }
func NewServer(ip string, port int) *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 server.Message <- sendMsg }
func (server *Server) Handler(conn net.Conn) { fmt.Println("链接建立成功 ip:", conn.RemoteAddr().String()) user := NewUser(conn, 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() return } } }
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() 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) } 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 { user.SendMsg(this.Name + "对您说:" + content) return } }
} else { this.server.BroadCast(this, msg) }
}
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 }
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 error:", 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) 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
func init() { flag.StringVar(&serverIp, "ip", "127.0.0.1", "设置服务器IP地址(默认是127.0.0.1)") flag.IntVar(&serverPort, "port", 8080, "设置服务器端口(默认是8080)") }
func main() { flag.Parse()
client := NewClient(serverIp, serverPort) if client == nil { fmt.Println(">>>>> 链接服务器失败...") return }
go client.DealResponse()
fmt.Println(">>>>>链接服务器成功...")
client.Run() }
|