en Go language chat server

Go language chat server

In this section, you will build on what you have learned so far to develop a chat sample program that allows you to broadcast text messages between multiple users.

server program

The server program contains four goroutines: a main goroutine and a broadcast (broadcaster) goroutine, and each connection contains a connection handling (handleConn) goroutine and a client writer (clientwriter) goroutine.

This is a specification of how to use select, as the broadcaster must respond to three different messages.

The main goroutine’s job is to listen on a port, accept network connections from connecting clients, and create a new handleConn goroutine for each connection.

The complete sample code is:

 package main

import (
  "bufio"
  "fmt"
  "log"
  "net"
)

func main() {
  listener、err : = net.Listen( "tcp"、 "localhost:8000")
  if err!= nil {
    log.Fatal(err)
  }

  go broadcaster()
  for {
    conn、err : = listener.Accept()
    if err!= nil {
      log.Print(err)
      continue
    }
    go handleConn(conn)
  }
}

type client chan<- string // 外部に送信するメッセージのチャンネル

var (
  entering = make (chan client)
  leaving  = make (chan client)
  messages = make (chan string) // 全接続クライアント
)

func broadcaster () {
  clients := make (map [client] bool)
  for {
    select {
    case msg:= <-messages:
      // 受信したメッセージをすべてのクライアントにブロードキャストする
      // メッセージ送信チャンネル
      for cli:= range clients {
        cli <- msg
      }

    case cli:= <-entering:
      clients [cli] = true

    case cli:= <-leaving:
      delete (clients、 cli)
      close(cli)
    }
  }
}
func handleConn (conn net.Conn) {
  ch := make (chan string) // クライアントメッセージを外部に送信するチャネル
  go clientWriter (conn、 ch)

  who:= conn.RemoteAddr().String()
  ch <- "ようこそ、" + who
  messages <- who + "がオンラインになりました"
  entering <- ch

  input := bufio.NewScanner(conn)
  for input.Scan() {
    messages <- who + ":" + input.Text()
  }
  // 注意:input.Err()中の可能性のあるエラーを無視する

  leaving <- ch
  messages <- who + "がオフラインになりました"
  conn.Close()
}

func clientWriter (conn net.Conn、 ch<-chan string) {
  for msg : = range ch {
    fmt.Fprintln (conn、 msg) // 注意:ネットワークレベルのエラーを無視してください
  }
} 

The code written in the main function in the code is very simple, all the server has to do is get the listener object, continuously get conn objects from the link, and finally process these objects. Throw it to the link. This is a function for processing.

When you use the handleConn method to handle conn objects, you don’t have to wait because you start goroutines for different connections and handle each conn at the same time.

We need to send a message to all online users, and the conn objects for different users are in different goroutines, but here we have channels in the Go language to handle sending messages between different goroutines, so channel. Broadcast messages are delivered by goroutines.

Let’s introduce the broadcaster. The broadcaster uses the local variable client to record the collection of currently connected clients. The only information recorded for each client is the ID of the outgoing message channel. Here are the details:

 type client chan<- string // 送信用の外部チャネル

var (
    entering = make(chan client)
    leaving  = make(chan client)
    messages = make(chan string) // すべての接続されたクライアント用
)

func broadcaster() {
    clients := make(map[client]bool)
    for {
        select {
        case msg := <-messages:
            // 受信したすべてのメッセージを全てのクライアントにブロードキャストする
            // 送信用のチャネル
            for cli := range clients {
                cli <- msg
            }

        case cli := <-entering:
            clients[cli] = true

        case cli := <-leaving:
            delete(clients, cli)
            close(cli)
        }
    }
} 

In the main function, we use a goroutine to open a broadcast function that broadcasts messages sent by all users.

A dictionary is used here to store user clients, and the keys of the dictionary are one-way concurrent queues declared by each connection.

Start multiplex with select.

  • Each time a broadcast message is sent from Message, the client loops and sends a message to each internal channel.
  • A new key and value are generated every time a message is sent from the input. This is the same as adding a new client to Clients.
  • Whenever a message is sent from exit, we remove the key-value pair and close the corresponding channel.

Let’s take a look at each customer’s unique goroutine.

The handleConn function creates a new channel for sending messages externally, notifies the broadcaster of the arrival of a new customer through the input channel, reads each line of text sent by the customer, and sends each line to the broadcaster’s global receive. Sent through a message channel, each message is prefixed with the sender ID. Once the message is read from the client, handleConn notifies the client to leave through the leave channel and closes the connection.

 func handleConn(conn net.Conn) {
    ch := make(chan string) // 外部に送信するクライアントのメッセージ用チャネル
    go clientWriter(conn, ch)

    who := conn.RemoteAddr().String()
    ch <- "ようこそ " + who +" さん"
    messages <- who + "が接続しました。"
    entering <- ch

    input := bufio.NewScanner(conn)
    for input.Scan() {
        messages <- who + ": " + input.Text()
    }
    // 注意:input.Err() 中の可能性のあるエラーを無視する

    leaving <- ch
    messages <- who + "が切断しました。"
    conn.Close()
}

func clientWriter(conn net.Conn, ch <-chan string) {
    for msg := range ch {
        fmt.Fprintln(conn, msg) // 注意:ネットワーク層面のエラーは無視する。
    }
} 

The handleConn function creates a new channel for each conn being handled, starts a new goroutine, and writes messages sent to this channel to the conn.

The process of executing the handleConn function can be easily summarized as the following steps.

  • Get the connected IP address and port number.
  • Writes welcome information to the channel and returns it to the client.
  • Generates a broadcast message and writes it to the message.
  • Add this channel to your client collection. In other words, type “<- ch;”.
  • Listens for data written to conn by clients and sends this message to the broadcast channel whenever it is scanned.
  • If the client is closed, leave the queue, write a Leave to the broadcast function, remove the client, and close the client.
  • Broadcast to notify other clients that the client is closed.
  • Finally close the client connection with Conn.Close().

client program

We briefly introduced the server earlier, but we’ll introduce the client below. Here it is named “netcat.go”. Here is the complete code:

 // netcatはシンプルなTCPサーバー読み書きクライアントです。
package main

import (
  "io"
  "log"
  "net"
  "os"
)

func main() {
  conn, err := net.Dial("tcp", "localhost:8000")
  if err != nil {
    log.Fatal(err)
  }
  done := make(chan struct{})
  go func() {
    io.Copy(os.Stdout, conn) // 注意:エラーは無視
    log.Println("完了")
    done <- struct{}{} // メインGoroutineに信号を送る
  }()
  mustCopy(conn, os.Stdin)
  conn.Close()
  <-done // バックグラウンドgoroutineの完了を待つ
}

func mustCopy(dst io.Writer, src io.Reader) {
  if _, err := io.Copy(dst, src); err != nil {
    log.Fatal(err)
  }
} 

If n client sessions are connected, the program runs 2n+2 goroutines simultaneously that communicate with each other and does not require implicit locking operations. Client maps are limited to being accessed by one goroutine on the broadcaster, so they cannot be accessed simultaneously. The only variables shared by multiple goroutines are the channel and the net.Conn instance, both of which are safe at the same time.

Compile the server and client using go build command and run the resulting executable.

The diagram below shows a server and three clients running on the same computer.

Easy-to-understand explanation of “Go language chat server”! Best 2 videos you must watch

GO言語でAPI開発「 gRPC 」入門
https://www.youtube.com/watch?v=vliAVKYELy8&pp=ygUnIEdv6KiA6Kqe44OB44Oj44OD44OI44K144O844OQ44O8JmhsPUpB
Go言語で何ができるの?どこで使われてる?現役エンジニアが解説
https://www.youtube.com/watch?v=ZtDMTl_s30s&pp=ygUnIEdv6KiA6Kqe44OB44Oj44OD44OI44K144O844OQ44O8JmhsPUpB