go聊天室

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")
}
相关推荐
智驭未来掌门人5 分钟前
利用FFmpeg快速实现一个RTSP推流服务器
后端
程序员雷欧13 分钟前
AQS深度解析
开发语言·python
垚垚学技术_聚焦云原生17 分钟前
K8s Pod 完整生命周期详解
后端
JavaGuide22 分钟前
Github 史诗级故障,与此同时,Cursor 版「GitHub」正式上线!
前端·后端
不一样的少年_25 分钟前
修了 Bug、做了重构,为什么老板还是觉得你没产出?
前端·后端·程序员
水深火乐44 分钟前
安全地生成验证码、密码和 Token
后端
小强19881 小时前
SQL Server 慢查询怎么定位?一套从“卡”到“快”的完整排查流程
后端
水深火乐1 小时前
interface和any
后端
步行cgn1 小时前
MyBatis Error evaluating expression ‘ids‘. Return value (3) was not iterable 错误详
java·后端
yujunl1 小时前
总结一下U9客开的成果
开发语言