24 Dec 2019
|
Kotlin
gRPC
hello.proto
syntax = "proto3";
package com.snowdeer;
option java_outer_classname = "Hello";
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
rpc LotsOfReplies (HelloRequest) returns (stream HelloResponse);
rpc LotsOfGreetings (stream HelloRequest) returns (HelloResponse);
rpc BidiHello (stream HelloRequest) returns (stream HelloResponse);
}
message HelloRequest {
string greeting = 1;
}
message HelloResponse {
string reply = 1;
}
HelloServer.kt
위에서 총 4개의 메소드를 정의했지만, 일단 첫 번째 메소드인 sayHello
에 대해서만 구현을 해봅니다.
package com.snowdeer
import io.grpc.ServerBuilder
import io.grpc.stub.StreamObserver
fun main(args: Array<String>) {
println("[snowdeer] main()")
val service = HelloService()
val server = ServerBuilder
.forPort(10004)
.addService(service)
.build()
println("[snowdeer] server starts()")
server.start()
server.awaitTermination()
}
class HelloService : HelloServiceGrpc.HelloServiceImplBase() {
override fun sayHello(request: Hello.HelloRequest?, responseObserver: StreamObserver<Hello.HelloResponse>?) {
println("[snowdeer] sayHello(${request?.greeting})")
val response = Hello.HelloResponse.newBuilder().setReply(request?.greeting).build()
responseObserver?.onNext(response)
responseObserver?.onCompleted()
}
override fun lotsOfReplies(request: Hello.HelloRequest?, responseObserver: StreamObserver<Hello.HelloResponse>?) {
println("[snowdeer] lotsOfReplies()")
}
override fun lotsOfGreetings(responseObserver: StreamObserver<Hello.HelloResponse>?): StreamObserver<Hello.HelloRequest> {
println("[snowdeer] lotsOfGreetings()")
return super.lotsOfGreetings(responseObserver)
}
override fun bidiHello(responseObserver: StreamObserver<Hello.HelloResponse>?): StreamObserver<Hello.HelloRequest> {
println("[snowdeer] bidiHello()")
return super.bidiHello(responseObserver)
}
}
HelloClient.kt
package com.snowdeer
import io.grpc.ManagedChannelBuilder
fun main(args: Array<String>) {
println("[snowdeer] main()")
val channel = ManagedChannelBuilder
.forAddress("localhost", 10004)
.usePlaintext()
.build()
val stub = HelloServiceGrpc.newBlockingStub(channel)
val response = stub.sayHello(getHelloRequest("hello. snowdeer"))
println("[snowdeer] response(${response.reply})")
}
fun getHelloRequest(greeting: String): Hello.HelloRequest {
return Hello.HelloRequest.newBuilder()
.setGreeting(greeting)
.build()
}
24 Dec 2019
|
Kotlin
gRPC
gRPC를 사용하기 위한 build.gradle
build.gradle
gRPC
를 사용하기 위해서는 필요한 플러그인과 종속성을 추가해야 합니다.
group 'com.snowdeer'
group 'com.snowdeer'
version '1.0-SNAPSHOT'
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'application'
apply plugin: 'com.google.protobuf'
apply plugin: 'idea'
mainClassName = "com.snowdeer.MainKt"
repositories {
mavenCentral()
}
buildscript {
ext.kotlin_version = '1.3.61'
ext.grpc_version = '1.17.0'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
compile "com.google.api.grpc:proto-google-common-protos:0.1.9"
compile "io.grpc:grpc-netty:${grpc_version}"
compile "io.grpc:grpc-protobuf:${grpc_version}"
compile "io.grpc:grpc-stub:${grpc_version}"
compile("javax.annotation:javax.annotation-api:1.3.2")
testCompile group: 'junit', name: 'junit', version:'4.12'
}
idea {
module {
sourceDirs += file("${projectDir}/build/generated/source/proto/main/java");
sourceDirs += file("${projectDir}/build/generated/source/proto/main/grpc");
}
}
compileKotlin.dependsOn ':generateProto'
sourceSets.main.java.srcDirs += 'build/generated/source/proto/main/grpc'
sourceSets.main.java.srcDirs += 'build/generated/source/proto/main/java'
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
protobuf {
protobuf {
protoc { artifact = "com.google.protobuf:protoc:3.6.1" }
plugins {
grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpc_version}" }
}
generateProtoTasks {
all()*.plugins { grpc {} }
}
}
}
main/proto/hello.proto
syntax = "proto3";
package com.snowdeer;
option java_outer_classname = "Hello";
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
rpc LotsOfReplies (HelloRequest) returns (stream HelloResponse);
rpc LotsOfGreetings (stream HelloRequest) returns (HelloResponse);
rpc BidiHello (stream HelloRequest) returns (stream HelloResponse);
}
message HelloRequest {
string greeting = 1;
}
message HelloResponse {
string reply = 1;
}
HelloService
에는 4개의 메소드가 정의되어 있습니다. 각각은 다음 특징을 가집니다.
SayHello
: 일반적인 Message 형태로 단일 요청에 단일 응답
LotsOfReplies
: 단일 요청에 대해 Stream 형태의 응답 전달
LotsOfGreetings
: 클라이언트에서 서버로 Stream 형태의 요청을 보내며, 서버에서는 단일 응답 전달함
BidiHello
: 양방향으로 Stream 형태의 요청과 응답을 전달함
위 파일을 추가한다음 Gradle 빌드 과정을 거치면 build/generated/source/proto
디렉토리 아래에 메시지 관련 클래스들이 생성됩니다.
23 Dec 2019
|
Kotlin
gRPC
gRPC
gRPC
는 구글이 공개한 RPC(Remote Procedure Call) 오픈 소스이며, CNCF 차원에서 밀고 있습니다.
여기에서 더 많은 정보를 볼 수 있습니다.
HTTP/2의 특징
HTTP/2
기반으로 되어 있으며 HTTP/1
에 비해 다음과 같은 장점을 가집니다.
- HTTP Connection 재사용: 기존
HTTP
에서는 매 요청마다 Connection을 새로 가져가지만, gRPC
에서는 Channel
이라는 형태로
기존 Connection을 유지해서 가져갑니다. 덕분에 매번 Connection하는 Cost가 대폭 줄었습니다.
- 멀티플렉싱:
gRPC
는 하나의 Connection에서 여러 요청을 보낼 수 있습니다. 또한 전송하는 데이터의 우선 순위를 정할 수도 있습니다.
- 메시지 압축:
HTTP/2
의 헤더 압축 기능을 사용합니다.
- 서버에서 Push 가능: 한 번 Connection이 맺어진 다음부터는 양방향 통신이 되기 때문에
Push
기능을 자연스럽게 사용할 수 있습니다.
gRPC와 Protobuf
gRPC는 메시지를 전송하는 IDL(Interface Definition Language)을 Protobuf
라는
라이브러리를 사용하고 있습니다.
message Person {
string name = 1;
int32 id = 2;
bool has_ponycopter = 3;
}
Protobuf는 위와 같은 형식으로 되어 있으며, .proto
확장자를 가집니다. protoc
라는 컴파일러를 이용해서 메시지를 컴파일 할 수 있으며,
컴파일된 결과물로 서버측과 클라이언트측에서 사용할 수 있는 코드가 생성됩니다.
위 메시지 예제에서 name
이나 id
와 같은 필드는 각 언어별로 적절한 Setter/Getter 함수를 자동으로 생성해서 제공해줍니다.
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
위 코드를 보면 service
와 message
가 존재합니다. service
는 서버와 클라이언트 양측에서 사용할 함수들의 묶음이라 생각할 수 있으며,
message
는 실제로 주고받는 데이터입니다.
23 Dec 2019
|
Kotlin
간단한 GUI Application 구현하기
package com.snowdeer
import java.awt.BorderLayout
import java.awt.Dimension
import java.lang.Thread.sleep
import javax.swing.JFrame
import javax.swing.JScrollPane
import javax.swing.JTextArea
fun main(args: Array<String>) {
val textArea = JTextArea()
textArea.text = "Hello, SnowDeer"
val scrollPane = JScrollPane(textArea)
val frame = JFrame("Hello, SnowDeer")
frame.contentPane.add(scrollPane, BorderLayout.CENTER)
frame.defaultCloseOperation = JFrame.EXIT_ON_CLOSE
frame.size = Dimension(600, 400)
frame.setLocationRelativeTo(null)
frame.isVisible = true
}
23 Dec 2019
|
Kotlin
Executable jar 생성하기
터미널에서 java -jar
명령어를 이용해서 간단하게 실행할 수 있는 jar
파일을 생성하는 예제입니다.
Main.kt
여기서는 com.snowdeer
라는 패키지를 만들고 그 안에 Main.kt
파일을 생성했습니다.
package com.snowdeer
import java.lang.Thread.sleep
fun main(args: Array<String>) {
println("Hello SnowDeer.")
val t = Thread() {
while (true) {
println("Thread is Running ...")
sleep(2000)
}
}
t.start()
}
build.gradle
buildscript {
ext.kotlin_version = '1.3.61'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'java'
apply plugin: 'kotlin'
group 'com.snowdeer'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
testCompile group: 'junit', name: 'junit', version: '4.12'
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
jar {
manifest {
attributes 'Main-Class': 'com.snowdeer.MainKt'
}
archiveName 'helloKotlin.jar'
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
위에서 dependencies
구문 안에 compile
키워드를 확인해야 합니다. 안드로이드 스튜디오에서 사용하던 gradle
에서는
compile
명령어가 deprecated
되어서 impelementation
키워드를 대신 사용하라고 되어 있지만, 사용하는 gradle
버전에
따라서는 compile
만 사용해야 정상 동작되는 경우가 있습니다.
빌드 및 실행
이후 터미널에서 다음 명령어를 이용해서 빌드 및 실행을 해봅니다.
$ ./gradlew clean build
$ java -jar build/libs/helloKotlin.jar
application 플러그인 추가
만약 gradle
명령어를 이용해서 실행을 해보고 싶으면 다음과 같이 apply plugin: 'application'
와 mainClassName = "com.snowdeer.MainKt"
를
추가해줍니다.
buildscript {
ext.kotlin_version = '1.3.61'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'application'
group 'com.snowdeer'
version '1.0-SNAPSHOT'
mainClassName = "com.snowdeer.MainKt"
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
testCompile group: 'junit', name: 'junit', version: '4.12'
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
jar {
manifest {
attributes 'Main-Class': 'com.snowdeer.MainKt'
}
archiveName 'helloKotlin.jar'
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
그 이후 터미널에서
명령어를 이용해서 바로 실행도 가능합니다.
IntelliJ에서 설정하는 방법
intelliJ
에서 IDE의 run
메뉴를 통해 실행하는 방법은 다음과 같습니다.
- 메뉴의
Run > Run
을 누른다음 Edit Configuration
선택
- 왼쪽의
+
버튼을 눌러서 Gradle
항목 선택
Gradle project
항목 옆의 버튼을 눌러서 실행할 프로젝트 선택
Arguments
에 run
명령어 추가
또는 다음과 같이 할 수도 있습니다.
- 메뉴의
Run > Run
을 누른다음 Edit Configuration
선택
- 왼쪽의
+
버튼을 눌러서 Application
항목 선택
Main Class
를 선택. 위 예제에서는 com.snowdeer.MainKt
선택
use classpath of module
에서 실행할 모듈 선택