Android

【Android】gRPC 入門教學 現代的跨語言遠程過程呼叫框架

【Android】gRPC 入門教學 現代的跨語言遠程過程呼叫框架

gRPC(Google Remote Procedure Call)是一種由 Google 開發的高效能、跨語言的遠程過程呼叫(RPC)框架。它允許不同的應用程式和服務之間進行通信,並能夠在多種編程語言之間實現互操作性。


文章目錄

  1. Protocaol Buffers
  2. 四種模式
  3. NodeJS 實現 Server 端
  4. NodeJS 實現 Clinet 端
  5. Android 實現 Client 端
  6. 相關連結

1.Protocaol Buffers

Protocol Buffers,簡稱ProtoBuf,是一種用於序列化結構化數據的介面描述語言(IDL)和底層數據序列化格式。它是由Google開發的開源技術,用於解決不同應用程式或不同系統之間的數據通信和持久化存儲的問題。
hello.proto
syntax = "proto3";

package hello;

service Greeter {
  // 單向呼叫 (Unary RPC)
  rpc sayHelloUnary (HelloRequest) returns (HelloReply) {}

  // 伺服器流 (Server Streaming RPC)
  rpc sayHelloServerStream (HelloRequest) returns (stream HelloReply) {}

  // 客戶端流 (Client Streaming RPC)
  rpc sayHelloClientStream (stream HelloRequest) returns (HelloReply) {}

  // 雙向流 (Bidirectional Streaming RPC)
  rpc sayHellBidirectionalStream (stream HelloRequest) returns (stream HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

2.四種模式

a.單向呼叫 (Unary RPC)

客戶端向伺服器發送一個請求,並等待伺服器的回覆。這是最簡單的 gRPC 通信模式,用於單一的請求-回覆操作。類似於傳統的同步函數調用。

b.伺服器流 (Server Streaming RPC)

客戶端向伺服器發送一個請求,並接收來自伺服器的流式回覆。這允許伺服器在一個請求中持續傳送多個消息,客戶端可以異步接收和處理這些消息。

c.客戶端流 (Client Streaming RPC)

客戶端將一個流式請求(多個消息)發送到伺服器,然後等待伺服器的回覆。這允許客戶端向伺服器連續傳送數據,並等待伺服器在處理完所有數據後回覆。

d.雙向流 (Bidirectional Streaming RPC)

客戶端和伺服器都可以同時發送和接收流式消息,實現雙向的非同步通信。這種模式適用於需要實時互動或長時間連接的應用,例如聊天應用或遊戲多人模式。

3.NodeJS 實現 Server 端

安裝 grpc 函示庫
npm i @grpc/grpc-js
npm i @grpc/proto-loader
proto.js
const PROTO_PATH = 'hello.proto';
const grpc = require('@grpc/grpc-js')
const protoLoader = require('@grpc/proto-loader')

const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
    keepCase: true, longs: String, enums: String, defaults: true, oneofs: true
})

const proto = grpc.loadPackageDefinition(packageDefinition).hello

module.exports = proto
單向呼叫 UnaryRPCServer.js
const proto = require('./proto')
const grpc = require('@grpc/grpc-js')

function sayHelloUnary(call, callback) {
    callback(null, {
        message: 'Hello ' + call.request.name
    });
}

async function main() {
    const server = new grpc.Server();
    server.addService(proto.Greeter.service, {sayHelloUnary: sayHelloUnary});

    server.bindAsync('0.0.0.0:30678', grpc.ServerCredentials.createInsecure(), () => {
        server.start();
        console.log('Server running at http://0.0.0.0:30678');
    });
}

main()
伺服器流 ServerSideRPCServer.js
const grpc = require('@grpc/grpc-js')
const proto = require('./proto')

function sayHelloServerStream(call) {
    setInterval(() => {
        call.write({ message: `Hello {call.request.name}{new Date()}`});
    }, 2000)
}

function main() {
    let server = new grpc.Server()
    server.addService(proto.Greeter.service, {sayHelloServerStream: sayHelloServerStream})
    server.bindAsync('0.0.0.0:30678', grpc.ServerCredentials.createInsecure(), () => {
        server.start()
        console.log('Server running at http://0.0.0.0:30678')
    });
}

main();
客戶端流 ClientSideRPCServer.js
const grpc = require('@grpc/grpc-js')
const proto = require('./proto')

function sayHelloClientStream(call, callback) {
    call.on('data', (response) => {
        console.log('server receive:', response);
    });

    call.on('end', () => {
        callback(null, { message: `Bye` })
    });
}

function main() {
    let server = new grpc.Server()
    server.addService(proto.Greeter.service, {sayHelloClientStream: sayHelloClientStream})
    server.bindAsync('0.0.0.0:30678', grpc.ServerCredentials.createInsecure(), () => {
        server.start()
        console.log('Server running at http://0.0.0.0:30678')
    });
}

main();
雙向流 BidirectionalRPCServer.js
const grpc = require('@grpc/grpc-js')
const proto = require('./proto')

function sayHellBidirectionalStream(call) {
    call.on('data', (response) => {
        console.log('server receive:', response);
    })

    call.on('end', (callback) => {
        callback(null, { message: `Bye` })
    });

    setInterval(() => {
        call.write({ message: `Hello Client ${new Date()}` });
    }, 2000)
}

function main() {
    let server = new grpc.Server()
    server.addService(proto.Greeter.service, {sayHellBidirectionalStream: sayHellBidirectionalStream})
    server.bindAsync('0.0.0.0:30678', grpc.ServerCredentials.createInsecure(), () => {
        server.start()
        console.log('Server running at http://0.0.0.0:30678')
    });
}

main();

4.NodeJS 實現 Clinet 端

單向呼叫 UnaryRPCClient.js
const proto = require('./proto')
const grpc = require('@grpc/grpc-js')

async function main() {
    const client = new proto.Greeter('localhost:30678', grpc.credentials.createInsecure());
    client.sayHelloUnary({
        "name": "Wade"
    }, (err, response) => {
        if (err) {
            console.log('Greeting:', err.message);
            return
        }
        console.log('Greeting:', response.message);
    });
}

main()
伺服器流 ServerSideRPCClient.js
const grpc = require('@grpc/grpc-js')
const proto = require('./proto')

function main() {
    const client = new proto.Greeter('localhost:30678', grpc.credentials.createInsecure())
    const call = client.sayHelloServerStream({
        name: "Wade"
    })

    call.on('data', (response) => {
        console.log('client receive:', response);
    });

    call.on('end', () => {
        console.log('Bye');
    });
}

main();
客戶端流 ClientSideRPCClient.js
const grpc = require('@grpc/grpc-js')
const proto = require('./proto')

function main() {
    const client = new proto.Greeter('localhost:30678', grpc.credentials.createInsecure())
    const call = client.sayHelloClientStream(() => {

    })

    setInterval(() => {
        call.write({ name: `Hello ${new Date()}`});
    }, 2000)
}

main();
雙向流 BidirectionalRPCClient.js
const grpc = require('@grpc/grpc-js')
const proto = require('./proto')

function main() {
    const client = new proto.Greeter('localhost:30678', grpc.credentials.createInsecure())
    const call = client.sayHellBidirectionalStream()

    call.on('data', (response) => {
        console.log('client receive:', response);
    });

    call.on('end', (callback) => {
        callback(null, { message: `Bye` })
    });

    setInterval(() => {
        call.write({ name: `Hello Server ${new Date()}` });
    }, 2000)
}

main();

5.Android 實現 Client 端

導入 grpc 函示庫
dependencies {
    implementation 'io.grpc:grpc-okhttp:1.42.1'
    implementation 'io.grpc:grpc-protobuf-lite:1.42.1'
    implementation 'io.grpc:grpc-stub:1.42.1'
}
設定 build.gradle(Project)
plugins {
    id 'com.google.protobuf' version '0.8.17' apply false
}
設定 build.gradle(Module)
plugins {
    id 'com.google.protobuf'
}

protobuf {
    protoc { artifact = 'com.google.protobuf:protoc:3.17.2' }

    plugins {
        javalite { artifact = "com.google.protobuf:protoc-gen-javalite:3.0.0" }
        grpc {
            artifact = 'io.grpc:protoc-gen-grpc-java:1.42.1' // CURRENT_GRPC_VERSION
        }
    }

    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java { option 'lite' }
            }
            task.plugins {
                grpc { // Options added to --grpc_out
                    option 'lite'
                }
            }
        }
    }

    generatedFilesBaseDir = "$projectDir/src/generated"
}
聲明網路權限
<uses-permission android:name="android.permission.INTERNET" />
創建 proto 資料夾 自動生成檔案
app\src\main\proto\hello.proto

點擊 Build > Rebuild Project 自動產生

單向呼叫 MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val channel = OkHttpChannelBuilder.forTarget("192.168.100.142:30678")
            .usePlaintext()
            .build()

        val client = GreeterGrpc.newStub(channel)

        val request = Hello.HelloRequest.newBuilder()
            .setName("Wade")
            .build()

        client.sayHelloUnary(request, object : StreamObserver<Hello.HelloReply> {
            override fun onNext(value: Hello.HelloReply?) {
                Log.e("GGGRPC", value?.message.toString())
            }

            override fun onError(t: Throwable?) {
                Log.e("GGGRPC", "onError $t")
            }

            override fun onCompleted() {
                Log.e("GGGRPC", "onCompleted")
            }
        })
    }
}
伺服器流 MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val channel = OkHttpChannelBuilder.forTarget("192.168.100.142:30678")
            .usePlaintext()
            .build()

        val client = GreeterGrpc.newStub(channel)

        val request = Hello.HelloRequest.newBuilder()
            .setName("Wade")
            .build()

        client.sayHelloServerStream(request, object : StreamObserver<Hello.HelloReply> {
            override fun onNext(value: Hello.HelloReply?) {
                Log.e("GGGRPC", value?.message.toString())
            }

            override fun onError(t: Throwable?) {
                Log.e("GGGRPC", "onError $t")
            }

            override fun onCompleted() {
                Log.e("GGGRPC", "onCompleted")
            }
        })
    }
}
客戶端流 MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val channel = OkHttpChannelBuilder.forTarget("192.168.100.142:30678")
            .usePlaintext()
            .build()

        val client = GreeterGrpc.newStub(channel)

        val call = client.sayHelloClientStream(object : StreamObserver<Hello.HelloReply> {
            override fun onNext(value: Hello.HelloReply?) {
                Log.e("GGGRPC", value?.message.toString())
            }

            override fun onError(t: Throwable?) {
                Log.e("GGGRPC", "onError t")
            }

            override fun onCompleted() {
                Log.e("GGGRPC", "onCompleted")
            }
        })

        setInterval {
            val request = Hello.HelloRequest.newBuilder()
                .setName("Hello{Date()}")
                .build()

            call.onNext(request)
        }
    }

    private fun setInterval(handler: () -> Unit) = CoroutineScope(Dispatchers.IO).launch {
        while (true) {
            delay(1000)
            handler()
        }
    }
}
雙向流 MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val channel = OkHttpChannelBuilder.forTarget("192.168.100.142:30678")
            .usePlaintext()
            .build()

        val client = GreeterGrpc.newStub(channel)

        val call = client.sayHellBidirectionalStream(object : StreamObserver<Hello.HelloReply> {
            override fun onNext(value: Hello.HelloReply?) {
                Log.e("GGGRPC", value?.message.toString())
            }

            override fun onError(t: Throwable?) {
                Log.e("GGGRPC", "onError t")
            }

            override fun onCompleted() {
                Log.e("GGGRPC", "onCompleted")
            }
        })

        setInterval {
            val request = Hello.HelloRequest.newBuilder()
                .setName("Hello{Date()}")
                .build()

            call.onNext(request)
        }
    }

    private fun setInterval(handler: () -> Unit) = CoroutineScope(Dispatchers.IO).launch {
        while (true) {
            delay(1000)
            handler()
        }
    }
}

6.相關連結

gRPC Error Code
Protobuf
Android gRPC
BloomRPC

發表迴響