# lcs **Repository Path**: xkgo/lcs ## Basic Information - **Project Name**: lcs - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-04-13 - **Last Updated**: 2025-04-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # LCS - 长连接服务 (Long Connection Service) ## 功能描述 - [x] 基于 `github.com/coder/websocket` 实现长连接服务 - [x] 客户端可以和服务端构建长连接的时候,给这个连接绑定一个uid,表示谁登陆了 - [x] 服务端可以进行会话管理 - [x] 支持发布订阅,客户端可以订阅特定的topic,服务端可以根据topic发送广播 - [x] 服务端可以按照一个或者多个uid给客户端发送下行单播 - [x] 支持自定义二进制消息格式,减少数据传输量 - [x] 支持使用Redis存储客户端会话,实现分布式部署 ## 安装 ```bash go get github.com/xkgo/lcs ``` ## 使用方法 ### 服务端 ```go package main import ( "log" "net/http" "github.com/xkgo/lcs/server" ) func main() { // 创建长连接服务器 wsServer := server.NewServer() // 设置WebSocket处理路由 http.Handle("/ws", wsServer) // 启动HTTP服务器 log.Println("Starting WebSocket server on :8081") log.Fatal(http.ListenAndServe(":8081", nil)) } ``` ### 客户端 ```go package main import ( "context" "encoding/json" "fmt" "log" "github.com/coder/websocket" ) // Message 消息结构 type Message struct { Type string `json:"type"` Content string `json:"content,omitempty"` Topic string `json:"topic,omitempty"` } func main() { // 连接到WebSocket服务器,指定UID ctx := context.Background() conn, _, err := websocket.Dial(ctx, "ws://localhost:8081/ws?uid=user123", nil) if err != nil { log.Fatalf("Failed to connect: %v", err) } defer conn.Close(websocket.StatusNormalClosure, "client closing connection") // 订阅主题 subscribeMsg := Message{ Type: "subscribe", Topic: "news", } data, _ := json.Marshal(subscribeMsg) conn.Write(ctx, websocket.MessageText, data) // 接收消息 for { _, data, err := conn.Read(ctx) if err != nil { log.Printf("Error reading: %v", err) break } var msg Message json.Unmarshal(data, &msg) fmt.Printf("Received: %+v\n", msg) } } ``` ## 高级功能 ### 发布广播消息 ```go // 发布消息到指定主题 msg := server.Message{ Type: "text", Content: "Hello subscribers!", } msgBytes, _ := json.Marshal(msg) wsServer.Publish("news", msgBytes) ``` ### 发送单播消息 ```go // 发送消息到指定客户端 msg := server.Message{ Type: "text", Content: "Hello specific user!", } msgBytes, _ := json.Marshal(msg) wsServer.SendToClient("user123", msgBytes) ``` ### 获取客户端信息 ```go // 获取客户端数量 count := wsServer.GetClientCount() // 获取特定主题的订阅者数量 subCount := wsServer.GetSubscriberCount("news") ``` ## 示例 查看 `examples` 目录中的完整示例: - `examples/server.go`: 完整的服务端示例,包括HTTP API - `examples/client.go`: 客户端示例 ## 本地测试步骤 ### 命令行测试 ### 1. 启动服务端 ```bash # 在终端1中运行 go run examples/server.go ``` 服务端将在8081端口启动,并输出: ``` Starting WebSocket server on :8081 ``` ### 2. 启动客户端 ```bash # 在终端2中运行,指定用户ID为user123 go run examples/client.go user123 ``` 客户端将连接到服务端,订阅news主题,并输出: ``` Connected to WebSocket server with UID: user123 Subscribed to topic: news ``` 服务端会每30秒自动发送一条广播消息,客户端会收到并显示: ``` Received message: {Type:text Content:Server broadcast message: 2025-04-13T19:44:03+08:00 Topic:} ``` ### 3. 发送广播消息 ```bash # 在终端3中运行,向news主题发送广播消息 curl -X POST "http://localhost:8081/broadcast?topic=news" -d '{"type":"text","content":"广播消息测试"}' ``` 所有订阅了news主题的客户端都会收到这条消息: ``` Received message: {Type:text Content:广播消息测试 Topic:} ``` ### 4. 发送单播消息 ```bash # 在终端3中运行,向特定用户ID发送单播消息 curl -X POST "http://localhost:8081/send?uid=user123" -d '{"type":"text","content":"单播消息测试"}' ``` 指定用户ID的客户端会收到这条消息: ``` Received message: {Type:text Content:单播消息测试 Topic:} ``` ### 5. 查看客户端连接信息 ```bash # 在终端3中运行,获取当前连接的客户端数量 curl "http://localhost:8081/clients" ``` 返回结果: ```json {"count":1} ``` ### Web浏览器测试 除了命令行客户端外,我们还提供了一个HTML版本的WebSocket客户端,可以在浏览器中使用。 #### 1. 启动服务端 ```bash go run examples/server.go ``` #### 2. 打开HTML客户端 在浏览器中访问: ``` http://localhost:8081/client ``` #### 3. 使用HTML客户端 1. 输入用户ID(例如:user123) 2. 点击“连接”按钮 3. 连接成功后,客户端会自动订阅news主题 4. 在输入框中输入消息,点击“发送到news主题”按钮 5. 消息将被发送到news主题,所有订阅该主题的客户端都会收到消息 这个HTML客户端提供了直观的用户界面,可以方便地测试WebSocket连接和消息发送功能。 ### 二进制消息格式 除了标准的JSON消息格式外,本项目还支持自定义二进制消息格式,可以显著减少数据传输量。 #### 1. 使用二进制客户端 在浏览器中访问: ``` http://localhost:8081/client/binary ``` #### 2. 发送二进制消息 使用HTTP API发送二进制消息: ```bash curl -X POST "http://localhost:8081/broadcast?topic=news&binary=true" -d '{"type":"text","content":"使用二进制格式发送的消息"}' ``` #### 3. 在代码中使用二进制消息 ```go // 创建二进制消息 binMsg := binary.NewTextMessage("测试消息") // 发布到主题 wsServer.PublishBinary("news", binMsg) // 发送给特定客户端 wsServer.SendBinaryToClient("user123", binMsg) ``` #### 4. 二进制消息格式定义 二进制消息格式如下: ``` 1字节: 消息类型 (0=未知, 1=文本, 2=订阅, 3=取消订阅) 4字节: 内容长度 变长字节: 内容 4字节: 主题长度 变长字节: 主题 ``` 这种格式相比JSON更紧凑,特别是在消息内容较大时可以显著减少数据传输量。 ### 分布式部署 本项目支持使用Redis存储客户端会话信息,实现分布式部署。这意味着您可以运行多个服务器实例,实现负载均衡和高可用性。 #### 1. 使用Redis会话存储 在创建服务器时指定使用Redis会话存储: ```go // 创建Redis会话存储选项 redisOptions := server.RedisSessionOptions{ Addr: "localhost:6379", // Redis地址 Password: "", // Redis密码 DB: 0, // Redis数据库 ServerID: "server1", // 服务器ID KeyPrefix: "lcs:", // 键前缀 SessionTTL: time.Hour * 24, // 会话过期时间 } // 创建服务器选项 serverOptions := server.ServerOptions{ ReadTimeout: time.Minute, WriteTimeout: time.Second * 10, MaxMessageSize: 1024 * 1024, // 1MB ServerID: "server1", SessionStoreType: "redis", RedisOptions: redisOptions, } // 创建长连接服务器 wsServer := server.NewServer(serverOptions) ``` #### 2. 分布式部署注意事项 在分布式部署中,需要注意以下几点: 1. 每个服务器实例应该有一个唯一的`ServerID` 2. 每个服务器实例应该有一个唯一的`ServerAddr`,用于RPC通信 3. 所有服务器实例应该使用相同的Redis服务器 4. 当客户端连接到一个服务器实例时,其会话信息会存储在Redis中 5. 当向不在本地的客户端发送消息时,系统会自动通过RPC调用将消息转发到客户端所在的服务器 #### 3. 消息转发机制 当客户端连接到服务器时,服务器会将客户端信息(包括客户端ID、服务器ID和服务器地址)存储在Redis中。当需要向客户端发送消息时,服务器会: 1. 先检查客户端是否在本地连接 2. 如果不在本地,从 Redis 中获取客户端所在的服务器信息 3. 检查目标服务器是否就是当前服务器(优化) - 如果是当前服务器,直接尝试发送到本地客户端 - 这种情况可能发生在客户端断开连接后重新连接到同一个服务器,但Redis中的信息还没有更新 4. 如果目标服务器不是当前服务器,通过 RPC 调用将消息转发到客户端所在的服务器 5. 目标服务器接收到消息后,将其发送给本地的客户端 这种机制确保了在分布式部署中,无论客户端连接到哪个服务器实例,都能收到发送给它的消息。同时,通过检查目标服务器是否就是当前服务器,避免了不必要的RPC调用,提高了性能。 #### 4. 运行示例 **标准示例**(使用内存存储): ```bash cd examples/standard # 使用默认参数 go run server.go # 指定端口 go run server.go -http-port=8082 -rpc-port=9082 # 指定主机和端口 go run server.go -host=192.168.1.100 -http-port=8082 -rpc-port=9082 -server-id=server1 # 查看所有可用参数 go run server.go -help ``` **Redis示例**(使用Redis存储,支持分布式部署): ```bash # 确保Redis服务器已经运行 cd examples/redis # 使用默认参数 go run server.go # 指定参数 go run server.go -http-port=8082 -rpc-port=9082 -redis=localhost:6379 -server-id=server2 # 分布式部署示例(在不同的终端运行) # 第一个服务器 go run server.go -host=192.168.1.100 -http-port=8081 -rpc-port=9081 -server-id=server1 # 第二个服务器 go run server.go -host=192.168.1.101 -http-port=8082 -rpc-port=9082 -server-id=server2 # 查看所有可用参数 go run server.go -help ``` **注意**:服务器使用两个不同的端口: 1. HTTP端口(默认8081):用于HTTP API和WebSocket连接 2. RPC端口(默认9081):用于服务器之间的RPC通信 这样可以避免端口冲突,并且可以更灵活地配置网络。 在分布式部署中,客户端可以连接到任何一个服务器实例,并且可以接收来自任何服务器实例的消息。 这个示例将使用Redis存储客户端会话信息,并在端口8081上启动WebSocket服务器。