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")
}
相关推荐
Mr_Xuhhh1 小时前
pytest -- 指定⽤例执⾏顺序
开发语言·python·pytest
F_D_Z1 小时前
【解决办法】网络训练报错AttributeError: module ‘jax.core‘ has no attribute ‘Shape‘.
开发语言·python·jax
chenyuhao20241 小时前
MySQL索引特性
开发语言·数据库·c++·后端·mysql
2***s6721 小时前
【Go】Go语言基础学习(Go安装配置、基础语法)
服务器·学习·golang
oouy1 小时前
《Java泛型:给你的代码装上“快递分拣系统”,再也不会拆出一双鞋!》
后端
Python私教1 小时前
别再瞎折腾 LangChain 了:从 0 到 1 搭建 RAG 知识库的架构决策实录
后端
微学AI1 小时前
openGauss在AI时代的向量数据库应用实践与技术演进深度解析
后端
前端伪大叔1 小时前
第29篇:99% 的量化新手死在挂单上:Freqtrade 隐藏技能揭秘
后端·python·github
白衣鸽子1 小时前
【基础数据篇】数据格式化妆师:Formatter模式
后端·设计模式