go ajax请求_go语言websocket-工具与指南

翻译自:How to Use Websockets in Golang : Best Tools and Step-by-Step Guide

在不刷新页面的情况下发送消息并获得即时响应是我们认为理所当然的事情。但在过去,实现实时功能对开发人员来说是一个真正的挑战。开发人员社区已经从HTTP长轮询和AJAX走了很长一段路,最终找到了构建真正实时应用程序的解决方案。

这个解决方案以WebSockets的形式出现,它使得在用户的浏览器和服务器之间打开交互会话成为可能。WebSockets允许浏览器向服务器发送消息并接收事件驱动的响应,而无需轮询服务器以获得响应。

目前,WebSockets是构建实时应用程序的首选解决方案:在线游戏、即时消息、跟踪应用程序等等。本指南解释了WebSocket的工作原理,并展示了如何用Go编程语言构建WebSocket应用程序。我们还比较了最流行的WebSocket库,以便您可以根据自己的需要选择最佳的WebSocket库。

网络套接字

网络套接字,或简单地称为套接字,作为内部端点,用于在运行在同一台计算机或运行在同一网络上的不同计算机上的应用程序之间交换数据。

套接字是Unix和基于windows的操作系统的关键部分,它们使开发人员更容易创建支持网络的软件。应用程序开发人员可以在他们的程序中包含套接字,而不是从头开始构建网络连接。由于网络套接字用于许多不同的网络协议(HTTP、FTP等),所以可以同时使用多个套接字。

套接字与一组函数调用一起创建和使用,这些函数调用有时称为套接字的应用程序编程接口(API)。由于函数调用,套接字可以像普通文件一样打开。

有几种类型的网络套接字:

数据报套接字(SOCK_DGRAM),也称为无连接套接字,使用用户数据报协议(UDP)。数据报套接字支持消息的双向流并保留记录边界。

流套接字(SOCK_STREAM),也称为面向连接的套接字,使用传输控制协议(TCP)、流控制传输协议(SCTP)或数据报拥塞控制协议(DCCP)。这些套接字提供了双向的、可靠的、有序的、不重复的数据流,没有记录边界。

原始套接字(或原始IP套接字)通常在路由器和其他网络设备中可用。这些套接字通常是面向数据流的,尽管它们的确切特征取决于协议提供的接口。大多数应用程序不使用原始套接字。提供它们是为了支持新通信协议的开发,并提供对现有协议中更深奥的设施的访问。

套接字通信

首先,让我们看看如何确保每个套接字都是惟一的。如果没有,你就无法建立一个可靠的沟通渠道。

给每个进程一个独特的PID有助于处理本地问题。但是这种方法不能在网络上工作。要创建一个惟一的套接字,我们建议使用TCP/IP协议。使用TCP/IP,网络层的IP地址在给定的网络中是惟一的,协议和端口在主机应用程序中也是惟一的。

TCP和UDP是主机之间通信的两种主要协议。让我们看看您的应用程序如何连接到TCP和UDP套接字。

连接到TCP套接字

f49b46fd9278ca7da9295cb62c2b017a.png

为了建立TCP连接,Go客户机使用网络包中的DialTCP函数。返回一个TCPConn对象。建立连接后,客户机和服务器开始交换数据:客户机通过TCPConn向服务器发送请求,服务器解析请求并发送响应,TCPConn接收来自服务器的响应。

此连接在客户机或服务器关闭之前一直有效。创建连接的功能如下:

Client side:

 // init    
tcpAddr, err := net.ResolveTCPAddr(resolver, serverAddr)    
if err != nil {
         // handle error    
}   
 conn, err := net.DialTCP(network, nil, tcpAddr)   
 if err != nil {            
// handle error   
 }     
// send message     
_, err = conn.Write({message})   
 if err != nil {   
    // handle error   
 }     
// receive message   
 var buf [{buffSize}]byte    _, err := conn.Read(buf[0:])    
if err != nil {        
 // handle error   
} 

Server side:

// init   
 tcpAddr, err := net.ResolveTCPAddr(resolver, serverAddr)        
if err != nil {            
// handle error        
}            
listener, err := net.ListenTCP("tcp", tcpAddr)    
 if err != nil {         
// handle error     
}          
// listen for an incoming connection     
conn, err := listener.Accept()     
if err != nil {        
 // handle error    
 }          
// send message    
 if _, err := conn.Write({message}); err != nil {        
 // handle error     
}         
// receive message     
buf := make([]byte, 512)     
n, err := conn.Read(buf[0:])     
if err != nil {         
// handle error     
} 

连接到UDP套接字

与TCP套接字相反,使用UDP套接字,客户机只向服务器发送一个数据报。没有Accept函数,因为服务器不需要接受连接,只需要等待数据报到达。

f1b492fbe169ace7b315532d40ce3931.png

其他TCP函数有UDP对应的函数;在上面的函数中,只需用UDP替换TCP。

Client side:

// init     
raddr, err := net.ResolveUDPAddr("udp", address)     
if err != nil {
         // handle error     
}             
conn, err := net.DialUDP("udp", nil, raddr)    
 if err != nil {        
 // handle error     
}         .......     
 // send message     
buffer := make([]byte, maxBufferSize)     
n, addr, err := conn.ReadFrom(buffer)     
if err != nil {         
// handle error    
 }          .......                 
// receive message     
buffer := make([]byte, maxBufferSize)     
n, err = conn.WriteTo(buffer[:n], addr)     
if err != nil {        
 // handle error     
} 

Server side:

    // init     
udpAddr, err := net.ResolveUDPAddr(resolver, serverAddr)    
 if err != nil {         
// handle error     
}          
conn, err := net.ListenUDP("udp", udpAddr)     
if err != nil {         
// handle error     
}         .......     
// send message     
buffer := make([]byte, maxBufferSize)     
n, addr, err := conn.ReadFromUDP(buffer)     
if err != nil {        
 // handle error     
}          .......     
// receive message     
buffer := make([]byte, maxBufferSize)     
n, err = conn.WriteToUDP(buffer[:n], addr)     
if err != nil {         
// handle error    
 } 

什么是WebSocket


WebSocket通信协议通过一个TCP连接提供一个全双工通信通道。与HTTPs相反,WebSockets不需要您发送请求来获得响应。它们允许双向数据流,因此您可以等待服务器响应。它会在你有空的时候给你发信息。

WebSockets对于需要持续数据交换的服务是一个很好的解决方案——例如,即时通讯、在线游戏和实时交易系统。您可以在RFC 6455规范中找到关于WebSocket协议的完整信息。

浏览器请求WebSocket连接,服务器响应WebSocket连接,然后建立连接。这个过程通常称为握手。WebSockets中特殊类型的头只需要在浏览器和服务器之间进行一次握手,就可以建立一个在整个生命周期内都保持活动的连接。

WebSockets解决了实时web开发的许多难题,并且比传统HTTP有以下几个好处:

  • 轻量级头文件减少了数据传输开销。
  • 一个web客户机只需要一个TCP连接。
  • WebSocket服务器可以将数据推送到web客户机。

f03ce1db4b05e8e5d3dde82b6aa69a3b.png

WebSocket协议实现起来相对简单。它使用HTTP协议进行初始握手。成功握手之后,就建立了连接,WebSocket基本上使用原始TCP读取/写入数据。

这是客户端请求的样子:

  GET /chat HTTP/1.1     
Host: server.example.com     
Upgrade: websocket     
Connection: Upgrade     
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==     
Sec-WebSocket-Protocol: chat, superchat     
Sec-WebSocket-Version: 13     
Origin: http://example.com


服务器响应:

    HTTP/1.1 101 
Switching Protocols     
Upgrade: websocket     
Connection: Upgrade     
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=     
Sec-WebSocket-Protocol: chat

如何在Go中创建WebSocket应用


要基于net/http库编写一个简单的WebSocket echo服务器,需要:

  • 发起一个握手
  • 从客户端接收数据帧
  • 向客户端发送数据帧
  • 关闭握手

首先,让我们创建一个带有WebSocket端点的HTTP处理程序:

// HTTP server with WebSocket endpoint         
func Server() {         
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {             
ws, err := NewHandler(w, r)             
if err != nil {                  
// handle error             
}            
 if err = ws.Handshake(); err != nil {                 
// handle error             
}         … 

然后初始化WebSocket结构。

初始握手请求总是来自客户机。一旦服务器定义了WebSocket请求,它就需要用握手响应进行响应。

请记住,您不能使用http编写响应。因为一旦您开始发送响应,它将关闭底层TCP连接。

所以需要使用HTTP劫持。劫持允许您接管底层TCP连接处理程序和bufio.Writer。这使您能够在不关闭TCP连接的情况下读写数据。

// NewHandler initializes a new handler        
 func NewHandler(w http.ResponseWriter, req *http.Request) (*WS, error) {         
hj, ok := w.(http.Hijacker)         if !ok {             
// handle error         
}                  ..... 
} 

要完成握手,服务器必须使用适当的头进行响应。

// Handshake creates a handshake header    
 func (ws *WS) Handshake() error {                  
hash := func(key string) string {             
h := sha1.New()             
h.Write([]byte(key))             
h.Write([]byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))          
return base64.StdEncoding.EncodeToString(h.Sum(nil))        
 }(ws.header.Get("Sec-WebSocket-Key"))       ..... 
} 

“Sec-WebSocket-key”是随机生成的,采用base64编码。服务器需要在接受请求后将此键附加到固定字符串。假设您有x3JJHMbDL1EzLkh9GBhXDw== key。在本例中,可以使用SHA-1计算二进制值,并使用Base64对其进行编码。你会得到HSmrc0sMlYUkAGmm5OPpG2HaGWk =。将此值用作Sec-WebSocket-Accept响应头的值。

传输数据帧

当握手成功完成后,您的应用程序可以从客户机读写数据。WebSocket规范定义了在客户机和服务器之间使用的特定框架格式。这是一个小模式的框架:

43efc8d135e742c653d18d34fce96c3a.png

使用以下代码解码客户端负载(payload):

// Recv receives data and returns a Frame     
func (ws *WS) Recv() (frame Frame, _ error) {         
frame = Frame{}         
head, err := ws.read(2)         
if err != nil {             
// handle error        
 } 

反过来,这些代码行允许对数据进行编码:

// Send sends a Frame     
func (ws *WS) Send(fr Frame) error {         
// make a slice of bytes of length 2         
data := make([]byte, 2)              // Save fragmentation & opcode information in the first byte         
data[0] = 0x80 | fr.Opcode         
if fr.IsFragment {             
data[0] &= 0x7F         
}         ..... 

关闭一个握手

当一方发送一个关闭状态为有效负载的关闭帧时,握手就结束了。发送关闭帧的一方可以选择在有效负载中发送关闭原因。如果关闭是由客户机发起的,服务器应该发送相应的关闭帧作为响应。

// Close sends a close frame and closes the TCP connection 
func (ws *Ws) Close() error {     
f := Frame{}     
f.Opcode = 8     
f.Length = 2     
f.Payload = make([]byte, 2)     
binary.BigEndian.PutUint16(f.Payload, ws.status)    
 if err := ws.Send(f); err != nil {         
return err     
}     
return ws.conn.Close() 
} 

WebSocket库列表

有几个第三方库可以简化开发人员的工作,并极大地促进WebSockets的使用。

STDLIB (x/net/websocket)

这个WebSocket库是标准库的一部分。它为WebSocket协议实现了客户机和服务器,如RFC 6455规范中所述。它不需要安装,并且有很好的官方文档。但另一方面,它仍然缺乏一些可以在其他WebSocket库中找到的特性。/x/net/ WebSocket包中的Golang WebSocket实现不允许用户以一种清晰的方式在连接之间重用I/O缓冲区。

让我们检查一下STDLIB包是如何工作的。下面是执行基本功能的代码示例,比如创建连接、发送和接收消息。

首先,要安装和使用这个库,你应该把这行代码添加到你的:

import "http://golang.org/x/net/websocket"

Client side:

    // create connection     // schema can be ws:// or wss://     // host, port – WebSocket server     
conn, err := websocket.Dial("{schema}://{host}:{port}", "", op.Origin)     、
if err != nil {         
// handle error     
}      
defer conn.Close()              .......       
// send message         
if err = websocket.JSON.Send(conn, {message}); err != nil {         
// handle error     
}               .......         
// receive message     // messageType initializes some type of message     
message := messageType{}     
if err := websocket.JSON.Receive(conn, &message); err != nil {           
// handle error    
 }           ....... 

Server side:

    // Initialize WebSocket handler + server     
mux := http.NewServeMux()         
mux.Handle("/", websocket.Handler(func(conn *websocket.Conn) {       func() {                 for {                                      
// do something, receive, send, etc.                 
}            
 }             .......             
// receive message    
 // messageType initializes some type of message     
message := messageType{}     
if err := websocket.JSON.Receive(conn, &message); err != nil {         
// handle error    
 }         .......          
// send message    
 if err := websocket.JSON.Send(conn, message); err != nil {         
// handle error     
}             ........  

Gorilla

Gorilla web toolkit中的WebSocket包拥有完整且经过测试的WebSocket协议实现,以及一个稳定的包API。WebSocket包有良好的文档记录,易于使用。您可以在Gorilla官方网站上找到相关文档。

Installation

go get http://github.com/gorilla/websocket

Examples of code

Client side:

    // init     
// schema – can be ws:// or wss://     
// host, port – WebSocket server       
u := url.URL{         Scheme: {schema},         Host:   {host}:{port},         Path:   "/",     }     
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)     
if err != nil {         
// handle error     
}         .......         
// send message     
err := c.WriteMessage(websocket.TextMessage, {message})     
if err != nil {         
// handle error     
}           .......           
// receive message     
_, message, err := c.ReadMessage()     
if err != nil {         
// handle error     
}         ....... 

Server side:

  // init     
u := websocket.Upgrader{}     
c, err := u.Upgrade(w, r, nil)    
 if err != nil {         
// handle error     
}         .......         
// receive message     
messageType, message, err := c.ReadMessage()     
if err != nil {         
// handle error    
 }         .......            
// send message     
err = c.WriteMessage(messageType, {message})    
 if err != nil {         
// handle error     
}         ....... 
 

GOBWAS

这个小小的WebSocket 包有一系列功能强大的特性,比如零拷贝升级和一个底层API,它允许构建自定义包处理逻辑。

GOBWAS在I/O期间不需要中间分配。它还拥有围绕wsutil包中的API的高级包装器和助手,允许开发人员快速启动,而无需深入研究协议的内部。这个库有一个灵活的API,但这是以可用性和清晰性为代价的。

文档可以在GoDoc网站上找到。你可以安装它包括以下代码行:

go get http://github.com/gobwas/ws


Client side:

    // init     
// schema – can be ws or wss     
// host, port – ws server     
conn, _, _, err := ws.DefaultDialer.Dial(ctx, {schema}://{host}:{port})    
 if err != nil {        
 // handle error    
 }         .......    
 // send message     
err = wsutil.WriteClientMessage(conn, ws.OpText, {message})     
if err != nil {         // handle error     }              .......     
// receive message         
msg, _, err := wsutil.ReadServerData(conn)     
if err != nil {         // handle error     }         ....... 

Server side:

   // init    
 listener, err := net.Listen("tcp", op.Port)    
 if err != nil {         // handle error     }     
conn, err := listener.Accept()     
if err != nil {         // handle error     }     
upgrader := ws.Upgrader{}    
 if _, err = upgrader.Upgrade(conn); err != nil {         // handle error     }         .......    
 // receive message     
for {          
 reader := wsutil.NewReader(conn, ws.StateServerSide)          
_, err := reader.NextFrame()         
 if err != nil {              // handle error          }          
data, err := ioutil.ReadAll(reader)          
if err != nil {              // handle error          }             .......     
}            .......     
// send message     
msg := "new server message"     
if err := wsutil.WriteServerText(conn, {message}); err != nil {         // handle error     }         ....... 

GOWebsockets

这个工具提供了广泛的易于使用的特性。它允许并发控制、数据压缩和设置请求头。GoWebsockets支持用于发送和接收文本和二进制数据的代理和子协议。开发人员还可以启用或禁用SSL验证。

您可以在GoDoc网站和项目的GitHub页面上找到关于如何使用GOWebsockets的文档和示例。通过添加以下代码行安装包:

go get http://github.com/sacOO7/gowebsocket

Client side:

    // init     
// schema – can be ws or wss    
 // host, port – ws server     
socket := gowebsocket.New({schema}://{host}:{port})     
socket.Connect()         .......       
// send message     
socket.SendText({message})     or     socket.SendBinary({message})         .......     
// receive message     
socket.OnTextMessage = func(message string, socket gowebsocket.Socket) {         // hande received message     };     or     socket.OnBinaryMessage = func(data [] byte, socket gowebsocket.Socket) {         // hande received message     };           .......   

Server side:

    // init     
// schema – can be ws or wss     
// host, port – ws server     
conn, _, _, err := ws.DefaultDialer.Dial(ctx, {schema}://{host}:{port})     
if err != nil {         // handle error     }         .......             
// send message     
err = wsutil.WriteClientMessage(conn, ws.OpText, {message})     
if err != nil {         // handle error     }         .......            
// receive message         
msg, _, err := wsutil.ReadServerData(conn)    
 if err != nil {         // handle error     } 

以上我们描述了Golang中使用最广泛的四个WebSocket库。下表包含了这些工具的详细比较。

2d428231feb21f01fbc88857aeb67b62.png

为了更好地分析它们的性能,我们还进行了一些基准测试。研究结果如下:

26f49c553952f8bb9856d10ff9fb1be1.png

正如您所看到的,GOBWAS比其他库具有明显的优势。它每个操作的分配更少,每个分配使用的内存和时间也更少。另外,它的I/O分配为零。此外,GOBWAS提供了创建WebSocket客户机-服务器交互和接收消息片段所需的所有方法。您还可以使用它轻松地使用TCP套接字。

如果你真的不喜欢GOBWAS,你可以用Gorilla。它非常简单,几乎具有所有相同的功能。您也可以使用STDLIB,但是它在生产环境中没有那么好,因为它缺乏许多必要的特性,而且,正如您在基准测试中看到的,它提供的性能较差。GOWebsocket与STDLIB基本相同。但是如果你需要快速构建一个原型或者MVP,这是一个合理的选择。

除了这些工具之外,还有一些替代实现允许您构建强大的流解决方案。其中包括:

  • go-socket.io :https://github.com/googollee/go-socket.io
  • Apache Thrift :https://thrift.apache.org/tutorial/go
  • gRPC :https://grpc.io/
  • Package rpc :https://golang.org/pkg/net/rpc/

流媒体技术的不断发展,以及诸如WebSockets等文档良好的工具的可用性,使得开发人员可以很容易地创建真正的实时应用程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值