英文:
How to register new websocket connections gofiber
问题
我正在为你翻译以下内容:
我正在尝试设置一个简单的 WebSocket 服务器,它应该在未知的时间间隔内为客户端提供一些内容。
我的代码目前如下所示:
router.go
func SetupRoutes(app *fiber.App) error {
app.Get("/whop/validate", handler.HandleWhopValidate)
/*其他非 WebSocket 路由*/
/*...*/
app.Get("/ws/monitor", websocket.New(wsHandler.HandleWsMonitor))
app.Use(func(c *fiber.Ctx) error {
c.SendStatus(404)
return c.Next()
})
return nil
}
handler.go
package handlers
import (
"fmt"
"log"
"github.com/gofiber/websocket/v2"
)
var register = make(chan *websocket.Conn)
var unregister = make(chan *websocket.Conn)
func HandleWsMonitor(c *websocket.Conn) {
go SocketListener()
defer func() {
unregister <- c
//可能需要在重新关闭之前检查连接是否已经关闭?
c.Close()
}()
//将连接发送到通道中
register <- c
for {
messageType, message, err := c.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Println("read error:", err)
}
return
}
if messageType == websocket.TextMessage {
log.Println("got textmessage:", string(message))
} else {
log.Println("received message of type:", messageType)
}
}
}
func SocketListener() {
for {
select {
case c := <-register:
messageType, message, err := c.ReadMessage()
if err != nil {
log.Println(err)
unregister <- c
return
}
fmt.Printf("Got message of type: %d\nMessage:%s\n", messageType, string(message))
fmt.Printf("Connection Params: %s\n", c.Params("id"))
//将连接添加到客户端列表中
case c := <-unregister:
//从客户端列表中移除连接
c.Close()
fmt.Printf("Closed connection\n")
}
}
}
我遇到的问题是,当我连接到 WebSocket 时,我的注册的 select case 没有被触发(我希望使用之前提供给客户端的 UUID 将客户端连接注册到一个映射中)。
client.go
package main
import (
"flag"
"log"
"net/url"
"github.com/fasthttp/websocket"
)
type Client struct {
C *websocket.Conn
}
func main() {
addr := flag.String("addr", "localhost:8080", "http service address")
u := url.URL{
Scheme: "ws",
Host: *addr,
Path: "/ws/monitor",
RawQuery: "id=12",
}
wsClient := &Client{}
log.Printf("connecting to %s\n", u.String())
// 连接到 WebSocket 服务器
conn, resp, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
log.Fatal("Dial:", err)
}
wsClient.C = conn
if resp != nil {
log.Println("Got response:", resp)
}
defer wsClient.closeConn()
}
func (client *Client) closeConn() {
err := client.C.WriteMessage(
websocket.CloseMessage,
websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""),
)
if err != nil {
log.Println("Write close:", err)
return
}
client.C.Close()
log.Println("Connection closed")
}
在 handler.go 中是否有什么我忽略的地方,或者我在连接到服务器时是否应该采取不同的方法?
英文:
I'm trying to set up a simple websocket server that should serve the client some content at unknown intervals.
My code currently looks like this:
router.go
func SetupRoutes(app *fiber.App) error {
app.Get("/whop/validate", handler.HandleWhopValidate)
/*Other non-websocket routes*/
/*...*/
app.Get("/ws/monitor", websocket.New(wsHandler.HandleWsMonitor))
app.Use(func(c *fiber.Ctx) error {
c.SendStatus(404)
return c.Next()
})
return nil
}
handler.go
package handlers
import (
"fmt"
"log"
"github.com/gofiber/websocket/v2"
)
var register = make(chan *websocket.Conn)
var unregister = make(chan *websocket.Conn)
func HandleWsMonitor(c *websocket.Conn) {
go SocketListener()
defer func() {
unregister <- c
//may need to check whether connection is already closed before re-closing?
c.Close()
}()
//sends conn into channel
register <- c
for {
messageType, message, err := c.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Println("read error:", err)
}
return
}
if messageType == websocket.TextMessage {
log.Println("got textmessage:", string(message))
} else {
log.Println("received message of type:", messageType)
}
}
}
func SocketListener() {
for {
select {
case c := <-register:
messageType, message, err := c.ReadMessage()
if err != nil {
log.Println(err)
unregister <- c
return
}
fmt.Printf("Got message of type: %d\nMessage:%s\n", messageType, string(message))
fmt.Printf("Connection Params: %s\n", c.Params("id"))
//append to list of co
case c := <-unregister:
//remove conection from list of clients
c.Close()
fmt.Printf("Closed connection\n")
}
}
}
The issue I'm having is that when I connect to the websocket, my select case for register is not hit (I'd want to register the client connection to a map using a uuid that is previously provided to the client).
client.go
package main
import (
"flag"
"log"
"net/url"
"github.com/fasthttp/websocket"
)
type Client struct {
C *websocket.Conn
}
func main() {
addr := flag.String("addr", "localhost:8080", "http service address")
u := url.URL{
Scheme: "ws",
Host: *addr,
Path: "/ws/monitor",
RawQuery: "id=12",
}
wsClient := &Client{}
log.Printf("connecting to %s\n", u.String())
// Connect to the WebSocket server
conn, resp, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
log.Fatal("Dial:", err)
}
wsClient.C = conn
if resp != nil {
log.Println("Got response:", resp)
}
defer wsClient.closeConn()
}
func (client *Client) closeConn() {
err := client.C.WriteMessage(
websocket.CloseMessage,
websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""),
)
if err != nil {
log.Println("Write close:", err)
return
}
client.C.Close()
log.Println("Connection closed")
}
Is there something I'm missing in handler.go or should i just be taking a different approach when connecting to the server with my client?
答案1
得分: 1
根据我的测试,注册选择(register)确实被触发了(我附上了我使用的代码)。
但是我在代码中发现了其他问题:
unregister
通道是无缓冲的,在SocketListener
中的unregister <- c
会被阻塞。当代码执行到unregister <- c
时,它与case c := <-unregister
之间发生了死锁。- 看起来我们只需要一个
SocketListener
的goroutine来处理整个服务器。如果是这样的话,它应该移出HandleWsMonitor
函数之外。 HandleWsMonitor
和SocketListener
都从连接中读取数据。SocketListener
的责任是什么?看起来它不应该从连接中读取数据。
再仔细思考一下,似乎你可以直接在HandleWsMonitor
中向映射表中添加连接和删除连接。完全可以移除SocketListener
。在设计中,简洁性应该是一个关键目标。参考KISS原则。
package main
import (
"log"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/websocket/v2"
)
var (
register = make(chan *websocket.Conn)
unregister = make(chan *websocket.Conn)
)
func main() {
// 让我们更容易找到打印日志的行。
log.SetFlags(log.Lshortfile)
app := fiber.New()
app.Get("/ws/monitor", websocket.New(HandleWsMonitor))
log.Fatal(app.Listen(":8080"))
}
func HandleWsMonitor(c *websocket.Conn) {
// 看起来我们只需要一个SocketListener的goroutine来处理整个服务器。
// 如果是这样的话,下一行应该移出这个函数之外。
go SocketListener()
defer func() {
unregister <- c
c.Close()
}()
register <- c
for {
messageType, message, err := c.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Println("read error:", err)
}
return
}
if messageType == websocket.TextMessage {
log.Println("got textmessage:", string(message))
} else {
log.Println("received message of type:", messageType)
}
}
}
func SocketListener() {
for {
select {
case c := <-register:
// 这在日志中出现了。
log.Println("case c := <-register")
messageType, message, err := c.ReadMessage()
if err != nil {
log.Println(err)
// unregister是无缓冲的,发送操作会被阻塞。
unregister <- c
// 如果我们只使用一个SocketListener的goroutine,那么它不应该在这里返回。
return
}
log.Printf("Got message of type: %d\nMessage:%s\n", messageType, string(message))
log.Printf("Connection Params: %s\n", c.Params("id"))
case c := <-unregister:
c.Close()
log.Println("Closed connection")
}
}
}
英文:
The select case for register did hit according to my test (the code I used is attached to the bottom of this answer).
But I found other issues in the code:
- The
unregister
chan is unbuffered, andunregister <- c
inSocketListener
will be blocked. When the code reachesunregister <- c
, there is a deadlock between it andcase c := <-unregister
. - It seems we only need one
SocketListener
goroutine for the whole server. If this is the case, it should be moved outside ofHandleWsMonitor
. - Both
HandleWsMonitor
andSocketListener
read from the connection. What's the responsibility ofSocketListener
? It seems that it should not read from the connection.
Thinking it more, it seems that you can add connections to and delete connections from the map in HandleWsMonitor
directly. SocketListener
can be removed entirely. Simplicity should be a key goal in design. See the KISS principle.
package main
import (
"log"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/websocket/v2"
)
var (
register = make(chan *websocket.Conn)
unregister = make(chan *websocket.Conn)
)
func main() {
// Make it easy to find out which line prints the log.
log.SetFlags(log.Lshortfile)
app := fiber.New()
app.Get("/ws/monitor", websocket.New(HandleWsMonitor))
log.Fatal(app.Listen(":8080"))
}
func HandleWsMonitor(c *websocket.Conn) {
// It seems the we only need one SocketListener goroutine for the whole server.
// If this is the case, the next line should be moved outside of this func.
go SocketListener()
defer func() {
unregister <- c
c.Close()
}()
register <- c
for {
messageType, message, err := c.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Println("read error:", err)
}
return
}
if messageType == websocket.TextMessage {
log.Println("got textmessage:", string(message))
} else {
log.Println("received message of type:", messageType)
}
}
}
func SocketListener() {
for {
select {
case c := <-register:
// This did appear in the log.
log.Println("case c := <-register")
messageType, message, err := c.ReadMessage()
if err != nil {
log.Println(err)
// unregister is unbuffered, the sending will be blocked.
unregister <- c
// If we use only one SocketListener goroutine then it should
// not return here.
return
}
log.Printf("Got message of type: %d\nMessage:%s\n", messageType, string(message))
log.Printf("Connection Params: %s\n", c.Params("id"))
case c := <-unregister:
c.Close()
log.Println("Closed connection")
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论