client端
Go
复制代码
package main
import (
"bufio"
"chatroom/utils"
"fmt"
"net"
"os"
)
func main() {
// 获取用户名
var flag = true
var username string
for flag {
fmt.Print("请输入您的网名: ")
username = utils.ReadLine()
if username == "" {
fmt.Println("未输入用户名,请从新输入!")
} else {
flag = false
}
}
//fmt.Print("请输入您的网名: ")
//username := utils.ReadLine()
// 连接到服务器
conn, err := net.Dial("tcp", "127.0.0.1:8888")
if err != nil {
fmt.Println("连接服务器失败:", err)
os.Exit(1)
}
defer conn.Close()
// 发送用户名到服务器
_, err = conn.Write([]byte(username + "\n"))
if err != nil {
fmt.Println("发送用户名失败:", err)
os.Exit(1)
}
// 启动接收消息的协程
go receiveMessages(conn)
// 发送消息
fmt.Println("连接到服务器!输入 'exit' 退出,输入check可以显示展示在线用户")
fmt.Println("输入private进行用户私聊")
for {
fmt.Print("> ")
message := utils.ReadLine()
if message == "exit" {
break
}
// 发送消息
_, err = conn.Write([]byte(message + "\n"))
if err != nil {
fmt.Println("发送消息失败:", err)
break
}
}
// 发送离开消息
conn.Write([]byte("exit\n"))
fmt.Println("你已退出聊天室")
}
// receiveMessages 从服务器接收消息并打印
func receiveMessages(conn net.Conn) {
reader := bufio.NewReader(conn)
for {
message, err := reader.ReadString('\n')
if err != nil {
fmt.Println("与服务器连接断开")
os.Exit(0)
}
fmt.Print(utils.TrimNewLine(message) + "\n")
}
}
// readLine 从标准输入读取一行
//func readLine() string {
// reader := bufio.NewReader(os.Stdin)
// line, err := reader.ReadString('\n')
// if err != nil {
// fmt.Println("Error reading input:", err)
// return ""
// }
// return strings.TrimSpace(line)
//}
// trimNewLine 去掉字符串末尾的换行符
//func trimNewLine(s string) string {
// return strings.TrimRight(s, "\n")
//}
server端:
Go
复制代码
package main
import (
"bufio"
"chatroom/common"
"fmt"
"net"
"strings"
"sync"
)
func main() {
// 创建服务器
server := &common.Server{
//Clients: make(map[string]*common.Client),
Clients: sync.Map{},
}
// 监听端口
listener, err := net.Listen("tcp", "0.0.0.0:8888")
if err != nil {
fmt.Println("服务器启动失败:", err)
return
}
defer listener.Close()
fmt.Println("服务器已启动,等待客户端连接...")
// 接收客户端连接
for {
conn, err2 := listener.Accept()
if err2 != nil {
fmt.Println("接受连接失败:", err2)
continue
}
// 读取用户名
reader := bufio.NewReader(conn)
username, err3 := reader.ReadString('\n')
if err3 != nil {
fmt.Println("读取用户名失败:", err3)
conn.Close()
continue
}
username = strings.TrimSpace(username)
// 添加客户端
if !server.AddClient(conn, username) {
// 用户名重复,连接已在addClient中关闭
continue
}
//var count int = 0
//server.Clients.Range(func(key, value interface{}) bool {
// count++
// return true
//})
// 在clients map中查找客户端
//server.Mutex.Lock()
//client, exists := server.Clients[username]
//server.Mutex.Unlock()
value, ok := server.Clients.Load(username)
if !ok {
continue
}
//if !exists {
// conn.Close()
// continue
//}
client := value.(*common.Client)
// 启动处理客户端的协程
go server.ProcessClient(client)
}
}
// Client 代表一个客户端连接
//type Client struct {
// conn net.Conn
// name string
//}
//Server 代表聊天服务器
//type Server struct {
// clients map[string]*common.Client
// mutex sync.Mutex
//}
//// broadcast 在调用前必须持有锁
//func (s *Server) broadcast(message string, excludeClient *common.Client) {
// // 注意:这个方法假设调用者已经持有 s.mutex 锁
// for _, client := range s.clients {
// if excludeClient != nil && client == excludeClient {
// continue // 跳过发送消息的客户端
// }
//
// _, err := client.Conn.Write([]byte(message + "\n"))
// if err != nil {
// fmt.Printf("向客户端 %s 广播消息失败: %v\n", client.Name, err)
// // 不在这里移除客户端,由上层处理
// }
// }
//}
//
//func (s *Server) addClient(conn net.Conn, name string) bool {
// s.mutex.Lock()
// defer s.mutex.Unlock()
//
// // 检查用户名是否已存在
// if _, exists := s.clients[name]; exists {
// conn.Write([]byte("用户名已存在,请重新输入\n"))
// conn.Close()
// return false
// }
//
// // 创建新客户端
// client := &common.Client{
// Conn: conn,
// Name: name,
// }
//
// // 添加到客户端列表
// s.clients[name] = client
//
// // 发送欢迎消息
// welcomeMsg := fmt.Sprintf("欢迎 %s 加入聊天室!当前在线人数: %d\n", name, len(s.clients))
// conn.Write([]byte(welcomeMsg))
//
// // 广播新用户加入,使用同一个锁
// s.broadcast(fmt.Sprintf("%s 加入了聊天室", name), nil)
// return true
//}
//
//func (s *Server) removeClient(client *common.Client) {
// s.mutex.Lock()
// defer s.mutex.Unlock()
//
// // 从客户端列表中移除
// if _, exists := s.clients[client.Name]; exists {
// delete(s.clients, client.Name)
// client.Conn.Close()
// }
//}
//
//func (s *Server) processClient(client *common.Client) {
// defer func() {
// // 先广播离开消息
// s.mutex.Lock()
// s.broadcast(fmt.Sprintf("%s 离开了聊天室", client.Name), nil)
// s.mutex.Unlock()
//
// // 然后移除客户端
// s.removeClient(client)
// }()
//
// reader := bufio.NewReader(client.Conn)
// for {
// message, err := reader.ReadString('\n')
// if err != nil {
// // 正常断开连接
// return
// }
//
// content := strings.TrimSpace(message)
// if content == "exit" {
// return
// }
//
// // 广播消息前获取锁
// s.mutex.Lock()
// s.broadcast(fmt.Sprintf("%s: %s", client.Name, content), client)
// s.mutex.Unlock()
// }
//}
common(结构体,及方法):
Go
复制代码
package common
import (
"bufio"
"fmt"
"net"
"strings"
"sync"
)
type Client struct {
Conn net.Conn
Name string
}
type Server struct {
//Clients map[string]*Client
Clients sync.Map
//Mutex sync.Mutex
}
// broadcast 在调用前必须持有锁
func (s *Server) Broadcast(message string, excludeClient *Client) {
// 注意:这个方法假设调用者已经持有 s.mutex 锁
//for _, client := range s.Clients {
// if excludeClient != nil && client == excludeClient {
// continue // 跳过发送消息的客户端
// }
//
// _, err := client.Conn.Write([]byte(message + "\n"))
// if err != nil {
// fmt.Printf("客户端 %s离开聊天室, 发送消息失败。\n", client.Name)
// // 不在这里移除客户端,由上层处理
// }
//}
s.Clients.Range(func(key, value interface{}) bool {
client := value.(*Client)
if excludeClient != nil && client == excludeClient {
// 如果是用户本身,就跳过消息广播
return true
}
_, err := client.Conn.Write([]byte(message + "\n"))
if err != nil {
fmt.Printf("客户端 %s离开聊天室, 发送消息失败。\n", client.Name)
}
return true
})
}
// 在 common 包的 Server 结构体中添加这个方法 获取到在线人数
func (s *Server) GetOnlineCount() int {
count := 0
s.Clients.Range(func(key, value interface{}) bool {
count++
return true // 继续遍历
})
return count
}
//添加用户
func (s *Server) AddClient(conn net.Conn, name string) bool {
// 检查用户是否已存在
if _, exist := s.Clients.Load(name); exist {
conn.Write([]byte("网名已存在!!!!!!\n"))
conn.Close()
return false
}
fmt.Printf("%s进入聊天室。\n", name)
//创建新的客户端
client := &Client{
Conn: conn,
Name: name,
}
// 存储客户列表
s.Clients.Store(name, client)
conn.Write([]byte(fmt.Sprintf("欢迎 %s 加入聊天室!当前在线人数: %d\n", name, s.GetOnlineCount())))
// 广播新用户加入
s.Broadcast(fmt.Sprintf("%s 加入了聊天室", name), client)
return true
//s.Mutex.Lock()
//defer s.Mutex.Unlock()
//
////_, exists := s.Clients[name]
//// 检查用户名是否已存在
//if _, exists := s.Clients[name]; exists {
// conn.Write([]byte("用户名已存在!!!!!!\n"))
// conn.Close()
// return false
//}
//fmt.Printf("%s进入聊天室。\n", name)
//// 创建新客户端
//client := &Client{
// Conn: conn,
// Name: name,
//}
//
//// 添加到客户端列表
//s.Clients[name] = client
//
//// 发送欢迎消息
//welcomeMsg := fmt.Sprintf("欢迎 %s 加入聊天室!当前在线人数: %d\n", name, len(s.Clients))
//conn.Write([]byte(welcomeMsg))
//
//// 广播新用户加入,使用同一个锁
//s.Broadcast(fmt.Sprintf("%s 加入了聊天室", name), client)
//return true
}
//移除用户
func (s *Server) RemoveClient(client *Client) {
s.Clients.Delete(client.Name)
client.Conn.Close()
client.Conn.Write([]byte(fmt.Sprintf("当前在线人数: %d\n", s.GetOnlineCount())))
//s.Mutex.Lock()
//defer s.Mutex.Unlock()
//
//// 从客户端列表中移除
//if _, exists := s.Clients[client.Name]; exists {
// delete(s.Clients, client.Name)
// client.Conn.Close()
//}
}
func (s *Server) ProcessClient(client *Client) {
defer func() {
// 广播离开消息
s.Broadcast(fmt.Sprintf("%s 离开了聊天室\n", client.Name), nil)
// 移除客户端
s.RemoveClient(client)
}()
reader := bufio.NewReader(client.Conn)
for {
message, err := reader.ReadString('\n')
if err != nil {
// 正常断开连接
return
}
content := strings.TrimSpace(message)
// 如果输入 exit 则退出程序
if content == "exit" {
fmt.Printf("%s离开聊天室", client.Name)
return
}
if content == "check" {
//查看在线用户
s.ViewOnlineClient(client)
//跳过本次循环
continue
}
if content == "private" {
//进入私聊模式
s.PrivateChat(client)
client.Conn.Write([]byte("已返回广播模式!\n"))
continue
}
// 广播消息
s.Broadcast(fmt.Sprintf("%s: %s", client.Name, content), client)
}
//defer func() {
// // 先广播离开消息
// s.Mutex.Lock()
// s.Broadcast(fmt.Sprintf("%s 离开了聊天室", client.Name), nil)
// s.Mutex.Unlock()
//
// // 然后移除客户端
// s.RemoveClient(client)
//}()
//
//reader := bufio.NewReader(client.Conn)
//for {
// message, err := reader.ReadString('\n')
// if err != nil {
// // 正常断开连接
// return
// }
//
// content := strings.TrimSpace(message)
// if content == "exit" {
// fmt.Printf("%s离开聊天室。", client.Name)
// return
// }
//
// // 广播消息前获取锁
// s.Mutex.Lock()
// s.Broadcast(fmt.Sprintf("%s: %s", client.Name, content), client)
// s.Mutex.Unlock()
//}
}
//查看在线用户
func (s *Server) ViewOnlineClient(client *Client) {
//s.Clients
var keys string
s.Clients.Range(func(key, value interface{}) bool {
// 因为知道 key 是 string,所以直接断言
if k, ok := key.(string); ok {
//keys = append(keys, k)
keys += k + " "
} else {
// 防御性编程
fmt.Printf("警告: key 不是 string 类型: %v (%T)\n", key, key)
}
return true // 继续遍历下一个
})
client.Conn.Write([]byte("在线用户名展示" + keys + "\n"))
}
//私聊模式
func (s *Server) PrivateChat(client *Client) {
conn := client.Conn
reader := bufio.NewReader(client.Conn)
var targetUsername string
var targetClient *Client
// 选择聊天对象
for {
conn.Write([]byte("请输入聊天对象网名:\n"))
message, err := reader.ReadString('\n')
if err != nil {
return
}
targetUsername = strings.TrimSpace(message)
if targetUsername == "" {
conn.Write([]byte("未输入用户名,请重新输入:\n"))
continue
}
// 检查用户是否存在
found := false
s.Clients.Range(func(key, value interface{}) bool {
if k, ok := key.(string); ok && k == targetUsername {
found = true
if v, ok := value.(*Client); ok {
targetClient = v
}
return false
}
return true
})
if !found {
conn.Write([]byte("用户不在线或不存在,请重新输入:\n"))
continue
}
// 不能和自己私聊
if targetUsername == client.Name {
conn.Write([]byte("不能给自己发私聊消息!\n"))
continue
}
// 找到有效目标,退出循环
break
}
conn.Write([]byte("----------------进入私聊模式---------------------\n"))
conn.Write([]byte(fmt.Sprintf("正在与 %s 私聊 (输入 'exits' 退出私聊)\n", targetUsername)))
// 私聊消息循环
for {
conn.Write([]byte(client.Name + "> "))
message, err := reader.ReadString('\n')
if err != nil {
conn.Write([]byte("读取信息失败,返回公聊模式...\n"))
return
}
message = strings.TrimSpace(message)
if message == "" {
continue // 忽略空消息
}
if message == "exits" {
conn.Write([]byte("退出私聊,返回公聊模式...\n"))
return
}
// 发送消息给目标用户
if targetClient != nil {
_, err = targetClient.Conn.Write([]byte(fmt.Sprintf("[私聊]%s: %s\n", client.Name, message)))
if err != nil {
conn.Write([]byte("消息发送失败,对方可能已离线,返回公聊模式...\n"))
return
}
}
}
}
工具包utils:
Go
复制代码
package utils
import (
"bufio"
"fmt"
"os"
"strings"
)
//消息处理
// ReadLine 从标准输入读取一行
func ReadLine() string {
reader := bufio.NewReader(os.Stdin)
line, err := reader.ReadString('\n')
if err != nil {
fmt.Println("用户退出")
//fmt.Println("Error reading input:")
return ""
}
return strings.TrimSpace(line)
}
// TrimNewLine 去掉字符串末尾的换行符
func TrimNewLine(s string) string {
return strings.TrimRight(s, "\n")
}