Mulai Menggunakan gRPC-Go - Streaming

1. Pengantar

Dalam codelab ini, Anda akan menggunakan gRPC-Go untuk membuat klien dan server yang membentuk dasar aplikasi pemetaan rute yang ditulis dalam Go.

Di akhir tutorial, Anda akan memiliki klien yang terhubung ke server jarak jauh menggunakan gRPC untuk mendapatkan informasi tentang fitur di rute klien, membuat ringkasan rute klien, dan bertukar informasi rute seperti info terbaru lalu lintas dengan server dan klien lain.

Layanan ini ditentukan dalam file Protocol Buffers, yang akan digunakan untuk membuat kode boilerplate bagi klien dan server sehingga keduanya dapat berkomunikasi satu sama lain, sehingga menghemat waktu dan upaya Anda dalam menerapkan fungsi tersebut.

Kode yang dihasilkan ini tidak hanya menangani kompleksitas komunikasi antara server dan klien, tetapi juga serialisasi dan deserialisasi data.

Yang akan Anda pelajari

  • Cara menggunakan Protocol Buffers untuk menentukan API layanan.
  • Cara membangun klien dan server berbasis gRPC dari definisi Protocol Buffers menggunakan pembuatan kode otomatis.
  • Pemahaman tentang komunikasi streaming klien-server dengan gRPC.

Codelab ini ditujukan untuk developer Go yang baru menggunakan gRPC atau ingin mempelajari kembali gRPC, atau siapa pun yang tertarik untuk membangun sistem terdistribusi. Tidak diperlukan pengalaman gRPC sebelumnya.

2. Sebelum memulai

Prasyarat

Pastikan Anda telah menginstal berikut ini:

  • Rangkaian alat Go versi 1.24.5 atau yang lebih baru. Untuk mengetahui petunjuk penginstalan, lihat Mulai Go.
  • Compiler buffer protokol, protoc, versi 3.27.1 atau yang lebih baru. Untuk mengetahui petunjuk penginstalan, lihat panduan penginstalan compiler.
  • Plugin compiler buffering protokol untuk Go dan gRPC. Untuk menginstal plugin ini, jalankan perintah berikut:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

Perbarui variabel PATH agar compiler buffer protokol dapat menemukan plugin:

export PATH="$PATH:$(go env GOPATH)/bin"

Mendapatkan kode

Agar Anda tidak perlu memulai dari awal, codelab ini menyediakan kerangka kode sumber aplikasi yang dapat Anda selesaikan. Langkah-langkah berikut akan menunjukkan cara menyelesaikan aplikasi, termasuk menggunakan plugin compiler buffer protokol untuk membuat kode gRPC boilerplate.

Pertama, buat direktori kerja codelab dan cd ke dalamnya:

mkdir streaming-grpc-go-getting-started && cd streaming-grpc-go-getting-started

Download dan ekstrak codelab:

curl -sL https://github.com/grpc-ecosystem/grpc-codelabs/archive/refs/heads/v1.tar.gz \
  | tar xvz --strip-components=4 \
  grpc-codelabs-1/codelabs/grpc-go-streaming/start_here

Atau, Anda dapat mendownload file .zip yang hanya berisi direktori codelab dan mengekstraknya secara manual.

Kode sumber yang sudah selesai tersedia di GitHub jika Anda ingin melewati pengetikan implementasi.

3. Menentukan pesan dan layanan

Langkah pertama Anda adalah menentukan layanan gRPC aplikasi, metode RPC, dan jenis pesan permintaan dan responsnya menggunakan Protocol Buffers. Layanan Anda akan menyediakan:

  • Metode RPC yang disebut ListFeatures, RecordRoute, dan RouteChat yang diimplementasikan server dan dipanggil klien.
  • Jenis pesan Point, Feature, Rectangle, RouteNote, dan RouteSummary, yang merupakan struktur data yang dipertukarkan antara klien dan server saat memanggil metode di atas.

Metode RPC ini dan jenis pesannya akan ditentukan dalam file routeguide/route_guide.proto kode sumber yang diberikan.

Protocol Buffers biasanya dikenal sebagai protobuf. Untuk mengetahui informasi selengkapnya tentang terminologi gRPC, lihat Konsep inti, arsitektur, dan siklus proses gRPC.

Menentukan Jenis pesan

Dalam file routeguide/route_guide.proto kode sumber, tentukan terlebih dahulu jenis pesan Point. Point mewakili pasangan koordinat lintang-bujur di peta. Untuk codelab ini, gunakan bilangan bulat untuk koordinat:

message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

Angka 1 dan 2 adalah nomor ID unik untuk setiap kolom dalam struktur message.

Selanjutnya, tentukan jenis pesan Feature. Feature menggunakan kolom string untuk nama atau alamat pos sesuatu di lokasi yang ditentukan oleh Point:

message Feature {
  // The name or address of the feature.
  string name = 1;

  // The point where the feature is located.
  Point location = 2;
}

Selanjutnya, pesan Rectangle yang merepresentasikan persegi panjang lintang-bujur, yang direpresentasikan sebagai dua titik yang berlawanan secara diagonal "lo" dan "hi".

message Rectangle {
  // One corner of the rectangle.
  Point lo = 1;

  // The other corner of the rectangle.
  Point hi = 2;
}

Juga pesan RouteNote yang merepresentasikan pesan yang dikirim pada titik tertentu.

message RouteNote {
  // The location from which the message is sent.
  Point location = 1;

  // The message to be sent.
  string message = 2;
}

Kami juga akan memerlukan pesan RouteSummary. Pesan ini diterima sebagai respons terhadap RPC RecordRoute yang dijelaskan di bagian berikutnya. Objek ini berisi jumlah titik individual yang diterima, jumlah fitur yang terdeteksi, dan total jarak yang ditempuh sebagai jumlah kumulatif jarak antara setiap titik.

message RouteSummary {
  // The number of points received.
  int32 point_count = 1;

  // The number of known features passed while traversing the route.
  int32 feature_count = 2;

  // The distance covered in metres.
  int32 distance = 3;

  // The duration of the traversal in seconds.
  int32 elapsed_time = 4;
}

Menentukan metode layanan

Untuk menentukan layanan, Anda menentukan layanan bernama dalam file .proto. File route_guide.proto memiliki struktur service bernama RouteGuide yang menentukan satu atau beberapa metode yang disediakan oleh layanan aplikasi.

Tentukan metode RPC di dalam definisi layanan Anda, dengan menentukan jenis permintaan dan responsnya. Di bagian codelab ini, mari kita tentukan:

ListFeatures

Mendapatkan Feature yang tersedia dalam Rectangle tertentu. Hasil di-streaming, bukan ditampilkan sekaligus (misalnya, dalam pesan respons dengan kolom berulang), karena persegi panjang dapat mencakup area yang luas dan berisi sejumlah besar fitur.

Jenis yang sesuai untuk RPC ini adalah RPC streaming sisi server: klien mengirim permintaan ke server dan mendapatkan aliran untuk membaca kembali urutan pesan. Klien membaca dari stream yang ditampilkan hingga tidak ada lagi pesan. Seperti yang dapat Anda lihat dalam contoh kami, Anda menentukan metode streaming sisi server dengan menempatkan kata kunci stream sebelum jenis respons.

rpc ListFeatures(Rectangle) returns (stream Feature) {}

RecordRoute

Menerima aliran Point di rute yang dilalui, menampilkan RouteSummary saat traversal selesai.

RPC streaming sisi klien tampaknya sesuai dalam kasus ini: klien menulis urutan pesan dan mengirimkannya ke server, lagi-lagi menggunakan stream yang disediakan. Setelah klien selesai menulis pesan, klien akan menunggu server membaca semua pesan tersebut dan menampilkan responsnya. Anda menentukan metode streaming sisi klien dengan menempatkan kata kunci stream sebelum jenis permintaan.

rpc RecordRoute(stream Point) returns (RouteSummary) {}

RouteChat

Menerima aliran RouteNote yang dikirim saat rute sedang dilalui, sambil menerima RouteNote lainnya (misalnya, dari pengguna lain).

Inilah jenis kasus penggunaan yang tepat untuk streaming dua arah. RPC streaming dua arah membuat kedua sisi mengirim urutan pesan menggunakan stream baca-tulis. Kedua aliran beroperasi secara independen, sehingga klien dan server dapat membaca dan menulis dalam urutan apa pun yang mereka inginkan.

Misalnya, server dapat menunggu untuk menerima semua pesan klien sebelum menulis responsnya, atau dapat membaca pesan lalu menulis pesan secara bergantian, atau kombinasi pembacaan dan penulisan lainnya.

Urutan pesan di setiap aliran akan tetap terjaga. Anda menentukan jenis metode ini dengan menempatkan kata kunci stream sebelum permintaan dan respons.

rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}

4. Membuat kode klien dan server

Selanjutnya, buat kode gRPC boilerplate untuk klien dan server dari file .proto menggunakan compiler protocol buffer. Di direktori routeguide, jalankan:

protoc --go_out=. --go_opt=paths=source_relative \
       --go-grpc_out=. --go-grpc_opt=paths=source_relative \
       route_guide.proto

Perintah ini akan menghasilkan file berikut:

  • route_guide.pb.go, yang berisi fungsi untuk membuat jenis pesan aplikasi dan mengakses data serta definisi jenis yang merepresentasikan pesan.
  • route_guide_grpc.pb.go, yang berisi fungsi yang digunakan klien untuk memanggil metode gRPC jarak jauh layanan, dan fungsi yang digunakan oleh server untuk menyediakan layanan jarak jauh tersebut.

Selanjutnya, kita akan menerapkan metode di sisi server, sehingga saat klien mengirim permintaan, server dapat membalas dengan jawaban.

5. Menerapkan layanan

Pertama, mari kita lihat cara membuat server RouteGuide. Ada dua bagian untuk membuat layanan RouteGuide kami melakukan tugasnya:

  • Mengimplementasikan antarmuka layanan yang dihasilkan dari definisi layanan kita: melakukan "pekerjaan" layanan kita yang sebenarnya.
  • Menjalankan server gRPC untuk memproses permintaan dari klien dan mengirimkannya ke implementasi layanan yang tepat.

Mari kita terapkan RouteGuide di server/server.go.

Mengimplementasikan RouteGuide

Kita perlu menerapkan antarmuka RouteGuideService yang dihasilkan. Seperti inilah tampilan implementasinya.

type routeGuideServer struct {
        ...
}
...
func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error {
        ...
}
...

func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error {
        ...
}
...

func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error {
        ...
}

Mari kita pelajari setiap implementasi RPC secara mendetail.

RPC streaming sisi server

Mulai dengan salah satu RPC streaming kami. ListFeatures adalah RPC streaming sisi server, jadi kita perlu mengirim kembali beberapa Feature ke klien.

func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error {
  for _, feature := range s.savedFeatures {
    if inRange(feature.Location, rect) {
      if err := stream.Send(feature); err != nil {
        return err
      }
    }
  }
  return nil
}

Seperti yang dapat Anda lihat, alih-alih mendapatkan objek permintaan dan respons sederhana dalam parameter metode, kali ini kita mendapatkan objek permintaan (Rectangle tempat klien ingin menemukan Features) dan objek RouteGuide_ListFeaturesServer khusus untuk menulis respons. Dalam metode ini, kita mengisi sebanyak mungkin objek Feature yang perlu ditampilkan, dengan menuliskannya ke RouteGuide_ListFeaturesServer menggunakan metode Send(). Terakhir, seperti pada RPC sederhana, kita menampilkan error nil untuk memberi tahu gRPC bahwa kita telah selesai menulis respons. Jika terjadi error dalam panggilan ini, kita akan menampilkan error non-null; lapisan gRPC akan menerjemahkannya ke dalam status RPC yang sesuai untuk dikirim melalui jaringan.

RPC streaming sisi klien

Sekarang mari kita lihat sesuatu yang sedikit lebih rumit: metode streaming sisi klien RecordRoute, tempat kita mendapatkan aliran Points dari klien dan menampilkan satu RouteSummary dengan informasi tentang perjalanannya. Seperti yang dapat Anda lihat, kali ini metode tersebut tidak memiliki parameter permintaan sama sekali. Sebagai gantinya, aplikasi ini mendapatkan aliran RouteGuide_RecordRouteServer, yang dapat digunakan server untuk membaca dan menulis pesan - aplikasi ini dapat menerima pesan klien menggunakan metode Recv() dan menampilkan satu respons menggunakan metode SendAndClose().

func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error {
  var pointCount, featureCount, distance int32
  var lastPoint *pb.Point
  startTime := time.Now()
  for {
    point, err := stream.Recv()
    if err == io.EOF {
      endTime := time.Now()
      return stream.SendAndClose(&pb.RouteSummary{
        PointCount:   pointCount,
        FeatureCount: featureCount,
        Distance:     distance,
        ElapsedTime:  int32(endTime.Sub(startTime).Seconds()),
      })
    }
    if err != nil {
      return err
    }
    pointCount++
    for _, feature := range s.savedFeatures {
      if proto.Equal(feature.Location, point) {
        featureCount++
      }
    }
    if lastPoint != nil {
      distance += calcDistance(lastPoint, point)
    }
    lastPoint = point
  }
}

Dalam isi metode, kita menggunakan metode Recv() RouteGuide_RecordRouteServer untuk berulang kali membaca permintaan klien ke objek permintaan (dalam hal ini Point) hingga tidak ada lagi pesan: server perlu memeriksa error yang ditampilkan dari Recv() setelah setiap panggilan. Jika nilainya nil, berarti streaming masih bagus dan dapat terus dibaca; jika nilainya io.EOF, berarti streaming pesan telah berakhir dan server dapat menampilkan RouteSummary-nya. Jika memiliki nilai lain, kita akan menampilkan error "apa adanya" sehingga akan diterjemahkan ke status RPC oleh lapisan gRPC.

RPC streaming dua arah

Terakhir, mari kita lihat RPC streaming dua arah RouteChat().

func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error {
  for {
    in, err := stream.Recv()
    if err == io.EOF {
      return nil
    }
    if err != nil {
      return err
    }
    key := serialize(in.Location)

    s.mu.Lock()
    s.routeNotes[key] = append(s.routeNotes[key], in)
    // Note: this copy prevents blocking other clients while serving this one.
    // We don't need to do a deep copy, because elements in the slice are
    // insert-only and never modified.
    rn := make([]*pb.RouteNote, len(s.routeNotes[key]))
    copy(rn, s.routeNotes[key])
    s.mu.Unlock()

    for _, note := range rn {
      if err := stream.Send(note); err != nil {
        return err
      }
    }
  }
}

Kali ini kita mendapatkan aliran RouteGuide_RouteChatServer yang, seperti dalam contoh streaming sisi klien, dapat digunakan untuk membaca dan menulis pesan. Namun, kali ini kita menampilkan nilai melalui stream metode saat klien masih menulis pesan ke stream pesannya. Sintaksis untuk membaca dan menulis di sini sangat mirip dengan metode streaming klien kami, kecuali server menggunakan metode send() stream, bukan SendAndClose() karena server menulis beberapa respons. Meskipun setiap sisi akan selalu mendapatkan pesan sisi lain dalam urutan penulisannya, klien dan server dapat membaca dan menulis dalam urutan apa pun — aliran beroperasi sepenuhnya secara independen.

Mulai server

Setelah menerapkan semua metode, kita juga perlu memulai server gRPC agar klien dapat menggunakan layanan kita. Cuplikan berikut menunjukkan cara kami melakukannya untuk layanan RouteGuide kami:

lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
if err != nil {
  log.Fatalf("failed to listen: %v", err)
}

grpcServer := grpc.NewServer()

s := &routeGuideServer{routeNotes: make(map[string][]*pb.RouteNote)}
s.loadFeatures()
pb.RegisterRouteGuideServer(grpcServer, s)
grpcServer.Serve(lis)

Berikut yang terjadi di main(), langkah demi langkah:

  1. Tentukan port TCP yang akan digunakan untuk memproses permintaan klien jarak jauh, menggunakan lis, err := net.Listen(...). Secara default, aplikasi menggunakan port TCP 50051 seperti yang ditentukan oleh variabel port atau dengan meneruskan tombol --port di command line saat menjalankan server. Jika port TCP tidak dapat dibuka, aplikasi akan berakhir dengan error fatal.
  2. Buat instance server gRPC menggunakan grpc.NewServer(...), dan beri nama instance ini grpcServer.
  3. Buat pointer ke routeGuideServer, struktur yang merepresentasikan layanan API aplikasi, dengan memberi nama pointer s.
  4. Gunakan s.loadFeatures() untuk mengisi array s.savedFeatures.
  5. Daftarkan implementasi layanan kita dengan server gRPC.
  6. Panggil Serve() di server dengan detail port kami untuk melakukan penantian pemblokiran permintaan klien; ini berlanjut hingga proses dihentikan atau Stop() dipanggil.

Fungsi loadFeatures() mendapatkan pemetaan koordinat ke lokasi dari server/testdata.go.

6. Buat klien

Sekarang edit client/client.go, tempat Anda akan menerapkan kode klien.

Untuk memanggil metode layanan jarak jauh, kita harus membuat channel gRPC terlebih dahulu untuk berkomunikasi dengan server. Kita membuatnya dengan meneruskan string URI target server (yang dalam hal ini hanyalah alamat dan nomor port) ke grpc.NewClient() dalam fungsi main() klien sebagai berikut:

// Set up a connection to the gRPC server.
conn, err := grpc.NewClient("dns:///"+*serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
  log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()

Alamat server, yang ditentukan oleh variabel serverAddr, secara default adalah localhost:50051, dan dapat diganti oleh switch --addr di command line saat menjalankan klien.

Jika klien perlu terhubung ke layanan yang memerlukan kredensial autentikasi, seperti kredensial TLS atau JWT, klien dapat meneruskan objek DialOptions sebagai parameter ke grpc.NewClient yang berisi kredensial yang diperlukan. Layanan RouteGuide tidak memerlukan kredensial apa pun.

Setelah channel gRPC disiapkan, kita memerlukan stub klien untuk melakukan RPC melalui panggilan fungsi Go. Kita mendapatkan stub menggunakan metode NewRouteGuideClient yang disediakan oleh file route_guide_grpc.pb.go yang dihasilkan dari file .proto aplikasi.

import (pb "github.com/grpc-ecosystem/codelabs/getting_started_streaming/routeguide")

client := pb.NewRouteGuideClient(conn)

Panggil metode layanan

Sekarang, mari kita lihat cara memanggil metode layanan. Di gRPC-Go, RPC beroperasi dalam mode pemblokiran/sinkron, yang berarti bahwa panggilan RPC menunggu server merespons, dan akan menampilkan respons atau error.

RPC streaming sisi server

Di sini, kita memanggil metode streaming sisi server ListFeatures, yang menampilkan aliran objek Feature geografis.

rect := &pb.Rectangle{ ... }  // initialize a pb.Rectangle
log.Printf("Looking for features within %v", rect)
stream, err := client.ListFeatures(context.Background(), rect)
if err != nil {
  log.Fatalf("client.ListFeatures failed: %v", err)
}
for {
  // For server-to-client streaming RPCs, you call stream.Recv() until it
  // returns io.EOF.
  feature, err := stream.Recv()
  if err == io.EOF {
    break
  }
  if err != nil {
    log.Fatalf("client.ListFeatures failed: %v", err)
  }
  log.Printf("Feature: name: %q, point:(%v, %v)", feature.GetName(),
    feature.GetLocation().GetLatitude(), feature.GetLocation().GetLongitude())
}

Seperti pada RPC sederhana, kita meneruskan konteks dan permintaan ke metode. Namun, alih-alih mendapatkan objek respons kembali, kita mendapatkan instance RouteGuide_ListFeaturesClient kembali. Klien dapat menggunakan stream RouteGuide_ListFeaturesClient untuk membaca respons server. Kita menggunakan metode RouteGuide_ListFeaturesClient's Recv() untuk berulang kali membaca respons server ke objek buffer protokol respons (dalam hal ini Feature) hingga tidak ada lagi pesan: klien perlu memeriksa error err yang ditampilkan dari Recv() setelah setiap panggilan. Jika nil, aliran masih bagus dan dapat terus membaca; jika io.EOF, aliran pesan telah berakhir; jika tidak, pasti ada error RPC, yang diteruskan melalui err.

RPC streaming sisi klien

Metode streaming sisi klien RecordRoute mirip dengan metode sisi server, kecuali kita hanya meneruskan konteks metode dan mendapatkan kembali aliran RouteGuide_RecordRouteClient, yang dapat kita gunakan untuk menulis dan membaca pesan.

// Create a random number of random points
r := rand.New(rand.NewSource(time.Now().UnixNano()))
pointCount := int(r.Int31n(100)) + 2 // Traverse at least two points
var points []*pb.Point
for i := 0; i < pointCount; i++ {
  points = append(points, randomPoint(r))
}
log.Printf("Traversing %d points.", len(points))
c2sStream, err := client.RecordRoute(context.TODO())
if err != nil {
  log.Fatalf("client.RecordRoute failed: %v", err)
}
// Stream each point to the server.
for _, point := range points {
  if err := c2sStream.Send(point); err != nil {
    log.Fatalf("client.RecordRoute: stream.Send(%v) failed: %v", point, err)
  }
}
// Close the stream and receive the RouteSummary from the server.
reply, err := c2sStream.CloseAndRecv()
if err != nil {
  log.Fatalf("client.RecordRoute failed: %v", err)
}
log.Printf("Route summary: %v", reply)

RouteGuide_RecordRouteClient memiliki metode Send() yang dapat kita gunakan untuk mengirim permintaan ke server. Setelah selesai menulis permintaan klien ke stream menggunakan Send(), kita perlu memanggil CloseAndRecv() di stream untuk memberi tahu gRPC bahwa kita telah selesai menulis dan mengharapkan untuk menerima respons. Kita mendapatkan status RPC dari err yang ditampilkan dari CloseAndRecv(). Jika statusnya nil, nilai yang ditampilkan pertama dari CloseAndRecv() akan menjadi respons server yang valid.

RPC streaming dua arah

Terakhir, mari kita lihat RPC streaming dua arah RouteChat(). Seperti dalam kasus RecordRoute, kita hanya meneruskan objek konteks ke metode dan mendapatkan kembali aliran yang dapat kita gunakan untuk menulis dan membaca pesan. Namun, kali ini kita menampilkan nilai melalui aliran metode saat server masih menulis pesan ke aliran pesannya.

biDiStream, err := client.RouteChat(context.Background())
if err != nil {
  log.Fatalf("client.RouteChat failed: %v", err)
}
// this channel is used to wait for the receive goroutine to finish.
recvDoneCh := make(chan struct{})
// receive goroutine.
go func() {
  for {
    in, err := biDiStream.Recv()
    if err == io.EOF {
      // read done.
      close(recvDoneCh)
      return
    }
    if err != nil {
      log.Fatalf("client.RouteChat failed: %v", err)
    }
    log.Printf("Got message %s at point(%d, %d)", in.Message, in.Location.Latitude, in.Location.Longitude)
  }
}()
// send messages simultaneously.
for _, note := range notes {
  if err := biDiStream.Send(note); err != nil {
    log.Fatalf("client.RouteChat: stream.Send(%v) failed: %v", note, err)
  }
}
biDiStream.CloseSend()
// wait for the receive goroutine to finish.
<-recvDoneCh

Sintaks untuk membaca dan menulis di sini sangat mirip dengan metode streaming sisi klien, kecuali kita menggunakan metode CloseSend() stream setelah menyelesaikan panggilan. Meskipun setiap sisi akan selalu mendapatkan pesan sisi lain dalam urutan penulisannya, klien dan server dapat membaca dan menulis dalam urutan apa pun — aliran beroperasi sepenuhnya secara independen.

7. Cobalah

Pastikan server dan klien berfungsi dengan benar satu sama lain dengan menjalankan perintah berikut di direktori kerja aplikasi:

  1. Jalankan server di satu terminal:
cd server
go run .
  1. Jalankan klien dari terminal lain:
cd client
go run .

Anda akan melihat output seperti ini, dengan stempel waktu yang dihilangkan agar lebih jelas:

Looking for features within lo:<latitude:400000000 longitude:-750000000 > hi:<latitude:420000000 longitude:-730000000 >
name:"Patriots Path, Mendham, NJ 07945, USA" location:<latitude:407838351 longitude:-746143763 >
...
name:"3 Hasta Way, Newton, NJ 07860, USA" location:<latitude:410248224 longitude:-747127767 >
Traversing 56 points.
Route summary: point_count:56 distance:497013163
Got message First message at point(0, 1)
Got message Second message at point(0, 2)
Got message Third message at point(0, 3)
Got message First message at point(0, 1)
Got message Fourth message at point(0, 1)
Got message Second message at point(0, 2)
Got message Fifth message at point(0, 2)
Got message Third message at point(0, 3)
Got message Sixth message at point(0, 3)

8. Langkah berikutnya