Websocket 总结
是什么
- WebSocket 是通过单个 TCP 连接提供全双工(双向通信)通信信道的计算机通信协议。
- 在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。——长链接,直到关闭
在 WebSocket 出现之前,为了实现即时通信,采用的技术都是“轮询”,即在特定的时间间隔内,由浏览器对服务器发出 HTTP Request,服务器在收到请求后,返回最新的数据给浏览器刷新,“轮询”使得浏览器需要对服务器不断发出请求,这样会占用大量带宽。
WebSocket 采用了一些特殊的报头,使得浏览器和服务器只需要做一个握手的动作,就可以在浏览器和服务器之间建立一条连接通道。且此连接会保持在活动状态,你可以使用 JavaScript 来向连接写入或从中接收数据,就像在使用一个常规的 TCP Socket 一样。它解决了 Web 实时化的问题,相比传统 HTTP 有如下好处:
- 一个 Web 客户端只建立一个 TCP 连接
- Websocket 服务端可以推送 (push) 数据到 web 客户端。
- 有更加轻量级的头,减少数据传送量
WebSocket URL 的起始输入是 ws:// 或是 wss://(在 SSL 上)。
过程
- 客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息 “Upgrade: WebSocket” 表明这是一个申请协议升级的 HTTP 请求
- 服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了
- 双方就可以通过这个连接通道自由的传递信息
- 持续存在直到客户端或者服务器端的某一方主动的关闭连接。
WebSocket 原理
WebSocket 的协议颇为简单,在第一次 handshake 通过以后,连接便建立成功,其后的通讯数据都是以”\x00″开头, 以”\xFF”结尾。在客户端,这个是透明的,WebSocket 组件会自动将原始数据“掐头去尾”。
实现
echo 框架版 echo_server.go, 使用的第三方库: github.com/gorilla/websocket
package main
import (
"fmt"
"log"
"time"
"github.com/labstack/echo"
"github.com/gorilla/websocket"
"github.com/labstack/echo/middleware"
)
var (
upgrader = websocket.Upgrader{}
)
func hello(c echo.Context) error {
ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil)
if err != nil {
return err
}
defer ws.Close()
for {
// Write
time.Sleep(time.Second)
err := ws.WriteMessage(websocket.TextMessage, []byte("Hello, Client!"))
if err != nil {
log.Fatal(err)
}
// Read
_, msg, err := ws.ReadMessage()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", msg)
}
}
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.GET("/echo", hello)
e.Logger.Fatal(e.Start(":6666"))
}
官方库:golang.org/x/net/websocket
package main
import (
"fmt"
"golang.org/x/net/websocket"
"net/http"
"os"
"time"
)
//错误处理函数
func checkErr(err error, extra string) bool {
if err != nil {
formatStr := " Err : %s\n";
if extra != "" {
formatStr = extra + formatStr;
}
fmt.Fprintf(os.Stderr, formatStr, err.Error());
return true;
}
return false;
}
func svrConnHandler(conn *websocket.Conn) {
request := make([]byte, 128);
defer conn.Close();
for {
readLen, err := conn.Read(request)
if checkErr(err, "Read") {
break;
}
//socket被关闭了
if readLen == 0 {
fmt.Println("Client connection close!");
break;
} else {
//输出接收到的信息
fmt.Println(string(request[:readLen]))
time.Sleep(time.Second);
//发送
conn.Write([]byte("World !"));
}
request = make([]byte, 128);
}
}
func main() {
http.Handle("/echo", websocket.Handler(svrConnHandler));
err := http.ListenAndServe(":6666", nil);
checkErr(err, "ListenAndServe");
fmt.Println("Func finish.");
}
client 测试代码:
package main
import (
"fmt"
"golang.org/x/net/websocket"
"os"
"sync"
)
var gLocker sync.Mutex; //全局锁
var gCondition *sync.Cond; //全局条件变量
var origin = "http://127.0.0.1:6666/"
var url = "ws://127.0.0.1:6666/echo"
//错误处理函数
func checkErr(err error, extra string) bool {
if err != nil {
formatStr := " Err : %s\n";
if extra != "" {
formatStr = extra + formatStr;
}
fmt.Fprintf(os.Stderr, formatStr, err.Error());
return true;
}
return false;
}
//连接处理函数
func clientConnHandler(conn *websocket.Conn) {
gLocker.Lock();
defer gLocker.Unlock();
defer conn.Close();
request := make([]byte, 128);
for {
readLen, err := conn.Read(request)
if checkErr(err, "Read") {
gCondition.Signal();
break;
}
//socket被关闭了
if readLen == 0 {
fmt.Println("Server connection close!");
//条件变量同步通知
gCondition.Signal();
break;
} else {
//输出接收到的信息
fmt.Println(string(request[:readLen]))
//发送
conn.Write([]byte("Hello !"));
}
request = make([]byte, 128);
}
}
func main() {
conn, err := websocket.Dial(url, "", origin);
if checkErr(err, "Dial") {
return;
}
gLocker.Lock();
gCondition = sync.NewCond(&gLocker);
_, err = conn.Write([]byte("Hello !"));
go clientConnHandler(conn);
//主线程阻塞,等待Singal结束
for {
//条件变量同步等待
gCondition.Wait();
break;
}
gLocker.Unlock();
fmt.Println("Client finish.")
}
- 原文作者:战神西红柿
- 原文链接:https://tomatoares.github.io/posts/web/websocket%E6%80%BB%E7%BB%93/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。