websocket是什么
websocket与http一样都是OSI模型中应用层的协议,都是基于TCP协议来传输数据的,我们把这些更高级的协议理解成对TCP的封装。
socket与websocket的关系
socket与websocket的关系:就是毫无关系。
socket并不是一种协议,而是一个抽象层,将物理层、数据链路层、网络层与传输层向用户抽象成一层,用户直接使用socket封装的API即可操作这四层所带来的功能。
websocket是一种完整的应用层协议,包含一套完整的API。WebSocket API 是 HTML5 标准的一部分, 但这并不代表 WebSocket 一定要用在 HTML 中,或者只能在基于浏览器的应用程序中使用。
实际上,许多语言、框架和服务器都提供了 WebSocket 支持。
有了http协议,为什么还要有websocket协议?
非常好的一篇文章:https://blog.csdn.net/java_beautiful/article/details/127283971
文章总结
问题的痛点
Q:怎么样才能在用户不做任何操作的情况下,使客户端的数据发生实时的变化?
如果只有http协议的话,我们需要使用http不断轮询,即前端不断定时发送http请求到服务器,服务器收到请求后响应消息。这是一种伪的实时更新,只是背着用户偷偷不断发请求,用户没有感觉到罢了。这种轮询的应用场景也有很多,比如扫码登录等需要扫二维码的场景,手机发送了请求,但是网页前端不知道用户扫没扫,就需要不断向后端询问,保证用户在扫码后能立即得到回应。
只用http轮询的缺点:
- 当你打开F12页面时,你会发现满屏的HTTP请求。虽然很小,但这其实也消耗带宽,同时也会增加下游服务器的负担。
- 用户会感到明显的卡顿
- 所以http接口来进行前后端交互,需要用在实时交互不是很密集的场景。由于http是短连接,一旦请求后会断开与服务器的连接,因此不适合一些长时间的实时交互。但是,优点是不受网络的限制,断开重连非常之快,一般非实时的请求都会使用HTTP。
而Websocket是长连接,缺点是会受到网络波动的影响,但优点也是明显的,就是所谓的全双工通信,服务器与客户端可以实时地交换数据,很明显游戏就一定是建立在websocket协议上的。
websocket原理
我们平时刷网页,一般都是在浏览器上刷的,一会刷刷图文,这时候用的是HTTP协议,一会打开网页游戏,这时候就得切换成我们新介绍的websocket协议。 为了兼容这些使用场景。浏览器在TCP三次握手建立连接之后,都统一使用HTTP协议先进行一次通信。
如果此时是普通的HTTP请求,那后续双方就还是老样子继续用普通HTTP协议进行交互,这点没啥疑问。
如果这时候是想建立websocket连接,就会在HTTP请求里带上一些特殊的header头。
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: T2a6wZlAwhgQNqruZ2YUyg
// 这些header头的意思是,浏览器想升级协议(Connection: Upgrade),并且想升级成websocket协议(Upgrade: websocket)。
// 同时带上一段随机生成的base64码(Sec-WebSocket-Key),发给服务器。
如果服务器正好支持升级成websocket协议。就会走websocket握手流程,同时根据客户端生成的base64码,用某个公开的算法变成另一段字符串,放在HTTP响应的 Sec-WebSocket-Accept 头里,同时带上101状态码(协议切换状态码),发回给浏览器。
HTTP/1.1 101 Switching Protocols
Sec-WebSocket-Accept: iBJKv/ALIW2DobfoA4dmr3JHBCY
Upgrade: websocket
Connection: Upgrade
TCP支持全双工通信的,即双方都可以同时发送数据。而HTTP在同一时间只有客户端或者服务端一方能主动发送数据,是半双工通信。为了弥补http无法满足实时互相大量发送数据的场景,所以websocket协议被设计出来了。
websocket借鸡生蛋
websocket并不是HTTP的新协议,因为websocket只有在建立连接时才用到了HTTP,升级完成了之后就跟HTTP没有任何区别。
websocket完美继承了TCP协议的全双工能力,并且还贴心的提供了解决粘包的方案。它适用于需要服务器和客户端(浏览器)频繁交互的大部分场景。比如网页/小程序游戏,网页聊天室,以及一些类似飞书这样的网页协同办公软件。
使用Go搭建简单的websocket
这里没有使用官方的websocket包
github.com/gorilla/websocket
http升级为websocket
服务端
package main
import (
"fmt"
"github.com/gorilla/websocket"
"log"
"net/http"
)
var UP = websocket.Upgrader{
// HandshakeTimeout: 0, // 握手时间0为不限制
ReadBufferSize: 1024, // 以字节为单位的IO缓冲区,如果缓冲区大小为0,则使用HTTP服务器分配的缓冲区(不为0就是读写做限制)
WriteBufferSize: 1024, // 以字节为单位的IO缓冲区,如果缓冲区大小为0,则使用HTTP服务器分配的缓冲区(不为0就是读写做限制)
// WriteBufferPool: nil, // WriteBufferPool是用于写操作的缓冲池
// Error: nil, // 指定用于生成HTTP错误响应的函数
// CheckOrigin: nil, // 对过来的请求做校验用的
// EnableCompression: false, // 指定服务器是否应尝试根据进行协商消息压缩
}
func handler(w http.ResponseWriter, r *http.Request) { // http 原生写法
conn, err := UP.Upgrade(w, r, nil) // 响应,请求,响应头可以不写
if err != nil {
log.Println(err)
return
}
for {
messageType, content, err := conn.ReadMessage()
if err != nil {
break
}
fmt.Println(messageType, string(content))
}
defer conn.Close()
log.Println("服务关闭...")
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8888", nil) // 端口号前必须加上冒号
}
客户端
package main
import (
"bufio"
"fmt"
"github.com/gorilla/websocket"
"log"
"os"
)
func main() {
dl := websocket.Dialer{
// NetDial: nil,
// NetDialContext: nil,
// NetDialTLSContext: nil,
// Proxy: nil,
// TLSClientConfig: nil,
// HandshakeTimeout: 0,
// ReadBufferSize: 0, // 重要
// WriteBufferSize: 0, // 重要
// WriteBufferPool: nil,
// Subprotocols: nil,
// EnableCompression: false,
// Jar: nil,
}
conn, _, err := dl.Dial("ws://127.0.0.1:8888", nil) // 返回连接,http响应,错误
if err != nil {
log.Println(err)
return
}
go send(conn)
for {
messageType, content, err := conn.ReadMessage()
if err != nil {
break
}
fmt.Println(messageType, string(content))
}
}
func send(conn *websocket.Conn) {
for {
reader := bufio.NewReader(os.Stdin)
l, _, _ := reader.ReadLine()
conn.WriteMessage(websocket.TextMessage, l)
}
}
多个客户端之间进行通讯,在服务端进行处理:
package main
import (
"github.com/gorilla/websocket"
"log"
"net/http"
)
var UP = websocket.Upgrader{
// HandshakeTimeout: 0, // 握手时间0为不限制
ReadBufferSize: 1024, // 以字节为单位的IO缓冲区,如果缓冲区大小为0,则使用HTTP服务器分配的缓冲区(不为0就是读写做限制)
WriteBufferSize: 1024, // 以字节为单位的IO缓冲区,如果缓冲区大小为0,则使用HTTP服务器分配的缓冲区(不为0就是读写做限制)
// WriteBufferPool: nil, // WriteBufferPool是用于写操作的缓冲池
// Error: nil, // 指定用于生成HTTP错误响应的函数
// CheckOrigin: nil, // 对过来的请求做校验用的
// EnableCompression: false, // 指定服务器是否应尝试根据进行协商消息压缩
}
// 客户端每一个conn都是独立的,因此要完成客户端之间的通信,我们要把所有注册进来的conn都获取
var conns []*websocket.Conn
func handler(w http.ResponseWriter, r *http.Request) { // http 原生写法
conn, err := UP.Upgrade(w, r, nil) // 响应,请求,响应头可以不写
if err != nil {
log.Println(err)
return
}
conns = append(conns, conn)
for {
_, content, err := conn.ReadMessage()
if err != nil {
break
}
// 每一个客户端发一个请求,我们就让所有用户都能获取信息
for i := range conns {
conns[i].WriteMessage(websocket.TextMessage, []byte("你说的是:"+string(content)+"吗?"))
}
}
defer conn.Close()
log.Println("服务关闭...")
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8888", nil) // 端口号前必须加上冒号
}
标签:websocket,nil,err,Websocket,http,HTTP,conn From: https://www.cnblogs.com/DTCLOUD/p/17409342.html作者:陈双寅