[go-websocket 搭建一对一,一对多的聊天室] 第一篇:websocket基础
源码地址
https://gitee.com/bin-0821/chat-room-demo-go-websocket
关于websocket的基础原理大家可以自行网上搜索,本篇文章关注于如何通过go代码进行实现websocket功能
略读代码需要了解
1,gin框架基础
2,go堵塞原理
1. 目录结构
│ go.mod
│ go.sum
│ main.go
├─api
│ socket_conn.go //websocket功能实现
├─middleware
│ Cros.go //跨域配置
├─model
│ socket_msg_form_front.go //websocket json数据结构体
│ to_front.go //返回前端结构体
└─route
route.go //路由配置
2. 代码
项目使用web框架为gin,也可以使用其他框架货go原生,只需要把请求进行升级就行了
webscoket 使用包 "github.com/gorilla/websocket"
关于代码解释都写在注释里了
下面是各个文件的代码
main.go
package main
import "WebSocketDemo/route"
func main() {
// 获取路由
ro := route.GetRoute()
// 监听端口
_ = ro.Run("0.0.0.0:8083")
}
socket_conn.go
user_id是为了在服务端知道那条链接对象是对应那个用户id,在下面的代码中是服务端与用户发送和接收,基本没有用处,但在后面要进行多对多的时候user_id就很重要了
package api
import (
"WebSocketDemo/model"
"fmt"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"net/http"
"strconv"
)
// websocket配置
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: checkOrigin,
}
func checkOrigin(r *http.Request) bool {
return true
}
// 用户申请创建socket链接
func ConCreateConn(ctx *gin.Context) {
var (
conn *websocket.Conn
err error
user_id int
)
// 获取user_id 这里可以是token,经过中间件解析后的存在 ctx 的user_id
// 为方便演示 这里直接请求头带user_id,正常开发不建议
user_id, err = strconv.Atoi(ctx.GetHeader("user_id"))
if err != nil && user_id <= 0 {
ctx.JSON(200, model.ResDatas(500, "请求必须带user_id"+err.Error(), nil))
return
}
//fmt.Println("user_id", user_id)
// 判断请求过来的链接是否要升级为websocket
if websocket.IsWebSocketUpgrade(ctx.Request) {
// 将请求升级为 websocket链接
conn, err = upgrader.Upgrade(ctx.Writer, ctx.Request, ctx.Writer.Header())
if err != nil {
ctx.JSON(200, model.ResDatas(500, "创建链接失败"+err.Error(), nil))
return
}
}else {
return
}
// 用户断开销毁
defer func() {
_ = conn.Close()
}()
for {
var msg model.ConnMsg
// ReadJSON 获取值的方式类似于gin的 ctx.ShouldBind() 通过结构体的json映射值
// 如果读不到值 则堵塞在此处
err = conn.ReadJSON(&msg)
if err != nil {
// 写回错误信息
err = conn.WriteJSON(model.ResDatas(400, "获取数据错误:"+err.Error(), nil))
if err != nil {
fmt.Println("用户断开")
return
}
}
// do something.....
msg.FormUserID = user_id
// 发送回信息
err = conn.WriteJSON(msg)
if err != nil {
fmt.Println("用户断开")
return
}
}
}
Cros.go
package middleware
import (
"bytes"
"github.com/gin-gonic/gin"
"io/ioutil"
"net/http"
)
// 跨域配置
func Cors(c *gin.Context) {
method := c.Request.Method
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Headers", "*,Content-Type,AccessToken,X-CSRF-Token, Authorization, Token ")
c.Header("Access-Control-Allow-Methods", "PUT, PATCH, GET, POST, OPTIONS,DELETE")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
c.Header("Access-Control-Allow-Credentials", "true")
b, _ := ioutil.ReadAll(c.Request.Body)
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(b))
//放行所有OPTIONS方法
if method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
}
// 处理请求
c.Next()
}
socket_msg_form_front.go
package model
type ConnMsg struct {
Msg ChatMsg `json:"msg,omitempty"`
FormUserID int `json:"form_user_id,omitempty"`
}
// ChatMsgType = 1 群聊信息 ChatMsgType = 2 一对一信息 ...
type ChatMsg struct {
ChatMsgType int `json:"chat_msg_type,omitempty"`
Data map[string]interface{} `json:"data,omitempty"`
}
to_front.go
package model
// 返回给 前端 的结构体
type ResData struct {
Code int `json:"code"`
Msg interface{} `json:"msg" form:"msg"`
Data interface{} `json:"data"`
}
// 创建前端返回结构体
// 参数:
// code:约定状态码
// msg:提示信息
// data:数据
// 返回:前端结构体
func ResDatas(code int, msg interface{}, date interface{}) ResData {
return ResData{
Code: code,
Msg: msg,
Data: date,
}
}
3. 运行项目
apiPost 测试
在apipost新建websocket连接
输入url,如果你有前端可以测试 记得url前加ws:// ,部署到服务器的话 可能需要放行端口
发送json内容
{
"msg": {
"chat_msg_type": 2,
"data": {
"to_user_id": 2,
"content": "xxxxxxxxxxxxxxxxxx1"
}
}
}
响应结果,可以看到多了form_user_id,这个是在服务端接收后进行加工加入的数据,到这里webscoket基础就已经完成了
4. 总结
整个代码最重要的就是这段内容了
if websocket.IsWebSocketUpgrade(ctx.Request) {
// 将请求升级为 websocket链接
conn, err = upgrader.Upgrade(ctx.Writer, ctx.Request, ctx.Writer.Header())
if err != nil {
ctx.JSON(200, model.ResDatas(500, "创建链接失败"+err.Error(), nil))
return
}
}else {
return
}
// 用户断开销毁
defer func() {
_ = conn.Close()
}()
for {
var msg model.ConnMsg
// ReadJSON 获取值的方式类似于gin的 ctx.ShouldBind() 通过结构体的json映射值
// 如果读不到值 则堵塞在此处
err = conn.ReadJSON(&msg)
if err != nil {
// 写回错误信息
err = conn.WriteJSON(model.ResDatas(400, "获取数据错误:"+err.Error(), nil))
if err != nil {
fmt.Println("用户断开")
return
}
}
// do something.....
msg.FormUserID = user_id
// 发送回信息
err = conn.WriteJSON(msg)
if err != nil {
fmt.Println("用户断开")
return
}
}
1 先把请求升级为websocket
2 for循环内读取json 读取不到就阻塞,
3 加工数据并再发送给前端
现在大家肯定注意到了,自始至终一直围绕着conn这个变量再进行操作。
目前只实现了服务端与客户端的通信,如何进行像微信一样进行通信,我会在后续的篇章进行讲解