文章目录
- package main import ( “fmt” “net” “sync” “time” ) type Client struct { conn net.Conn username string } type ChatServer struct { clients map[*Client]bool lock sync.Mutex } func NewChatServer() *ChatServer { return &ChatServer{ clients: make(map[*Client]bool), } } func (server *ChatServer) addClient(client *Client) { server.lock.Lock() defer server.lock.Unlock() server.clients[client] = true } func (server *ChatServer) removeClient(client *Client) { server.lock.Lock() defer server.lock.Unlock() delete(server.clients, client) } func (server *ChatServer) broadcastMessage(message string, sender *Client) { server.lock.Lock() defer server.lock.Unlock() for client := range server.clients { if client != sender { _, err := client.conn.Write([]byte(message)) if err != nil { fmt.Println(“发送消息失败:”, err) } } } } func handleConnection(conn net.Conn, server *ChatServer) { defer conn.Close() var client *Client fmt.Println(“新的客户端连接:”, conn.RemoteAddr()) // 为每个客户端创建唯一的连接对象 client = &Client{conn: conn} // 发送欢迎信息 conn.Write([]byte(“欢迎加入聊天室! 请输入你的昵称:n”)) // 获取用户名 var username string fmt.Fscan(conn, &username) client.username = username server.addClient(client) // 向所有客户端广播消息,通知新用户加入 server.broadcastMessage(client.username+” 加入了聊天室n”, client) // 处理客户端消息 go func() { reader := make([]byte, 1024) for { length, err := conn.Read(reader) if err != nil { fmt.Println(“客户端断开:”, client.username) server.removeClient(client) break } message := fmt.Sprintf(“%s: %s”, client.username, string(reader[:length])) server.broadcastMessage(message+”n”, client) } }() // 保持主线程 for { time.Sleep(time.Second * 1) } } func main() { server := NewChatServer() // 启动服务器并监听端口 listener, err := net.Listen(“tcp”, “:8080”) if err != nil { fmt.Println(“无法启动服务器:”, err) return } defer listener.Close() fmt.Println(“聊天室服务端已启动,监听端口 8080…”) for { conn, err := listener.Accept() if err != nil { fmt.Println(“连接错误:”, err) continue } go handleConnection(conn, server) } }
- package main import ( “fmt” “net” “os” “bufio” “strings” ) func main() { fmt.Print(“请输入聊天室服务器的IP地址: “) var serverIP string fmt.Scanln(&serverIP) conn, err := net.Dial(“tcp”, serverIP+”:8080″) if err != nil { fmt.Println(“连接服务器失败:”, err) return } defer conn.Close() fmt.Print(“请输入你的昵称: “) var username string fmt.Scanln(&username) conn.Write([]byte(username + “n”)) // 读取服务器的消息并显示 go func() { reader := bufio.NewReader(conn) for { message, _ := reader.ReadString(‘n’) fmt.Print(message) } }() // 输入消息并发送到服务器 for { fmt.Print(“请输入消息: “) reader := bufio.NewReader(os.Stdin) message, _ := reader.ReadString(‘n’) message = strings.TrimSpace(message) if message == “exit” { fmt.Println(“退出聊天室…”) break } conn.Write([]byte(message + “n”)) } }
- 使用 Go 内置的 net 包,服务端监听指定端口,客户端通过 net.Dial 连接到服务器。
- 使用 Goroutine 处理客户端输入和服务器的输出,确保客户端可以实时接收消息。 每当一个客户端连接,服务器会为其创建一个 Goroutine 来处理该客户端的消息。 go func() { reader := make([]byte, 1024) for { length, err := conn.Read(reader) if err != nil { fmt.Println(“客户端断开:”, client.username) server.removeClient(client) break } message := fmt.Sprintf(“%s: %s”, client.username, string(reader[:length])) server.broadcastMessage(message+”n”, client) } }()
- 每当有客户端发送消息时,服务器会通过 broadcastMessage 方法将消息广播给所有其他客户端。 func (server *ChatServer) broadcastMessage(message string, sender *Client) { server.lock.Lock() defer server.lock.Unlock() for client := range server.clients { if client != sender { _, err := client.conn.Write([]byte(message)) if err != nil { fmt.Println(“发送消息失败:”, err) } } } }
- 当客户端断开连接时,服务器会清理相应的资源,确保不再向其发送消息。 server.removeClient(client)
目录
- 一、实战背景
- 二、实战目标
- 三、完整代码实现
- 1. 服务端实现(server.go)
- 2. 客户端实现(client.go)
- 四、运行方式
- 1. 启动聊天室服务端
- 2. 启动客户端(多个终端可以启动)
- 五、关键技术点解析
- 1. 使用 TCP 连接实现客户端与服务端的通信
- 2. 客户端并发处理
- 3. 广播消息
- 4. 处理客户端的断开连接
- 六、可扩展方向
- 七、小结
本篇为《Go语言100个实战案例 · 网络与并发篇》第9篇,介绍如何使用 Go 编写一个简易的聊天室服务端。聊天室的实现涉及到并发连接管理、消息广播和客户端管理等重要的并发编程概念。通过本案例,你将学会如何构建一个高效、灵活的聊天系统。
即时通讯是现代互联网应用中不可或缺的功能之一。无论是社交网络、企业沟通工具,还是游戏中的实时聊天,聊天室服务端都是它们的重要组成部分。
在本案例中,我们将创建一个简单的TCP聊天服务器,支持多个客户端并发连接,通过广播的方式实时发送消息。
我们将实现一个基本的聊天室服务器,具备以下功能:
- 支持多个客户端同时连接
- 支持消息广播,所有连接的客户端可以接收到其他客户端发送的消息
- 客户端可以输入消息发送给所有人
- 客户端断开时,服务器能正常处理
package main
import (
"fmt"
"net"
"sync"
"time"
)
type Client struct {
conn net.Conn
username string
}
type ChatServer struct {
clients map[*Client]bool
lock sync.Mutex
}
func NewChatServer() *ChatServer {
return &ChatServer{
clients: make(map[*Client]bool),
}
}
func (server *ChatServer) addClient(client *Client) {
server.lock.Lock()
defer server.lock.Unlock()
server.clients[client] = true
}
func (server *ChatServer) removeClient(client *Client) {
server.lock.Lock()
defer server.lock.Unlock()
delete(server.clients, client)
}
func (server *ChatServer) broadcastMessage(message string, sender *Client) {
server.lock.Lock()
defer server.lock.Unlock()
for client := range server.clients {
if client != sender {
_, err := client.conn.Write([]byte(message))
if err != nil {
fmt.Println("发送消息失败:", err)
}
}
}
}
func handleConnection(conn net.Conn, server *ChatServer) {
defer conn.Close()
var client *Client
fmt.Println("新的客户端连接:", conn.RemoteAddr())
// 为每个客户端创建唯一的连接对象
client = &Client{conn: conn}
// 发送欢迎信息
conn.Write([]byte("欢迎加入聊天室! 请输入你的昵称:n"))
// 获取用户名
var username string
fmt.Fscan(conn, &username)
client.username = username
server.addClient(client)
// 向所有客户端广播消息,通知新用户加入
server.broadcastMessage(client.username+" 加入了聊天室n", client)
// 处理客户端消息
go func() {
reader := make([]byte, 1024)
for {
length, err := conn.Read(reader)
if err != nil {
fmt.Println("客户端断开:", client.username)
server.removeClient(client)
break
}
message := fmt.Sprintf("%s: %s", client.username, string(reader[:length]))
server.broadcastMessage(message+"n", client)
}
}()
// 保持主线程
for {
time.Sleep(time.Second * 1)
}
}
func main() {
server := NewChatServer()
// 启动服务器并监听端口
listener, err := net.Listen("tcp", ":8080")
if err != nil {
fmt.Println("无法启动服务器:", err)
return
}
defer listener.Close()
fmt.Println("聊天室服务端已启动,监听端口 8080...")
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("连接错误:", err)
continue
}
go handleConnection(conn, server)
}
}
package main
import (
"fmt"
"net"
"os"
"bufio"
"strings"
)
func main() {
fmt.Print("请输入聊天室服务器的IP地址: ")
var serverIP string
fmt.Scanln(&serverIP)
conn, err := net.Dial("tcp", serverIP+":8080")
if err != nil {
fmt.Println("连接服务器失败:", err)
return
}
defer conn.Close()
fmt.Print("请输入你的昵称: ")
var username string
fmt.Scanln(&username)
conn.Write([]byte(username + "n"))
// 读取服务器的消息并显示
go func() {
reader := bufio.NewReader(conn)
for {
message, _ := reader.ReadString('n')
fmt.Print(message)
}
}()
// 输入消息并发送到服务器
for {
fmt.Print("请输入消息: ")
reader := bufio.NewReader(os.Stdin)
message, _ := reader.ReadString('n')
message = strings.TrimSpace(message)
if message == "exit" {
fmt.Println("退出聊天室...")
break
}
conn.Write([]byte(message + "n"))
}
}
go run server.go
输出示例:
聊天室服务端已启动,监听端口 8080…
go run client.go
输入聊天室服务器的 IP 地址及昵称。
客户端输入示例:
请输入聊天室服务器的IP地址: 127.0.0.1
请输入你的昵称: Alice
请输入消息: Hello, everyone!
服务器端会接收到来自客户端的消息并广播:
新的客户端连接: 127.0.0.1:54321
Alice 加入了聊天室
使用 Go 内置的 net 包,服务端监听指定端口,客户端通过 net.Dial 连接到服务器。
- 使用 Goroutine 处理客户端输入和服务器的输出,确保客户端可以实时接收消息。
- 每当一个客户端连接,服务器会为其创建一个 Goroutine 来处理该客户端的消息。
go func() {
reader := make([]byte, 1024)
for {
length, err := conn.Read(reader)
if err != nil {
fmt.Println("客户端断开:", client.username)
server.removeClient(client)
break
}
message := fmt.Sprintf("%s: %s", client.username, string(reader[:length]))
server.broadcastMessage(message+"n", client)
}
}()
每当有客户端发送消息时,服务器会通过 broadcastMessage 方法将消息广播给所有其他客户端。
func (server *ChatServer) broadcastMessage(message string, sender *Client) {
server.lock.Lock()
defer server.lock.Unlock()
for client := range server.clients {
if client != sender {
_, err := client.conn.Write([]byte(message))
if err != nil {
fmt.Println("发送消息失败:", err)
}
}
}
}
当客户端断开连接时,服务器会清理相应的资源,确保不再向其发送消息。
server.removeClient(client)
| 功能方向 | 实现建议 |
|---|---|
| 消息存储 | 使用文件或数据库存储聊天记录,以便查看历史消息 |
| 用户身份验证 | 在用户加入聊天室前进行身份验证 |
| 私聊功能 | 支持用户之间的私聊,发送消息时指定目标用户名 |
| 聊天室管理 | 管理员可以踢出不合规用户或修改聊天室设置 |
| 图形界面 | 为聊天客户端增加图形界面(GUI),提升用户体验 |
通过本案例,你学会了如何使用 Go 构建一个简易的聊天室服务端,掌握了以下关键技术:
- 使用 TCP 进行客户端和服务端的通信
- 使用 Goroutine 实现并发处理多个客户端
- 实现消息广播功能
- 管理客户端连接和断开
这是构建高效聊天系统、即时通讯工具、多人在线游戏的基础。
到此这篇关于Go语言编写一个简易聊天室服务端的实现的文章就介绍到这了,更多相关Go语言 聊天室服务端内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!
您可能感兴趣的文章:
- golang实现简单的udp协议服务端与客户端示例
- Golang实现的聊天程序服务端和客户端代码分享
- go语言实现http服务端与客户端的例子
- go标准库net/http服务端的实现示例
- Go语言实现服务端消息接收和发送
- Golang原生rpc(rpc服务端源码解读)
- Go gRPC服务端流式RPC教程示例
- go微服务PolarisMesh源码解析服务端启动流程