Go WebSocket 服务器:使用封闭的网络连接

我正在使用 websocket 服务器,由于某种原因它输出:

“WSARecv tcp 127.0.0.1:8080:使用关闭的网络连接。”

我不知道为什么会这样说,因为我在任何时候都没有关闭连接。

下面是服务器的一些源代码文件。如果需要,完整的源代码是 here on GitHub

连接.go

package net

import (
  "log"

  "golang.org/x/net/websocket"

  pnet "kekocity/misc/packet"
  "kekocity/interfaces"
)

type Connection struct {
  socket *websocket.Conn

  txChan chan pnet.INetMessageWriter
    rxChan chan pnet.INetMessageReader

  user interfaces.IUser
}

func NewConnection(_socket *websocket.Conn) *Connection {
  // The pointer allow us to modify connection struct from outside
  connection := &Connection{
    socket: _socket,
    txChan: make(chan pnet.INetMessageWriter),
    rxChan: make(chan pnet.INetMessageReader),
  }

  go connection.ReceivePoller()
  go connection.SendPoller()

  return connection
}

func (c *Connection) AssignToUser(_user interfaces.IUser) {
  if _user == nil {
    panic("net.connection: the user interface can not be nil!")
    return
  }

  c.user = _user
  _user.SetNetworkChans(c.rxChan, c.txChan)
}

/*
 * ReceivePoller and SendPoller starts listening when the first packet is verified and the new connection is started
 */
func (c *Connection) ReceivePoller() {
  for {
    packet := pnet.NewPacket()

    var buffer []uint8
        err := websocket.Message.Receive(c.socket, &buffer)

        if err == nil {
            copy(packet.Buffer[0:len(buffer)], buffer[0:len(buffer)])

            c.parsePacket(packet)
        } else {
            println(err.Error())
            break
        }
  }
}

func (c *Connection) SendPoller() {
  for {
    // Read messages from transmit channel
    message := <-c.txChan

    if message == nil {
      log.Println("SenPoller", "The message is nil, break the loop")
      break
    }

    // Convert netmessage to packet
    packet := message.WritePacket()
    packet.SetHeader()

    // Create byte buffer
    buffer := packet.GetBuffer()
    data := buffer[0:packet.GetMsgSize()]

    // Send bytes off to the internetz
    websocket.Message.Send(c.socket, data)
  }
}

func (c *Connection) parsePacket(_packet pnet.IPacket) {
  log.Println("net.connection:", "Received new packet!")
}

func (c *Connection) Close() {
  // Close channels
  close(c.txChan)
  close(c.rxChan)

  // Close the socket
  c.socket.Close()

  c.user = nil
}

服务器.go

package net

// <imports>
import (
  "log"
  "fmt"
  "net/http"

  "golang.org/x/net/websocket"

  pnet "kekocity/misc/packet"
  cmap "kekocity/misc/concurrentmap"
  "kekocity/data/helpers"
  "kekocity/net/message"
)

var server *Server

type Server struct {
  port int

  connectedUsers *cmap.ConcurrentMap
}

func init() {
    server = newServer()
}

func newServer() *Server {
    return &Server{
    port: 8080,
    connectedUsers: cmap.New(),
  }
}

func Listen(_port int) {
  server.port = _port

  log.Printf("Listening for connections on port %d!", _port)

  http.Handle("/ws", websocket.Handler(clientConnection))

    err := http.ListenAndServe(fmt.Sprintf(":%d", _port), nil)
    if err != nil {
        panic("ListenAndServe: " + err.Error())
    }
}

func clientConnection(clientsock *websocket.Conn) {
  packet := pnet.NewPacket()
  buffer := make([]uint8, pnet.PACKET_MAXSIZE)

  recv, err := clientsock.Read(buffer)

  if err == nil {
    copy(packet.Buffer[0:recv], buffer[0:recv])

    parseFirstMessage(clientsock, packet)
  } else {
    if err.Error() != "EOF" {
      log.Println("net.server", "Client connection error:", err.Error())
    }
  }
}

func parseFirstMessage(_conn *websocket.Conn, _packet *pnet.Packet) {
  _message := _packet.ToString()

  // If the first packet length is < 1 close the socket
  if len(_message) < 1 {
    _conn.Close()
    return
  }

  // Create the connection
  connection := NewConnection(_conn)

  // Authentication wrapper
  authPacket := &message.AuthMessage{}
  user, err := helpers.AuthHelper.AuthenticateUsingCredentials(_message)

  if err != nil {
    log.Fatal("Invalid credentials!")
    authPacket.Status = "error"
  } else {
    // Need to check if its already logged

    authPacket.Status = "success"

    connection.AssignToUser(user)
      connection.txChan <- authPacket

    return
  }

  // Send bad auth message and close
  connection.txChan <- authPacket
  connection.Close()
}

完整源代码: github

stack overflow Go WebSocket server: Use of closed network connection
原文答案

答案:

作者头像

处理程序完成后立即取消请求中的上下文。

serverHandler{c.server}.ServeHTTP(w, w.req)
w.cancelCtx()

这就是为什么你的上下文。在此图中,您可以找到如何在 server.Serve 方法中创建上下文。

HTTP context livetime

博客文章中对其进行了更详细的描述: HTTP context livetime

在 websocket 中,情况非常相似。处理程序完成后,上下文立即关闭。

func (s Server) serveWebSocket(w http.ResponseWriter, req *http.Request) {
    rwc, buf, err := w.(http.Hijacker).Hijack()
    if err != nil {
        panic("Hijack failed: " + err.Error())
    }
    // The server should abort the WebSocket connection if it finds
    // the client did not send a handshake that matches with protocol
    // specification.
    defer rwc.Close() // <- here! It's executed when the s.Handler(conn) exites
    conn, err := newServerConn(rwc, buf, req, &s.Config, s.Handshake)
    if err != nil {
        return
    }
    if conn == nil {
        panic("unexpected nil conn")
    }
    s.Handler(conn)
}

要解决这个问题,您可以从 context.Background() 创建一个新的上下文,如果需要,添加一些超时并改用它。