---慢更-基于 go 的 IM 聊天 (我更了没有太监!!!!)

欢迎使用社区 Markdown 编辑器写文章!

一. 背景

大佬们如有什么写的不对的欢迎大家在下面留言指出,持续更新中。

最近公司在从 PHP 转到 Golang ,所以这个项目属于练手项目很多东西也是在摸着石头过河,持续更新中....

二 .库的选择

既然是IM 系统必定会涉及到用TCP 来维持长连接,再这里我们选择了github.com/gorilla/websocket

作为我们的webcocket 库。在web服务的选择上我们选择了github.com/gin-gonic/gin 作为我们的web服务。

json 库的选择我们没有选择官方的库,而是选择了第三方的json 库(josn是什么不做解释)

传统的做法就是用非官方的json 库 ,需要我们先提前定义struct 或者用一个map[string]interface。

我个人不喜欢定义结构体所以我们用了一个第三方库https://github.com/tidwall/gjson

在接下来的更新里面我会详细介绍这些库的使用方法

三 .项目目录结构

---慢更-基于go 的IM 聊天

API :基于gin 的web服务
socket : websocket 服务

---慢更-基于go 的IM 聊天

config :配置文件

controller:控制器

database:数据库操作

helps :帮助方法

middleware : 中间件

models:数据库的结构体 定义

router : 路由地址

四:数据库创建

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `account` varchar(50) DEFAULT NULL COMMENT '账号z',
  `niack_name` varchar(50) DEFAULT NULL COMMENT '用户名',
  `user_avatar` varchar(255) DEFAULT NULL COMMENT '头像',
  `password` varchar(100) DEFAULT NULL COMMENT '密码',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8mb4;

CREATE TABLE `groups` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `group_name` varchar(50) DEFAULT NULL COMMENT '群名称',
  `user_id` int(11) DEFAULT NULL COMMENT '群主ID',
  `grop_avatar` varchar(255) DEFAULT NULL COMMENT '头像',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

CREATE TABLE `history_message` (
  `id` int(11) NOT NULL,
  `from_id` int(11) NOT NULL COMMENT '发送者id',
  `from_name` varchar(255) DEFAULT NULL COMMENT '发送者名称',
  `from_avatar` varchar(255) DEFAULT NULL COMMENT '发送者头像',
  `to_id` int(11) NOT NULL COMMENT '接收者ID',
  `msg_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1:私聊 2:群聊',
  `msg` json DEFAULT NULL COMMENT '消息内容',
  `send_time` datetime DEFAULT NULL COMMENT '发送时间',
  `send_status` tinyint(1) DEFAULT NULL COMMENT '1:发送完成 2:失败',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `messages` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `message` varchar(255) DEFAULT NULL COMMENT '申请消息',
  `from_id` int(11) DEFAULT NULL COMMENT '申请人',
  `to_id` varchar(255) DEFAULT NULL COMMENT '被申请的id',
  `type` int(1) DEFAULT '0' COMMENT '1:好友 2:群组',
  `status` int(1) DEFAULT '0' COMMENT '1:同意 2:拒绝',
  `created_at` datetime DEFAULT NULL,
  `update_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

四 :搭建web服务

1:下载 gin

   go get github.com/gin-gonic/gin    

2:下载 安装mysql驱动

 go get github.com/go-sql-driver/mysql
 package router

import (
    `time`
    `MyModel/blog/helps`
    "github.com/gin-gonic/gin"
)

func ApiRouter() {

    // 初始化路由
    router := gin.Default()
    // miss路由
    router.Any("/", func(context *gin.Context) {
        ApiJson :=helps.Api{
            Message:    "请求成功",
            Data:       make([]int, 0, 0),
            Time:       time.Now(),
            StatusCode: 200,
        }
        context.AbortWithStatusJSON(200, ApiJson)
    })

    _ = router.RunTLS(":8888","default.pem","default.key") // 在 0.0.0.0:8080 上监听并服务

}

然后我们新建一个main.go 文件

    package main

    import (
        `MyModel/blog/router`
    )

    func main()  {

        router.ApiRouter()

    }

在cli 界面输入 go run main.go 就会开到以下界面

---慢更-基于 go 的 IM 聊天

然后再网页上输入 https://127.0.0.1:8080/ 这样我们的web 服务的第一步就已经搭建成功。

---慢更-基于 go 的 IM 聊天

五 : websocket服务

如果我要用golang 创建一个 webscoket 服务,我们需要做以下几点功能
websocket和HTTP 都是基于TCP 的协议, 但websocket和http是两种不同的东西

  1. 握手阶段
  2. 接收客户端传输的数据
  3. 发送数据库到客户端
  4. 关闭链接
  • 我们看一下啊webscoket 的请求头和服务器的响应头
  1. 客户端请求如下

---慢更-基于 go 的 IM 聊天

  1. 服务端响应

---慢更-基于 go 的 IM 聊天

  1. 接下来我们看看每个请求的参数的大概意思

        Connection :Upgrade  这是告诉服务器升级了
        Upgrade :websocket 升级为websocket
        Host :服务器地址
        Origin :发起地址
        Sec-WebSocket-Extensions: permessage-deflat;client_max_window_bits
        `permessage-deflate`这个参数来协商是否对传输数据进行deflate压缩的。
        client_max_window_bits 表示采用LZ77压缩算法时,滑动窗口相关的SIZE大小
        Sec-WebSocket-Key :asdklasjdloadkd  是base64加密的字符串 浏览器自动生成
        Sec-WebSocket-Version :告诉服务器所使用的协议版本
    1. 让我们用GO 实现一个websocket 得基础版本
func main() {
    listeningPort()
}

func listeningPort() {
    service := ":7777"
    listener, _ := net.Listen("tcp", service)

    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Println("出现错误:", err)
            continue
        }
        upgradeTCP(conn)
    }

}
func upgradeTCP(conn net.Conn) {

    data := make([]byte, 1024)
    _, _ = conn.Read(data)
    headers := getTheHead(data)
    // 在 RFC 6455  258EAFA5-E914-47DA-95CA-C5AB0DC85B11 是用于协议内的协议加密
    // 截取Sec-WebSocket-Key的值并加密
    SecWebSocketKey := headers["Sec-WebSocket-Key"]
    h := sha1.New()
    _, _ = io.WriteString(h, SecWebSocketKey)
    _, _ = io.WriteString(h, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
    // 这个用来得到最终的加密字符串
    bs := h.Sum(nil)
    // 然后base 64加密
    encoded := base64.StdEncoding.EncodeToString(bs)
    // 设置
    upgrade := SetTheHead(encoded)
    // 返回
    conn.Write([]byte(upgrade))
}

func SetTheHead(encoded string) string {

    newMessage := "HTTP/1.1 101 Switching Protocols\r\n" +
        "Upgrade: websocket\r\n" +
        "Sec-WebSocket-Version: 13\r\n" +
        "Connection: Upgrade\r\n" +
        "Sec-WebSocket-Accept: " + encoded + "\r\n\r\n"
    return newMessage
}

func getTheHead(data []byte) map[string]string {
    headers := make(map[string]string)

    lines := strings.Split(string(data), "\r\n")
    for _, v := range lines {
        arrayData := strings.Split(v, ": ")
        if len(arrayData) > 1 {
            headers[arrayData[0]] = arrayData[1]
        }
    }
    return headers
}
本帖由系统于 1周前 自动加精
讨论数量: 4

:joy:更的比较慢 保证每天都会更新。主要是怕写详细了又觉得啰嗦。纠结

1周前 评论

老哥一看就是写laravel的,这目录结构

1周前 评论
sugarsugar (楼主) 1周前
sugarsugar (楼主) 1周前

大佬牛逼

1周前 评论

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!