欢迎使用社区 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
在接下来的更新里面我会详细介绍这些库的使用方法
三 .项目目录结构
API :基于gin 的web服务
socket : websocket 服务
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 就会开到以下界面
然后再网页上输入 https://127.0.0.1:8080/ 这样我们的web 服务的第一步就已经搭建成功。
五 : websocket服务
如果我要用golang 创建一个 webscoket 服务,我们需要做以下几点功能
websocket和HTTP 都是基于TCP 的协议, 但websocket和http是两种不同的东西
- 握手阶段
- 接收客户端传输的数据
- 发送数据库到客户端
- 关闭链接
- 我们看一下啊webscoket 的请求头和服务器的响应头
- 客户端请求如下
- 服务端响应
-
接下来我们看看每个请求的参数的大概意思
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 :告诉服务器所使用的协议版本
- 让我们用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
}
© 著作权归作者所有
发表评论