开发者

使用Go语言实现简单聊天系统

开发者 https://www.devze.com 2024-10-06 10:26 出处:网络 作者: 小于负无穷
目录一、项目结构WebSocket 简介二、实现思路三、详细实现1. main.go - 启动 WebSocket 服务2. hub.go - 消息管理中心3. client.go - 处理客户端连接四、运行项目在互联网时代,聊天系
目录
  • 一、项目结构
    • WebSocket 简介
  • 二、实现思路
    • 三、详细实现
      • 1. main.go - 启动 WebSocket 服务
      • 2. hub.go - 消息管理中心
      • 3. client.go - 处理客户端连接
    • 四、运行项目

      在互联网时代,聊天系统是常见的应用场景之一。无论是即时通讯、在线客服还是多人游戏中的消息系统,聊天功能的实现都是必不可少的。本文将使用 Go 语言,结合 WebSocket 来构建一个简单的多人聊天室系统。

      一、项目结构

      首先,我们设计一个简单的项目结构,文件结构如下:

      go-chat/
      │
      ├── main.go          // 主程序
      ├── client.go        // 处理客户端连接
      └── hub.go           // 消息管理
      

      WebSocket 简介

      WebSocket 是一种基于 TCP 的网络协议,允许客户端和服务端建立持久的全双工通信连接。相比于传统的 HTTP 请求-响应模型,WebSocket 更加适合实时通信场景,因此它是实现聊天系统的理想选择。

      二、实现思路

      • 客户端连接管理:每个客户端通过 WebSocket 连接到服务器,服务器会为每个连接的客户端分配一个唯一的 connection
      • 消息广播:当某个客户端发送消息时,服务器将该消息广播给所有连接的客户端。
      • 并发处理:Go 原生支持并发编程,通过 Goroutine 和 Channel 可以轻松处理并发消息传递。

      三、详细实现

      1. main.go - 启动 WebSocket 服务

      package main
      
      import (
          "log"
          "net/http"
      )
      
      func main() {
          hub := newHub()
          go hub.run()
      
          http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
              phpserveWs(hub, w, r)
          })
      
          log.Println("服务器启动,监听端口 8080...")
          err := http.ListenAndServe(":8080", nil)
          if err != nil {
              log.Fatal("监听失败:", err)
          }
      }
      

      main.go 文件的作用是启动 HTTP 服务器,并在 /ws 路径上处理 WebSocket 连接请求。

      2. hub.go - 消息管理中心

      Hub 负责管理所有的客户端连接,以及消息的广播。

      package main
      
      // Hub 负责管理所有客户端的注册、注销及消息广播
      type Hub struct {
          clients    map[*Client]bool // 已连接的客户端
          broadcast  chan []byte      // 从客户端接收的广播消息
          register   chan *Client     // 注册请求
          unregister chan *Client     // 注销请求
      }
      
      func newHub() *Hub {
          return &Hub{
              clients:    make(map[*Client]bool),
              broadcast:  make(chan []byte),
              register:   make(chan *Client),
              unregister: make(chan *Client),
          }
      }
      
      func (h *Hub) run() {
          for {
              select {
              case client := <-h.register:
                  h.clients[client] = true
              case client := <-h.unregister:
                  if _, ok := h.clients[client]; ok {
                      delete(h.clients, client)
                      close(client.send)
                  }
              case message := <-h.broadcast:
                  for client := range h.clients {
                      select {
                  android    case client.send <- message:
                      default:
                          close(client.send)
                          delete(h.clients, client)
                      }
                  }
              }
          }
      }
      
      • clients:保存当前连接的所有客户端。
      • broadcast:一个通道,用于广播消息给所有客户端编程客栈
      • register/unregister:用于客户端连接和断开的注册和注销。

      3. client.go - 处理客户端连接

      每个客户端的连接由 Client 结构体表示,并包含了 WebSocket 连接和发送消息的通道。

      package main
      
      import (
          "github.com/gorilla/websocket"
          "log"
          "net/http"
          "time"
      )
      
      const (
          writeWait = 10 * time.Second
          pongWait  = 60 * time.Second
          pingPeriod = (pongWait * 9) / 10
      )
      
      var upgrader = websocket.Upgrader{
          ReadBufferSize:  1024,
          WriteBufferSize: 1024,
      }
      
      type Client struct {
          hub  *Hub
          conn *websocket.Conn
          send chan []byte
      }
      
      func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
          conn, err := upgrader.Upgrade(w, r, nil)
          if err != nil {
              log.Println("升级到 WebSocket 失败:", err)
              return
          }
          client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)}
          client.hub.register <- client
      
          go client.writePump()
          go client.readPump()
      }
      
      func (c *Client) readPump() {
          defer func() {
              c.hub.unregister <- c
              c.conn.Close()
          }()
          c.conn.SetReadLimit(512)
          c.conn.SetReadDeadline(time.Now().Add(pongWait))
          c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
      
          for {
              _, message, err := c.conn.ReadMessage()
              if err != nil {
                  if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
                      log.Printf("读取错误: %v", err)
                  }
                  break
              }
              c.hub.broadcast <- message
          }
      }
      
      func (c *Client) writePump() {
          ticker := time.NewTicker(pingPeriod)
          defer func() {
              ticker.Stop()
              c.conn.Close()
          }()
      
          for {
              select {
              case message, ok := <-c.send:
                  c.conn.SetWriteDeadline(time.Now().Add(writeWait))
                  if !ok {
                      c.conn.WriteMehttp://www.devze.comssage(websocket.CloseMessage, []byte{})
                      return
                  }
      
                  w编程客栈, err := c.conn.NextWriter(websocket.TextMessage)
                  if err != nil {
                      return
                  }
                  w.Write(message)
      
                  n := len(c.send)
                  for i := 0; i < n; i++ {
                      w.Write(<-c.send)
                  }
      
                  if err := w.Close(); err != nil {
                      return
                  }
      
              case <-ticker.C:
                  c.conn.SetWriteDeadline(time.Now().Add(writeWait))
                  if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
                      return
                  }
              }
          }
      }
      
      • serveWs:处理每个 WebSocket 连接请求,并为每个连接创建一个客户端实例。
      • readPump:从 WebSocket 连接中读取消息,并将消息广播到所有客户端。
      • writePump:负责将消息发送给客户端,并定期发送心跳检测(ping)消息以保持连接。

      四、运行项目

      首先,安装 WebSocket 依赖:

      go get github.com/gorilla/websocket
      

      编写前端 html 页面(可用于测试),例如 index.html

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Go 聊天室</title>
      </head>
      <body>
          <div id="chatbox"></div>
          <input id="msg" type="text" />
          <button onclick="sendMessage()">发送</button>
      
          <script>
              var ws = new WebSocket("ws://localhost:8080/ws");
              ws.onmessage = function(event) {
                  var chatbox = document.getElementById('chatbox');
                  chatbox.innerHTML += event.data + "<br/>";
              };
      
              function sendMessage() {
                  var msg = document.getElementById("msg").value;
                  ws.send(msg);
              }
          </script>
      </body>
      </html>
      

      运行 Go 服务:

      go run main.go
      

      打开浏览器,访问 index.html,即可体验多人聊天室的功能。

      到此这篇关于使用Go语言实现简单聊天系统的文章就介绍到这了,更多相关Go语言聊天系统内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

      0

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      关注公众号