Heim >Backend-Entwicklung >Golang >Schreiben Sie einen einfachen WebSocket-Push-Dienst in der Go-Sprache

Schreiben Sie einen einfachen WebSocket-Push-Dienst in der Go-Sprache

尚
nach vorne
2019-11-25 16:13:232546Durchsuche

Schreiben Sie einen einfachen WebSocket-Push-Dienst in der Go-Sprache

Push-Service-Implementierung

Grundprinzip

Nachdem der Server gestartet wurde, Es werden zwei Handler registriert.

websocketHandler wird verwendet, um dem Browser das Senden einer Upgrade-Anfrage und das Upgrade auf eine WebSocket-Verbindung bereitzustellen.

pushHandler wird verwendet, um Anfragen an externe Push-Terminals zum Senden von Push-Daten bereitzustellen.

Der Browser stellt zunächst eine Verbindung zum websocketHandler her (die Standardadresse ist ws://ip:port/ws) und die Upgrade-Anfrage ist eine WebSocket-Verbindung. Wenn die Verbindung hergestellt ist, müssen Registrierungsinformationen gesendet werden Anmeldung. Die Registrierungsinformationen hier enthalten eine Token-Information.

Der Server überprüft das bereitgestellte Token und erhält die entsprechende Benutzer-ID (im Allgemeinen kann eine Benutzer-ID mit vielen Token gleichzeitig verknüpft sein) und speichert und verwaltet die Beziehung zwischen Token, Benutzer-ID und Verbindung (Verbindung). ).

Das Push-Ende sendet eine Anfrage zum Pushen von Daten an pushHandler (die Standardadresse ist ws://ip:port/push. Die Anfrage enthält das Benutzer-ID-Feld und das Nachrichtenfeld). Der Server ruft alle zu diesem Zeitpunkt mit dem Server verbundenen Verbindungen basierend auf der Benutzer-ID ab und überträgt die Nachrichten dann einzeln.

Aufgrund der Echtzeitnatur des Push-Dienstes werden und müssen die gepushten Daten nicht zwischengespeichert werden.

Detaillierte Erläuterung des Codes

Ich werde hier kurz die Grundstruktur des Codes beschreiben und auch über einige häufig verwendete Schreibmethoden und -muster in der Go-Sprache sprechen (Ich komme auch aus anderen Sprachen und wende mich der Go-Sprache zu. Schließlich ist die Go-Sprache auch noch recht jung. Wenn Sie also Vorschläge haben, geben Sie diese bitte weiter.

Da die meisten Erfinder und Hauptbetreuer der Go-Sprache aus der C/C++-Sprache stammen, ist der Code der Go-Sprache auch stärker auf das C/C++-System ausgerichtet.

Sehen Sie sich zunächst die Struktur des Servers an:

// Server defines parameters for running websocket server.
type Server struct {
    // Address for server to listen on
    Addr string

    // Path for websocket request, default "/ws".
    WSPath string

    // Path for push message, default "/push".
    PushPath string

    // Upgrader is for upgrade connection to websocket connection using
    // "github.com/gorilla/websocket".
    //
    // If Upgrader is nil, default upgrader will be used. Default upgrader is
    // set ReadBufferSize and WriteBufferSize to 1024, and CheckOrigin always
    // returns true.
    Upgrader *websocket.Upgrader

    // Check token if it's valid and return userID. If token is valid, userID
    // must be returned and ok should be true. Otherwise ok should be false.
    AuthToken func(token string) (userID string, ok bool)

    // Authorize push request. Message will be sent if it returns true,
    // otherwise the request will be discarded. Default nil and push request
    // will always be accepted.
    PushAuth func(r *http.Request) bool

    wh *websocketHandler
    ph *pushHandler
}

Hier sprechen wir über den Upgrader *websocket.Upgrader, der das Objekt des Gorilla/Websocket-Pakets ist, das zum Upgrade von HTTP verwendet wird Anfragen.

Wenn eine Struktur zu viele Parameter hat, empfiehlt es sich normalerweise nicht, sie direkt zu initialisieren, sondern die von ihr bereitgestellte New-Methode zu verwenden. Hier ist es:

// NewServer creates a new Server.func NewServer(addr string) *Server {    return &Server{
        Addr:     addr,
        WSPath:   serverDefaultWSPath,
        PushPath: serverDefaultPushPath,
    }
}

Dies ist auch eine häufige Verwendung der Go-Sprache, um der Außenwelt Initialisierungsmethoden bereitzustellen.

Dann verwendet der Server die ListenAndServe-Methode, um den Port zu starten und abzuhören, ähnlich der Verwendung des http-Pakets:

// ListenAndServe listens on the TCP network address and handle websocket
// request.
func (s *Server) ListenAndServe() error {
    b := &binder{
        userID2EventConnMap: make(map[string]*[]eventConn),
        connID2UserIDMap:    make(map[string]string),
    }

    // websocket request handler
    wh := websocketHandler{
        upgrader: defaultUpgrader,
        binder:   b,
    }
    if s.Upgrader != nil {
        wh.upgrader = s.Upgrader
    }
    if s.AuthToken != nil {
        wh.calcUserIDFunc = s.AuthToken
    }
    s.wh = &wh
    http.Handle(s.WSPath, s.wh)

    // push request handler
    ph := pushHandler{
        binder: b,
    }
    if s.PushAuth != nil {
        ph.authFunc = s.PushAuth
    }
    s.ph = &ph
    http.Handle(s.PushPath, s.ph)

    return http.ListenAndServe(s.Addr, nil)
}

Hier haben wir zwei Handler generiert, nämlich websocketHandler und pushHandler. websocketHandler ist dafür verantwortlich, eine Verbindung mit dem Browser herzustellen und Daten zu übertragen, während pushHandler Push-Seiten-Anfragen verarbeitet.

Wie Sie sehen können, kapseln beide Handler hier ein Binderobjekt. Dieser Ordner wird verwendet, um die Beziehung zwischen Token <-> userID <->

// binder is defined to store the relation of userID and eventConn
type binder struct {
    mu sync.RWMutex

    // map stores key: userID and value of related slice of eventConn
    userID2EventConnMap map[string]*[]eventConn

    // map stores key: connID and value: userID
    connID2UserIDMap map[string]string
}

Sehr einfache Struktur. websocketHandler implementiert die http.Handler-Schnittstelle:

// websocketHandler defines to handle websocket upgrade request.
type websocketHandler struct {
    // upgrader is used to upgrade request.
    upgrader *websocket.Upgrader

    // binder stores relations about websocket connection and userID.
    binder *binder

    // calcUserIDFunc defines to calculate userID by token. The userID will
    // be equal to token if this function is nil.
    calcUserIDFunc func(token string) (userID string, ok bool)
}

Konvertiert zunächst die eingehende http.Request in websocket.Conn und packt sie dann in unsere angepasste wserver.Conn (Kapselung oder Kombination ist eine typische Verwendung der Go-Sprache . Denken Sie daran, die Go-Sprache hat keine Vererbung, nur Zusammensetzung.

Dann werden die AfterReadFunc- und BeforeCloseFunc-Methoden von Conn festgelegt und anschließend conn.Listen() gestartet. AfterReadFunc bedeutet, dass Conn nach dem Lesen der Daten versucht, die Benutzer-ID basierend auf dem Token zu überprüfen und zu berechnen, und dann bind die Bindung registriert. BeforeCloseFunc führt den Entbindungsvorgang aus, bevor Conn geschlossen wird.

pushHandler

pushHandler ist leicht zu verstehen. Es analysiert die Anfrage und überträgt dann die Daten:

// First try to upgrade connection to websocket. If success, connection will
// be kept until client send close message or server drop them.
func (wh *websocketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    wsConn, err := wh.upgrader.Upgrade(w, r, nil)
    if err != nil {
        return
    }
    defer wsConn.Close()

    // handle Websocket request
    conn := NewConn(wsConn)
    conn.AfterReadFunc = func(messageType int, r io.Reader) {
        var rm RegisterMessage
        decoder := json.NewDecoder(r)
        if err := decoder.Decode(&rm); err != nil {
            return
        }

        // calculate userID by token
        userID := rm.Token
        if wh.calcUserIDFunc != nil {
            uID, ok := wh.calcUserIDFunc(rm.Token)
            if !ok {
                return
            }
            userID = uID
        }

        // bind
        wh.binder.Bind(userID, rm.Event, conn)
    }
    conn.BeforeCloseFunc = func() {
        // unbind
        wh.binder.Unbind(conn)
    }

    conn.Listen()
}

Die Hauptmethode ist Listen():

// Authorize if needed. Then decode the request and push message to each
// realted websocket connection.
func (s *pushHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        w.WriteHeader(http.StatusMethodNotAllowed)
        return
    }

    // authorize
    if s.authFunc != nil {
        if ok := s.authFunc(r); !ok {
            w.WriteHeader(http.StatusUnauthorized)
            return
        }
    }

    // read request
    var pm PushMessage
    decoder := json.NewDecoder(r.Body)
    if err := decoder.Decode(&pm); err != nil {
        w.WriteHeader(http.StatusBadRequest)
        w.Write([]byte(ErrRequestIllegal.Error()))
        return
    }

    // validate the data
    if pm.UserID == "" || pm.Event == "" || pm.Message == "" {
        w.WriteHeader(http.StatusBadRequest)
        w.Write([]byte(ErrRequestIllegal.Error()))
        return
    }

    cnt, err := s.push(pm.UserID, pm.Event, pm.Message)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        w.Write([]byte(err.Error()))
        return
    }

    result := strings.NewReader(fmt.Sprintf("message sent to %d clients", cnt))
    io.Copy(w, result)
}
Conn
Conn (此处指 wserver.Conn) 为 websocket.Conn 的包装。

// Conn wraps websocket.Conn with Conn. It defines to listen and read
// data from Conn.
type Conn struct {
    Conn *websocket.Conn

    AfterReadFunc   func(messageType int, r io.Reader)
    BeforeCloseFunc func()

    once   sync.Once
    id     string
    stopCh chan struct{}
}

Richtet hauptsächlich die Verarbeitung und das kontinuierliche Lesen von Daten ein, wenn die Websocket-Verbindung geschlossen ist.

Empfohlen: g

Olang-Tutorial

Das obige ist der detaillierte Inhalt vonSchreiben Sie einen einfachen WebSocket-Push-Dienst in der Go-Sprache. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:cnblogs.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen