package mainimport ("fmt""log""net/http""github.com/gorilla/websocket"
)var UP = websocket.Upgrader{/*握手时间缓冲区(包括写和读)缓冲池指定用于生成HTTP错误响应的函数对过来的请求进行校验指定服务器是否应尝试根据进行协商消息压缩*/ReadBufferSize: 1024,WriteBufferSize: 1024,
}var conns []*websocket.Connfunc handler(w http.ResponseWriter, r *http.Request) {conn, err := UP.Upgrade(w, r, nil)if err != nil {log.Println(err)return}conns = append(conns, conn)for {m, p, e := conn.ReadMessage() //读取消息if e != nil {fmt.Println(err)break}for i := range conns {conns[i].WriteMessage(websocket.TextMessage, []byte(string(p)))//变成渣男了,可以对所有给他发消息的人进行说话}fmt.Println(m, string(p))}defer conn.Close()log.Println("服务关闭")
}
func main() {http.HandleFunc("/", handler)http.ListenAndServe(":8888", nil)}
package mainimport ("bufio""fmt""github.com/gorilla/websocket""os"
)func main() {dl := websocket.Dialer{} //这是websocket提供的的结构体/*一切提供的东西都和业务挂钩,和学习无关*/conn, _, err := dl.Dial("ws://127.0.0.1:8888", nil)if err != nil {fmt.Println(err)return}go send(conn)for {m, p, e := conn.ReadMessage()if e != nil {break}//只要连接还在,不断的读fmt.Println(m, string(p))}
}func send(conn *websocket.Conn) {for {reader := bufio.NewReader(os.Stdin)l, _, _ := reader.ReadLine()conn.WriteMessage(websocket.TextMessage, l)}
}
首先需要了解下列知识:
WebSocket 是一种在单个 TCP 连接上提供全双工通信的网络协议。它可以让客户端和服务器之间进行双向通信,而无需通过 HTTP 请求和响应。在理解 WebSocket 的底层代码知识时,以下是您可能需要了解的一些重要概念:
- 握手(Handshake):
-
WebSocket 通信始于一个 HTTP 握手过程,客户端发起 WebSocket 握手请求,服务器接受并响应这个请求,然后建立起 WebSocket 连接。
-
握手过程通常发生在 HTTP/HTTPS 的特定端点上,例如 "/ws" 或 "/websocket"。
- 帧(Frame):
-
WebSocket 数据通过帧进行传输,每个帧代表了一个消息的一部分。
-
帧可以是文本、二进制数据或控制帧。控制帧用于连接管理,例如关闭连接。
- 数据帧格式:
-
数据帧通常由几个字节的控制头部和数据部分组成。
-
控制头部包括一些标志位和指示数据类型的信息,如数据是否被分片、数据类型(文本或二进制)、是否是最后一个片段等。
- 状态码(Status Codes):
-
WebSocket 握手过程和连接管理中使用了一系列状态码来表示不同的状态和结果。
-
例如,101 表示握手成功,而 1006 表示连接异常关闭。
- 心跳(Heartbeat):
-
为了保持连接活跃,WebSocket 可以周期性地发送心跳帧。
-
心跳帧通常是空的或包含特定的数据,用于告知对端连接依然存在。
- 拓展(Extensions):
-
WebSocket 支持通过拓展机制增加协议的功能。
-
拓展可以用于压缩数据、加密通信等目的。
- 子协议(Subprotocols):
- WebSocket 握手阶段可以指定一个或多个子协议,以便客户端和服务器之间选择适当的通信协议。
- 安全性:
-
WebSocket 连接的安全性可以通过使用 TLS/SSL 加密来保障。
-
这通常通过在标准的 HTTP(S) 端口上启用 WebSocket,或者在自定义端口上使用 wss:// URL 来实现
gin包
r := gin.Default()/*GET 查 ->放在地址栏里面uri传参POST 增 ->参数在form /body /uriDELETE 删 -> uri同样也可以用bodyPUT 改 -> 参数在 form/body/uriRestful 就是这四款uri的格式:"/path/:id" 可以取得id只存在面试*/r.GET("/path/:id", func(c *gin.Context) {id := c.Param("id")//user是放在地址栏后面的,我们称为Query传参user := c.Query("user")//如果说想要有一个默认值user := c.DefaultQuery("user", "qimiao")、//就是我输入uri的时候是没有user这个值的,但是反馈到服务器是需要有的pwd := c.Query("pwd")c.JSONP(200, gin.H{"id": id,"user": user,"pwd": pwd,})})r.POST("/path", func(c *gin.Context) {/*form表单*/user := c.DefaultPostForm("user", "xzc")pwd := c.PostForm("pwd")c.JSONP(200, gin.H{"user": user,"pwd": pwd,})})r.DELETE("/path/:id", func(c *gin.Context) {id := c.Param("id")//DELETE也可以解析表单c.JSONP(200, gin.H{"id": id,})})r.PUT("/path", func(c *gin.Context) {/*form表单*/user := c.DefaultPostForm("user", "xzc")pwd := c.PostForm("pwd")c.JSONP(200, gin.H{"user": user,"pwd": pwd,})})
接着来到第二课,接触了Bind,这个是啥嘞
有时候听他说话都听不懂,我心想第二遍肯定会好点
type PostParm struct {Name string `json:"name"`Age int `json:"age"`Sex bool `json:"sex"`
}
r := gin.Default()r.POST("/testBind", func(c *gin.Context) {var p PostParmerr := c.ShouldBind(&p)if err != nil {c.JSON(200, gin.H{"msg": "wrong","data": gin.H{},})} else {c.JSON(200, gin.H{"msg": "success","data": p,})}})
之后又增加了Query and uri的绑定形式
type PostParm struct {Name string `json:"name" uri:"name" form:"name"`Age int `json:"age" uri:"age" form:"age"`Sex bool `json:"sex" uri:"sex" form:"sex"`
}func main() {r := gin.Default()r.POST("/testBind", func(c *gin.Context) {var p PostParmerr := c.ShouldBindQuery(&p) //这里换上之后 上面的结构体会增加tag//err := c.ShouldBindURI(&p)if err != nil {c.JSON(200, gin.H{"msg": "wrong","data": gin.H{},})} else {c.JSON(200, gin.H{"msg": "success","data": p,})}})
还有一个shouldBind这里可以大致看一下
// If `GET`, only `Form` binding engine (`query`) used.// 如果是Get,那么接收不到请求中的Post的数据??// 如果是Post, 首先判断 `content-type` 的类型 `JSON` or `XML`, 然后使用对应的绑定器获取数据.// See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48if c.ShouldBind(&person) == nil {log.Println(person.Name)log.Println(person.Address)log.Println(person.Birthday)}
如果是GET,那么这个方法就只会拿到query数据
如果是POST,会判断content-type的格式
//之后我们研究这个binding,这其实是一个表单验证的功能
type PostParm struct {Name string `json:"name" binding:"required"`Age int `json:"age" binding:"required"`Sex bool `json:"sex" biding:"required"`
}r.POST("/testBind", func(c *gin.Context) {var p PostParmerr := c.ShouldBindJSON(&p) //这里换上之后 上面的结构体会增加tagif err != nil {fmt.Println(err.Error())c.JSON(200, gin.H{"msg": "wrong","data": gin.H{},})} else {c.JSON(200, gin.H{"msg": "success","data": p,})}})
然后我们做了一个实验,如果我们只传入name这个参数,将会返回什么呢
这个是返回的一个错误,说我们age的必填项没有填
然后我们继续加限制
type PostParm struct {Name string `json:"name" `Age int `json:"age" binding:"required,mustBig"`Sex bool `json:"sex" `
}
//然后接下来的什么,这个mustBig又自己写了一个func mustBig(f1 validator.FieldLevel) bool {if f1.Field().Interface().(int) <= 18 {return false}return true
}//验证规则
/*
内部通过反射的形式判断年龄的大小是否符合标准*/
//main里面在 接收前也要编写一个if v, ok := binding.Validator.Engine().(*validator.Validate); ok {v.RegisterValidation("mustBig", mustBig)}
这个是报错的信息,说明它违反了mustBig的tag
r.POST("/testBind", func(c *gin.Context) {if v, ok := binding.Validator.Engine().(*validator.Validate); ok {v.RegisterValidation("mustBig", mustBig)//绑定一个注册规则}var p PostParmerr := c.ShouldBindJSON(&p) //这里换上之后 上面的结构体会增加tagif err != nil {fmt.Println(err.Error())c.JSON(200, gin.H{"msg": "wrong","data": gin.H{},})} else {c.JSON(200, gin.H{"msg": "success","data": p,})}})r.Run(":8080")
一般情况要使用ShouldBind,因为MustBind返回的code不好控制
ShouldBind有一个自定义验证
gin对于文件的接受和返回
前端给后端传文件
首先设置Header
然后再发送文件
r.POST("/testUpload", func(c *gin.Context) {file, _ := c.FormFile("file")c.SaveUploadedFile(file, "./"+file.Filename) //接受一个文件的格式还有路径(保存位置)/*会返回一个文件流和err*/c.JSON(200, gin.H{"msg": file,})})
r := gin.Default()r.POST("/testUpload", func(c *gin.Context) {file, _ := c.FormFile("file")//将文件和postform都传给后台//接收文件in, _ := file.Open()defer in.Close()out, _ := os.Create("./" + file.Filename)//接受一个文件路径还有一个名字,用来搞清楚保存在哪里defer out.Close()io.Copy(out, in)//给前端返回文件c.Writer.Header().Add("Content-Disposition", fmt.Sprintf("attachment;filename=%s", file.Filename))c.File("./" + file.Filename)})
第二遍看需要自己手动写了
file, _ := c.FormFile("file")in, _ := file.Open()//打开文件并知晓其内容defer in.Close()out, _ := os.Create("./"+file.Filename)//在给定的路径下创建一个文件defer out.Close()io.Copy(out,in)//将一开始复制的文件中的内容复制到刚创建的文件