[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. 运行项目

image
apiPost 测试
在apipost新建websocket连接
输入url,如果你有前端可以测试 记得url前加ws:// ,部署到服务器的话 可能需要放行端口
image
发送json内容

{
    "msg": {
      "chat_msg_type": 2,
      "data": {
        "to_user_id": 2,
        "content": "xxxxxxxxxxxxxxxxxx1"
      }
    }
}

响应结果,可以看到多了form_user_id,这个是在服务端接收后进行加工加入的数据,到这里webscoket基础就已经完成了
image

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这个变量再进行操作。
目前只实现了服务端与客户端的通信,如何进行像微信一样进行通信,我会在后续的篇章进行讲解

posted @ 2022-07-18 16:09  树杉  阅读(840)  评论(0编辑  收藏  举报