Maison  >  Article  >  développement back-end  >  Comment implémenter un cadre de programmation réseau à haute concurrence en langage Go

Comment implémenter un cadre de programmation réseau à haute concurrence en langage Go

PHPz
PHPzoriginal
2023-04-06 09:11:581103parcourir

Dans la réalisation de programmation réseau à haute concurrence, le framework Workerman du langage PHP a toujours été connu pour ses excellentes performances, sa simplicité et sa facilité d'utilisation. Cependant, comparé au langage PHP, Golang est plus adapté au développement de systèmes distribués et à haute concurrence, donc la mise en œuvre de la version Golang du framework Workerman est devenue la poursuite de nombreux développeurs. Dans cet article, nous présenterons comment utiliser le langage Golang pour implémenter un cadre de programmation réseau à haute concurrence, similaire à Workerman.

1. Connaissances préalables

Avant de commencer, nous devons maîtriser quelques connaissances de base :

1. Les bases du langage Golang : concepts de base tels que les variables, les fonctions, les structures, les interfaces, etc.

2. Bases de la programmation réseau : connaissances de base de TCP/UDP, HTTP et autres protocoles.

3.Goroutine : La coroutine du langage Golang peut considérablement améliorer l'efficacité de la programmation simultanée.

4.Channel : Un mécanisme de communication fourni par le langage Golang, qui peut être utilisé pour la transmission de données et la synchronisation entre différentes coroutines.

5.Sélectionner : un mécanisme de multiplexage fourni par le langage Golang, qui peut surveiller l'état de plusieurs canaux et améliorer l'efficacité du programme.

2. Architecture du framework

Selon la mise en œuvre du framework worker, nous pouvons le diviser en trois parties :

1. Recevoir les connexions et générer des clients.

2. Processus métier utilisé pour traiter les demandes des clients.

3. Surveillez l'état de la connexion du client et recyclez-le.

En langage Golang, nous pouvons utiliser la goroutine pour implémenter respectivement les trois parties ci-dessus.

1. Recevez la connexion et générez le client

Nous pouvons utiliser le package "net" fourni avec le langage Golang pour créer un serveur TCP, et en même temps ouvrir une goroutine pour surveiller l'état de la connexion du client.

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 {}
}

Après avoir reçu la connexion client, nous devons encapsuler un objet Client pour gérer toutes les demandes et réponses pour la connexion.

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. Processus métier utilisé pour traiter les demandes des clients

Les demandes et réponses du client sont transmises directement via le canal. Lorsqu'une nouvelle connexion est reçue, nous devons l'encapsuler dans un objet Client et démarrer une goroutine pour gérer la connexion. Cette goroutine écoutera toutes les demandes envoyées par le client via le canal et répondra en conséquence.

Nous encapsulons le processus métier dans une interface Handler.

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

Les demandes et réponses du client sont transmises via l'attribut RespCh de l'objet Client. Par conséquent, dans l'interface Handler, nous devons définir une propriété RespCh pour recevoir la réponse du client.

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

Nous pouvons créer un EchoHandler pour implémenter l'interface Handler.

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
}

Lorsque l'objet Client de chaque client connecté est stocké dans le tableau clients, nous pouvons recevoir les données envoyées par chaque client via l'attribut RespCh, et implémenter la fonction de diffusion des informations envoyées par le client aux autres clients.

3. Surveillez l'état de la connexion client et recyclez-la

Pour l'ancienne version du framework Workerman, Workerman recyclera les connexions inactives dans un certain laps de temps. La nouvelle version de Workerman implémente cette fonction via TCP keepalive.

Lors de la mise en œuvre de la version Golang de Workererman, nous pouvons également résoudre le problème de connexion inactive via TCP keepalive. Nous pouvons surveiller l'état de son socket dans la goroutine de chaque client. Si un client n'envoie pas de données après 10 secondes d'inactivité, cela sera considéré comme une connexion illégale et son socket sera fermé.

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. Implémenter le processus Worker

Après avoir terminé les trois étapes ci-dessus, nous devons créer un processus Worker pour gérer toutes les connexions client. Un ou plusieurs gestionnaires doivent être chargés dans le processus Worker pour gérer toutes les demandes de données envoyées par le client.

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 {}
}

Dans le processus Worker, nous devons définir un attribut handlers pour stocker les instances de différents handlers, et dans la fonction Start(), écouter les connexions client et démarrer de nouvelles goroutines pour gérer les demandes des clients.

4. Test

Nous pouvons utiliser le code suivant pour créer un processus Worker et y enregistrer un EchoHandler pour gérer toutes les demandes des clients.

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

Nous pouvons utiliser l'outil telnet pour simuler plusieurs clients envoyant des messages au serveur et visualiser leur réception.

Nous utilisons la commande suivante pour nous connecter au serveur :

telnet 127.0.0.1 8080

Nous pouvons saisir le texte suivant dans telnet :

Hello workerman!

Nous pouvons ouvrir plusieurs fenêtres telnet en même temps pour simuler plusieurs requêtes parallèles de clients.

Sur le serveur, nous pouvons voir le résultat :

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

En effet, lorsque nous fermons la connexion client, nous annulons son opération d'écoute, ce qui entraîne une erreur de lecture.

Une fois la saisie telnet terminée, nous pouvons voir que chaque fenêtre telnet recevra le texte renvoyé par le serveur.

5. Résumé

Dans cet article, nous avons présenté comment utiliser le langage Golang pour implémenter un cadre de programmation réseau hautement concurrent, similaire au Workerman du langage PHP. Au cours du processus de mise en œuvre, nous avons utilisé la coroutine, le mécanisme de communication et le mécanisme de multiplexage du langage Golang, et avons implémenté avec succès un cadre de programmation réseau à haute concurrence similaire à Workerman en encapsulant l'objet Client et l'interface Handler.

En fait, dans la programmation quotidienne, nous recommandons d'utiliser directement le package net/http fourni par le langage Golang pour implémenter une programmation réseau à haute concurrence, qui est plus concise et offre de meilleures performances que le framework Workerman. Il nous suffit d'ouvrir un serveur http et d'utiliser goroutine pour traiter chaque requête simultanément afin de mettre en œuvre facilement une programmation réseau à haute concurrence.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn