ホームページ >バックエンド開発 >Golang >Go 言語で同時実行性の高いネットワーク プログラミング フレームワークを実装する方法

Go 言語で同時実行性の高いネットワーク プログラミング フレームワークを実装する方法

PHPz
PHPzオリジナル
2023-04-06 09:11:581160ブラウズ

高同時実行ネットワーク プログラミングの実現において、PHP 言語の Workerman フレームワークは、その優れたパフォーマンス、シンプルさ、使いやすさで常に知られています。ただし、PHP 言語と比較して、Golang は高い同時実行性と分散システム開発に適しているため、Workerman フレームワークの Golang バージョンの実装が多くの開発者の追求となっています。この記事では、Golang 言語を使用して、Workerman に似た同時実行性の高いネットワーク プログラミング フレームワークを実装する方法を紹介します。

1.前提知識

始める前に、いくつかの基本的な知識を習得する必要があります:

1.Golang 言語の基本:変数、関数、構造、インターフェースなど。コンセプト。

2. ネットワーク プログラミングの基礎: TCP/UDP、HTTP、およびその他のプロトコルの基本的な知識。

3.Goroutine: Golang 言語のコルーチンは、同時プログラミングの効率を大幅に向上させることができます。

4.Channel: Golang 言語によって提供される通信メカニズム。異なるコルーチン間のデータ送信と同期に使用できます。

5.Select: Golang 言語によって提供される多重化メカニズム。複数のチャネルのステータスを監視し、プログラムの効率を向上させることができます。

2. フレームワーク アーキテクチャ

Workerman フレームワークの実装に従って、それを 3 つの部分に分けることができます:

1. 接続を受信し、クライアントを生成します。

2. クライアントのリクエストを処理するために使用されるビジネス プロセス。

3. クライアントの接続ステータスを監視し、それを再利用します。

Golang 言語では、ゴルーチンを使用して上記の 3 つの部分をそれぞれ実装できます。

1. 接続を受信して​​クライアントを生成する

Golang 言語に付属する「net」パッケージを使用して TCP サーバーを作成し、同時にゴルーチンを開いて、クライアントの接続ステータスを監視します。

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

クライアント接続を受信した後、接続に対するすべてのリクエストと応答を処理するために Client オブジェクトをカプセル化する必要があります。

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. クライアントのリクエストの処理に使用されるビジネス プロセス

クライアントのリクエストと応答は、チャネルを通じて直接配信されます。新しい接続を受信したら、それを Client オブジェクトにカプセル化し、接続を処理するための goroutine を開く必要があります。このゴルーチンは、チャネルを通じてクライアントによって送信されたすべてのリクエストをリッスンし、それに応じて応答します。

ビジネス プロセスをハンドラー インターフェイスにカプセル化します。

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

クライアントのリクエストとレスポンスは、Client オブジェクトの RespCh 属性を通じて渡されます。したがって、Handler インターフェイスでは、クライアントからの応答を受信するために RespCh プロパティを定義する必要があります。

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

EchoHandler を作成して 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
}

接続された各クライアントの Client オブジェクトがクライアント配列に格納されると、各クライアントから送信されたデータを RespCh 属性を通じて受信し、クライアントから送信された情報を他のクライアントにブロードキャストできます。

3. クライアントの接続ステータスを監視し、再利用する

古いバージョンの Workerman フレームワークの場合、Workerman は一定期間内のアイドル状態の接続を再利用します。新しいバージョンの Workerman は、TCP キープアライブを通じてこの機能を実装します。

workerman の Golang バージョンを実装する場合、TCP キープアライブを通じてアイドル接続の問題を解決することもできます。各クライアントのゴルーチン内でソケットの状態を監視することができ、10秒以上アイドル時間が経過してもクライアントがデータを送信しない場合は不正な接続とみなされ、ソケットがクローズされます。

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. ワーカー プロセスを実装する

上記の 3 つの手順を完了したら、すべてのクライアント接続を管理するワーカー プロセスを作成する必要があります。クライアントから送信されたすべてのデータ要求を処理するには、ワーカー プロセスに 1 つ以上のハンドラーをロードする必要があります。

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

ワーカー プロセスでは、さまざまなハンドラーのインスタンスを保存するためのハンドラー属性を定義し、Start() 関数でクライアント接続を監視し、クライアント要求を処理するための新しいゴルーチンを開始する必要があります。

4. テスト

次のコードを使用してワーカー プロセスを作成し、そのプロセスに EchoHandler を登録してすべてのクライアント リクエストを処理できます。

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

telnet ツールを使用すると、複数のクライアントがサーバーにメッセージを送信する様子をシミュレートし、その受信を確認できます。

次のコマンドを使用してサーバーに接続します:

telnet 127.0.0.1 8080

Telnet に次のテキストを入力できます:

Hello workerman!

複数の Telnet ウィンドウを同時に開くことができます複数のクライアント要求を並行してシミュレートします。

サーバー上では、次の出力が表示されます:

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

これは、クライアント接続を閉じると、そのリスニング操作がキャンセルされ、読み取りエラーが発生するためです。

Telnet 入力が完了すると、各 Telnet ウィンドウがサーバーから返されたテキストを受信することがわかります。

5. 概要

この記事では、Golang 言語を使用して、PHP 言語の workman と同様の、同時実行性の高いネットワーク プログラミング フレームワークを実装する方法を紹介しました。実装プロセスでは、Golang 言語のコルーチン、通信メカニズム、多重化メカニズムを使用し、クライアント オブジェクトとハンドラー インターフェイスをカプセル化することで、Workerman と同様の同時実行性の高いネットワーク プログラミング フレームワークを実装することに成功しました。

実際、日常のプログラミングでは、Golang 言語によって提供される net/http パッケージを直接使用して、同時実行性の高いネットワーク プログラミングを実装することをお勧めします。これは、workerman フレームワークよりも簡潔でパフォーマンスが優れています。 http サーバーを開き、ゴルーチンを使用して各リクエストを同時に処理するだけで、同時実行性の高いネットワーク プログラミングを簡単に実装できます。

以上がGo 言語で同時実行性の高いネットワーク プログラミング フレームワークを実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。