Heim  >  Artikel  >  Backend-Entwicklung  >  So implementieren Sie ein Netzwerkprogrammierungs-Framework mit hoher Parallelität in der Go-Sprache

So implementieren Sie ein Netzwerkprogrammierungs-Framework mit hoher Parallelität in der Go-Sprache

PHPz
PHPzOriginal
2023-04-06 09:11:581101Durchsuche

Bei der Realisierung von Netzwerkprogrammierung mit hoher Parallelität ist das Workerman-Framework der PHP-Sprache seit jeher für seine hervorragende Leistung sowie Einfachheit und Benutzerfreundlichkeit bekannt. Im Vergleich zur PHP-Sprache eignet sich Golang jedoch besser für die Entwicklung hoher Parallelität und verteilter Systeme, sodass die Implementierung der Golang-Version des Workerman-Frameworks für viele Entwickler zum Ziel geworden ist. In diesem Artikel stellen wir vor, wie man mit der Golang-Sprache ein Framework für die Netzwerkprogrammierung mit hoher Parallelität implementiert, ähnlich wie Workerman.

1. Vorkenntnisse

Bevor wir beginnen, müssen wir einige Grundkenntnisse beherrschen:

1. Grundlegende Konzepte wie Variablen, Funktionen, Strukturen, Schnittstellen usw.

2. Grundlagen der Netzwerkprogrammierung: Grundkenntnisse von TCP/UDP, HTTP und anderen Protokollen.

3.Goroutine: Die Coroutine der Golang-Sprache kann die Effizienz der gleichzeitigen Programmierung erheblich verbessern.

4.Kanal: Ein von der Golang-Sprache bereitgestellter Kommunikationsmechanismus, der zur Datenübertragung und Synchronisierung zwischen verschiedenen Coroutinen verwendet werden kann.

5.Auswählen: Ein von der Golang-Sprache bereitgestellter Multiplexmechanismus, der den Status mehrerer Kanäle überwachen und die Programmeffizienz verbessern kann.

2. Framework-Architektur

Je nach Implementierung des Worker-Frameworks können wir es in drei Teile unterteilen:

1.

2. Geschäftsprozess zur Bearbeitung von Kundenanfragen.

3. Überwachen Sie den Client-Verbindungsstatus und recyceln Sie ihn.

In der Golang-Sprache können wir Goroutine verwenden, um jeweils die oben genannten drei Teile zu implementieren.

1. Empfangen Sie die Verbindung und generieren Sie den Client

Wir können das mit der Golang-Sprache gelieferte „net“-Paket verwenden, um einen TCP-Server zu erstellen und gleichzeitig eine Goroutine zu öffnen, um den Client-Verbindungsstatus zu überwachen.

import (
  "fmt"
  "net"
)

func main() {
  listener, err := net.Listen("tcp", "127.0.0.1:8080")
  if err != nil {
    fmt.Println("failed to listen:", err)
    return
  }

  go func() {
    for {
      conn, err := listener.Accept()
      if err != nil {
        fmt.Println("failed to accept:", err)
        continue
      }
      // 生成客户端
    }
  }()

  // 等待进程退出
  select {}
}

Nachdem wir die Client-Verbindung erhalten haben, müssen wir ein Client-Objekt kapseln, um alle Anfragen und Antworten für die Verbindung zu verarbeiten.

type Client struct {
  Conn   net.Conn
  RespCh chan []byte
}

func NewClient(conn net.Conn) *Client {
  return &Client {
    Conn:   conn,
    RespCh: make(chan []byte, 10),
  }
}

2. Geschäftsprozess zur Bearbeitung von Kundenanfragen

Die Anfragen und Antworten des Kunden werden direkt über den Kanal übermittelt. Wenn eine neue Verbindung empfangen wird, müssen wir sie in ein Client-Objekt kapseln und eine Goroutine starten, um die Verbindung zu verarbeiten. Diese Goroutine hört alle vom Client über den Kanal gesendeten Anfragen ab und antwortet entsprechend.

Wir kapseln den Geschäftsprozess in eine Handler-Schnittstelle.

type Handler interface {
  OnConnect(*Client) error
  OnMessage(*Client, []byte) error
  OnClose(*Client) error
}

Die Anfragen und Antworten des Clients werden über das RespCh-Attribut des Client-Objekts weitergeleitet. Daher müssen wir in der Handler-Schnittstelle eine RespCh-Eigenschaft definieren, um die Antwort vom Client zu empfangen.

type Handler interface {
  OnConnect(*Client) error
  OnMessage(*Client, []byte) error
  OnClose(*Client) error
  RespCh() chan []byte
}

Wir können einen EchoHandler erstellen, um die Handler-Schnittstelle zu implementieren.

type EchoHandler struct {
  clients   []*Client
  respChan  chan []byte
}

func NewEchoHandler() *EchoHandler {
  return &EchoHandler{
    clients:  make([]*Client, 0),
    respChan: make(chan []byte, 10),
  }
}

func (h *EchoHandler) OnConnect(c *Client) error {
  h.clients = append(h.clients, c)
  return nil
}

func (h *EchoHandler) OnMessage(c *Client, data []byte) error {
  // 将客户端发送的数据广播给所有其他客户端,并将其存入respChan中
  for _, client := range h.clients {
    if client == c {
      continue
    }
    client.RespCh <- data
  }
  return nil
}

func (h *EchoHandler) OnClose(c *Client) error {
  for index, client := range h.clients {
    if client == c {
      h.clients = append(h.clients[:index], h.clients[index+1:]...)
    }
  }
  return nil
}

func (h *EchoHandler) RespCh() chan []byte {
  return h.respChan
}

Wenn das Client-Objekt jedes verbundenen Clients im Client-Array gespeichert ist, können wir die von jedem Client gesendeten Daten über das RespCh-Attribut empfangen und die Funktion zum Senden der vom Client gesendeten Informationen an andere Clients implementieren.

3. Überwachen Sie den Client-Verbindungsstatus und recyceln Sie ihn. Bei der alten Version des Workerman-Frameworks recycelt Workerman inaktive Verbindungen innerhalb eines bestimmten Zeitraums. Die neue Version von Workerman implementiert diese Funktion über TCP Keepalive.

Bei der Implementierung der Golang-Version von workererman können wir das Problem der Leerlaufverbindung auch durch TCP-Keepalive lösen. Wir können den Status seines Sockets in der Goroutine jedes Clients überwachen. Wenn ein Client nach 10 Sekunden Leerlaufzeit keine Daten sendet, wird dies als illegale Verbindung betrachtet und sein Socket wird geschlossen.

func (c *Client) Process() {
  defer func() {
    c.Conn.Close()
    c.handler.OnClose(c)
  }()
  // 设置 socket keepalive
  tcpConn, ok := c.Conn.(*net.TCPConn)
  if ok {
    tcpConn.SetKeepAlive(true)
    tcpConn.SetKeepAlivePeriod(10 * time.Second)
  }
  // 进入读协程,接收客户端发送的所有数据
  go func() {
    for {
      buf := make([]byte, 1024)
      n, err := c.Conn.Read(buf)
      if err != nil {
        if err != io.EOF {
          fmt.Println("failed to read:", err)
        }
        break
      }
      // 将客户端发送的消息交给Handler处理
      c.handler.OnMessage(c, buf[:n])
    }
  }()
  // 进入写协程,将respChan中的所有响应发送给当前客户端
  go func() {
    for resp := range c.handler.RespCh() {
      _, err := c.Conn.Write(resp)
      if err != nil {
        fmt.Println("failed to write:", err)
        break
      }
    }
  }()
  // OnConnect
  err := c.handler.OnConnect(c)
  if err != nil {
    fmt.Println("failed to on connect:", err)
    return
  }
  // 在Worker进程退出时进行清理
  select {}
}

3. Implementieren Sie den Worker-Prozess

Nach Abschluss der oben genannten drei Schritte müssen wir einen Worker-Prozess erstellen, um alle Clientverbindungen zu verwalten. Im Worker-Prozess müssen ein oder mehrere Handler geladen werden, um alle vom Client gesendeten Datenanforderungen zu verarbeiten.

type Worker struct {
  listener  net.Listener
  handlers  map[string]Handler
}

func NewWorker(addr string) (*Worker, error) {
  listener, err := net.Listen("tcp", addr)
  if err != nil {
    fmt.Println("failed to listen:", err)
    return nil, err
  }
  return &Worker{
    listener: listener,
    handlers: make(map[string]Handler),
  }, nil
}

func (w *Worker) Register(name string, handler Handler) {
  w.handlers[name] = handler
}

func (w *Worker) Start() {
  go func() {
    for {
      conn, err := w.listener.Accept()
      if err != nil {
        fmt.Println("failed to accept:", err)
        continue
      }
      // 封装连接客户端为Client对象,用于后续的处理
      client := NewClient(conn)
      client.handler = w.handlers["Echo"]
      // 开启客户端goroutine来处理该连接
      go client.Process()
    }
  }()
  // 等待进程退出
  select {}
}

Im Worker-Prozess müssen wir ein Handler-Attribut definieren, um Instanzen verschiedener Handler zu speichern, und in der Start()-Funktion auf Client-Verbindungen warten und neue Goroutinen starten, um Client-Anfragen zu bearbeiten.

4. Test

Wir können den folgenden Code verwenden, um einen Worker-Prozess zu erstellen und darin einen EchoHandler zu registrieren, der alle Client-Anfragen verarbeitet.

func main() {
  server, _ := NewWorker("127.0.0.1:8080")
  handler := NewEchoHandler()
  server.Register("Echo", handler)
  server.Start()
}

Mit dem Telnet-Tool können wir simulieren, dass mehrere Clients Nachrichten an den Server senden, und deren Empfang anzeigen.

Wir verwenden den folgenden Befehl, um eine Verbindung zum Server herzustellen:

telnet 127.0.0.1 8080

Wir können den folgenden Text in Telnet eingeben:

Hello workerman!

Wir können mehrere Telnet-Fenster gleichzeitig öffnen, um mehrere parallele Client-Anfragen zu simulieren.

Auf dem Server können wir die Ausgabe sehen:

$ go run worker.go 
服务器已启动...
failed to read: read tcp 127.0.0.1:8080->127.0.0.1:56182: use of closed network connection

Dies liegt daran, dass beim Schließen der Clientverbindung der Überwachungsvorgang abgebrochen wird, was zu einem Lesefehler führt.

Nachdem die Telnet-Eingabe abgeschlossen ist, können wir sehen, dass jedes Telnet-Fenster den vom Server zurückgegebenen Text empfängt.

5. Zusammenfassung

In diesem Artikel haben wir vorgestellt, wie man mit der Golang-Sprache ein hochgradig gleichzeitiges Netzwerkprogrammierungs-Framework implementiert, ähnlich dem Workerman in der PHP-Sprache. Während des Implementierungsprozesses haben wir die Coroutine, den Kommunikationsmechanismus und den Multiplexmechanismus in der Golang-Sprache verwendet und durch Kapselung des Client-Objekts und der Handler-Schnittstelle erfolgreich ein Netzwerkprogrammierungsframework mit hoher Parallelität ähnlich wie Workerman implementiert.

Tatsächlich empfehlen wir bei der täglichen Programmierung, direkt das von der Golang-Sprache bereitgestellte Net/http-Paket zu verwenden, um eine Netzwerkprogrammierung mit hoher Parallelität zu implementieren, die prägnanter ist und eine bessere Leistung als das Workerman-Framework bietet. Wir müssen lediglich einen http-Server öffnen und Goroutine verwenden, um jede Anfrage gleichzeitig zu verarbeiten und so problemlos eine Netzwerkprogrammierung mit hoher Parallelität zu implementieren.

Das obige ist der detaillierte Inhalt vonSo implementieren Sie ein Netzwerkprogrammierungs-Framework mit hoher Parallelität in der Go-Sprache. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn