///
Search

00_Study of Network

01.Marshalling

자바 프로그래밍을 하다보면 marshalling(마셜링), unmarshalling(언마셜링) 등의 단어를 자주 접하게 되는데 확 이미지가 와닿지가 않아 조금 찾아보았다.
marshal 이란 단어는 사전에서 찾아보면 명사로 쓰일 때는 군대의 원수나 경찰서장, 법원의 집행관 등을 의미하고 , 동사로서는  (특정 목적을 위해 사람.사물.생각등을) 모으다, 결집시키다 라는 뜻과 (많은 사람들을) 통제하다 라는 뜻이 있다. 즉, 많은 것들을 모으거나 통제한다는 관리적인 개념의 단어라고 할 수 있다.
marshal 의 동사의 의미를 명사화 시킨 (것으로 추정되는) marshalling이란  단어를 찾아보면 정렬시키기, 순위결정 이라고 나온다.
자바에서 마셜링이란 자바 객체를 byte stream으로 변환하는 과정을 의미한다. 또는 자바 객체를 XML문서로 변환하는 것을 의미하기도 한다. 마셜링이란 단어가 정렬시키기를 의미한다고 생각했을 때  데이터를 잘 정돈된 상태로 만드는 이미지를 상상할 수 있다. 잘 정돈된 상태여야 나중에 복원(언마셜링)을 할 수 있을 것이기 때문이다.
정리하면, 메모리에 존재하는 자바 객체를 범용적인 파일이나 바이트 스트림으로 변환 하는 작업을 마셜링이라고 부르고, 거꾸로 스트림이나 파일로부터 자바 객체를 만들어내는 작업을 언마셜링이라고 부른다.

02.Marshalling_Go

소프트웨어는 바이트 단위로 데이터를 인식한다. 97이란 바이트 값을 정수로 보면 97이지만 문자로 보면 "a"다. 메모리 바이트는 해석하는 틀에 따라 달라지는데 이러한 변환을 '인코딩' 또는 '마샬링'이라고 한다.
Go의 encoding이 이를 담당하는 기본 패키지다. 실제로는 인터페이스 타입만 정의 되어 있고 데이터 형태에 따라 서브 패키지로 기능을 제공한다.
그 중 컴퓨터간 통신을 위한 테이터 포맷인 encoding/json 패키지에 대해 살펴 보겠다. 마지막엔 에코(echo) 웹프레임웍에서 이를 어떻게 사용하는지 확인하고 글을 마무리하겠다.

03.패닉 & 복구

패닉

언어에 내장된 panic 함수는 go 루틴의 정상적인 실행을 중단한다.
모든 지연된 함수가 정상적으로 실행된 후 프로그램이 종료된다.
func panic(v interface{})
Go
복사

복구

애플리케이션이 패닉에 빠진 Go 루틴 동작을 제어하도록 한다.
패닉을 중지하고 패닉 함수 호출시 전달된 에러를 리턴한다.
07장에 상세 참고 바람
func recover() interface{}
Go
복사

04.마샬링, 언마샬링, 인코더, 디코더

구조체를 활용한 마샬링 함수 기본동작

type helloWorldResponse struct {//구조체 Message string } func main() { port := 8080 http.HandleFunc("/helloworld", helloWorldHandler) log.Printf("Server starting on port %v\n", port) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", port), nil)) } func helloWorldHandler(w http.ResponseWriter, r *http.Request) { response := helloWorldResponse{Message: "Hello World"} data, err := json.Marshal(response) if err != nil { panic("Ooops") } fmt.Fprint(w, string(data)) }
Go
복사
구조체 필드의 이름을 가져와서 JSON 출력의 필드로 사용하는 것도 마샬링 함수 기본동작이다.
주의 고에서는 소문자로 된 구조체 맴버 변수는 내보낼수 없다.
구조체 필드 태그를 사용하면 출력이 어떻게 표시되는지 더 잘 알 수가 있다.
type helloWorldResponse struct { Message string `json:"message"`//구조체 필드 태그 붙인것임 }
Go
복사
위의 코드에서 필드 태그를 사용해 출력을 더 많이 할 수 있다.
★★★객체 타입을 변환도 가능하며 필드 모두를 무시할 수도 있다. (대박기능)
type helloWorldResponse struct { Message string `json:"message"`//구조체 필드 태그 붙인것임 즉 출력 필드를 메시지로 바꾼다. Author string `json:"-"` // 이 필드는 출력하지 않는다. Date string `json:",omitempty"` // 값이 없으면 필드를 출력 안 한다. ID int `json:"id,string"` // 출력을 문자열로 변환하고 이름을 id로 변경 }
Go
복사
채널 복소수 및 함수는 JSON으로 인코딩 할 수 없다.
순환 데이터 구조를 나타낼 수 없다. (무한 재귀)때문에
들여쓰기(indent)로 깔끔하게 양식을 지정한 JSON을 보내려면 아래의 함수를 사용해야한다.
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
Go
복사
Go는 스트림에 직접 쓸 수 있는 인코더 디코더가 있다.
//ResponseWriter로 응답하기 //핸들러 함수 인자 중 ResponseWriter가 응답을 위한 구조체다. type ResponseWriter interface { Header() Header Write([]byte) (int, error) WriteHeader(statusCode int) } //Write 메소드가 io.Writer 인터페이를 충족하기 때문에 fmt.Fprintf 함수로 출력을 보낼 수 있다. func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "URL: %#v\n\nQuery: %#v\n\nname: %s\n", r.URL, r.URL.Query(), r.URL.Query().Get("name")) }) http.ListenAndServe(":8080", nil) //서버를 구동하고 요청을 보내면 서버 터미널에 찍현던 로그가 응답 데이터로 전달된다.
Go
복사

인코더

많은 데이터를 처리할 때 스트림을 사용하는 것은 현명한 방법이다.
json.Encoder 타입은 스트림 기반으로 JSON 문자열을 만든다.
type Encoder struct func NewEncoder(w io.Writer) *Encoder func (enc *Encoder) Encode(v interface{}) error
Go
복사
json.NewEncoder 함수로 인코더를 생성하고 json.Encode 함수로 Go 밸류를 JSON으로 변환한다.
표준 출력으로 인코딩하는 스트림을 만들어 보자.
u = User {"Gopher", 7} enc := json.NewEncoder(os.Stdout) enc.Encode(u) // {"name":"Gopher","age":7}
Go
복사
io.Writer 타입을 인자로 받는 json.NewEncoder에 표준 출력 os.Stdout를 전달한다.
생성된 인코더는 앞으로 입력할 데이터를 표준 출력으로 연결하는 스트림을 갖는다.
Encode(u) 로 User 값을 보내면 표준 출력에는 인코딩된 JSON 문자열이 출력된다.
마찬가지로 io.Writer 인터페이스를 따르는 파일도 스트림으로 연결할 수 있다.
f, _ := os.Create("out.txt") enc := json.NewEncoder(f) enc.Encode(u)
Go
복사
out.txt 파일을 생성한 뒤 json 인코더에 연결했다.
그리고나서 User 값을 보내면 이 파일에 인코딩된 텍스트가 기록된다.
$ ls out.txt $ cat out.txt {"name":"Gopher","age":7}
Go
복사
마샬링처럼 인코더도 들여쓰기를 설정할 수 있는 SetIndent 메소드를 제공한다.
enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") enc.Encode(u) /* { "name": "Gopher", "age": 7 } */
Go
복사
마샬링의 결과를 바이트 배열에 저장하는 대신 HTTP 응답에 바로 쓸 수 있다.
func helloWorldHandler(w http.ResponseWriter, r *http.Request) { response := helloWorldResponse{Message: "HelloWorld"} encoder := json.NewEncoder(w) encoder.Encode(response) }
Go
복사
★★두개의 파일을 한개의 폴더에 넣어두고 인코더와 마샬링의 속도를 비교할 수 있다.
package main import ( "encoding/json" "fmt" "log" "net/http" ) type helloWorldResponse struct { Message string `json:"message"`//구조체 필드 태그 붙인것임 } func main() { port := 7080 http.HandleFunc("/helloworld", helloWorldHandler) log.Printf("Server starting on port %v\n", port) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", port), nil)) } func helloWorldHandler(w http.ResponseWriter, r *http.Request) { response := helloWorldResponse{Message: "HelloWorld"} data, err := json.Marshal(response) if err != nil { panic("Ooops") } fmt.Fprint(w, string(data)) }
Go
복사
package main import ( "encoding/json" "fmt" "io/ioutil" "testing" ) type Response struct { Message string } func BenchmarkHelloHandlerVariable(b *testing.B) { b.ResetTimer() var writer = ioutil.Discard response := Response{Message: "Hello World"} for i := 0; i < b.N; i++ { data, _ := json.Marshal(response) fmt.Fprint(writer, string(data)) } } func BenchmarkHelloHandlerEncoder(b *testing.B) { b.ResetTimer() var writer = ioutil.Discard response := Response{Message: "Hello World"} for i := 0; i < b.N; i++ { encoder := json.NewEncoder(writer) encoder.Encode(response) } } func BenchmarkHelloHandlerEncoderReference(b *testing.B) { b.ResetTimer() var writer = ioutil.Discard response := Response{Message: "Hello World"} for i := 0; i < b.N; i++ { encoder := json.NewEncoder(writer) encoder.Encode(&response) } }
Go
복사
컴파일 방법
go test -v -run="none" -bench=. -benchtime="5s" -benchmem
PowerShell
복사

언마샬링

반대로 바이트 슬라이스나 문자열을 논리적 자료 구조로 변경하는 것을 언마샬링(Unmashaling)이라고 한다. json.Unmarshal 이 이 역할을 하는 함수다.
func Unmarshal(data []byte, v interface{}) error
Go
복사
불리언 값으로 이루어진 문자열 "true"를 언마샬링 해보자.
var b bool json.Unmarshal([]byte("true"), &b) fmt.Printf("%t\n", b) // true
Go
복사
첫번째 인자로 바이트 슬라이스를 넘겨주고, 두번째로 결과를 담게될 변수 포인터를 넘겨준다. 시그니처를 보면 모든 타입을 의미하는 interface{}이기 때문에 v의 타입에 따라 언마샬링 하는 방식이다. 출력하면 true 값이 나온다.
합성타입도 마찬가지다.
var s = `{"name":"gopher","age":7}` var u User json.Unmarshal([]byte(s), &u) fmt.Printf("%+v\n", u) // {Name:gopher Age:7}
Go
복사
JSON 문자열을 바이트 슬라이스 형태로 넘겨주고 User 타입 변수 u의 포인터를 전달한다. 함수가 실행되면 문자열이 파싱되어 User 값이 생성된다.

디코딩

인코딩과 반대로 JSON 문자열을 Go 밸류로 바꾸는 것을 디코딩(Decoding)이라고 하는데 json.Decoder가 바로 이런 경우 사용하는 타입이다.
type Decoder struct func NewDecoder(r io.Reader) *Decoder func (dec *Decoder) Decode(v interface{}) error
Go
복사
json.NewDecoder 함수로 디코더를 만들고 json.Decode 메소드로 JSON 문자열을 Go 밸류로 변경한다.
표준입력에서 들어온 데이터를 스트림 방식으로 디코딩하는 기능을 만들어보자.
var u User dec := json.NewDecoder(os.Stdin) dec.Decode(&u) fmt.Printf("%+v\n", u)
Go
복사
io.Reader 타입을 인자로 받는 json.NewDecoder에 표준 입력 os.Stdin을 전달한다. 생성된 디코더는 표준 입력으로 연결된 스트림을 갖게 된다. 표준 입력으로부터 데이터가 들어오면 Decode(&u) 메소드에 의해 User 데이터로 변경되는 것이다.
$ {"name":"gopher","age":7} {Name:gopher Age:7}
Go
복사
io.Reader 인터페이스를 만족하는 파일도 마찬가지다.
$ cat input.txt {"name":"gopher",""age": 7} f, _ := os.Open("input.txt") dec := json.NewDecoder(f) dec.Decode(&u) fmt.Printf("%+v\n", u) // {Name:Gopher Age:7}
Go
복사
input.txt에 기록된 JSON 문자열을 스트림으로 받아서 User 타입으로 변환하였다. 결과를 출력하면 구조체인 것을 확인할 수 있다.

마샬링과 인코딩의 차이

바이트 슬라이스나 문자열을 사용하려면 Marshal/Unmarshal 함수가 적합하다. 만약 표준 입출력이나 파일 같은 Reader/Writer 인터페이스를 사용하여 스트림 기반으로 동작하려면 Encoder/Decoder를 사용한다.
처리 속도는 스트림 방식이 더 낫다. 데이터 크기가 작다면 성능차이를 체감할 수 없지만 비교적 큰 데이터를 다룬다면 스트림 기반의 Encoder/Decoder가 거의 50% 정도 더 빠른 성능을 낸다(출처: Go 언어를 활용한 마이크로서비스 개발 - 에이콘)

HTTP 서버 핸들러

웹 서버와 클라이언트는 JSON으로 통신한다. 요청 바디로 들어온 JSON 문자열을 구조체로 변환 후 로직에 활용한다. 반대로 응답용 구조체는 JSON문자열로 변환하여 바디에 담아 클라이언트로 전달한다.
Go에서 유명한 웹 프레임웍인 에코(Echo)에서는 내부적으로 encoding/json 패키지를 사용해 이를 구현한다.
서버가 요청을 받으면 요청바디를 Go 밸류로 변경하는데 스트림 방식을 사용하고 있다. DefaultBinder의 Bind 메소드를 보자
func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) { req := c.Request() // 생략 json.NewDecoder(req.Body).Decode(i) // 생략 }
Go
복사
c.Request() 함수 호출로 반환된 http.Request는 요청 데이터를 담고있는 Body 프로퍼티를 가지고 있다. io.Reader 인터페이스를 구현한 io.ReadClose 타입의 req.Body를 json.NewDecoder 인자로 전달하여 스트림을 생성한다. 그리고 Decode 함수를 이용해 req.Body 값을 스트림 기반으로 디코딩 하는 것이다.
응답 데이터를 보낼 때는 컨택스트의 json 메소드를 호출하는데 여기서도 스트림 방식으로 인코딩된 문자열을 생성한다.
func (c *context) json(code int, i interface{}, indent string) error { enc := json.NewEncoder(c.response) if indent != "" { enc.SetIndent("", indent) } c.writeContentType(MIMEApplicationJSONCharsetUTF8) c.response.WriteHeader(code) return enc.Encode(i) }
Go
복사
io.Writer 인터페이스를 구현한 c.Response를 이용해 스트림 기반 인코더를 생성한다. 들여쓰기를 설정하고 헤더값 처리 후 마지막에 Encode() 메소드를 호출하여 i 값을 문자열로 변환한다.

정리

encoding/json 패키지는 JSON 문자열(혹은 바이트 슬라이스)과 타입간의 변환시 사용한다.
문자열을 다룰 때는 json.Marshal, json.Unmashal 함수를 사용한다. json.Marshal은 Go 밸류를 JSON 문자열로 변환하고 json.Unmashal은 그 반대 방향으로 동작한다.
스트림 방식으로 데이터를 다룰 때는 json.Encoder, json.Decoder 타입을 사용한다. json.Encoder는 Go 밸류를 JSON 문자열로 변환하고 json.Decoder는 그 반대 방향일 때 사용한다.

05.net/http를 사용한 라우팅

웹 어플리케이션을 개발하려고 Go 언어를 살펴보기 시작했다. 앞서 정리한 몇 가지 기본 패키지는 net/http 패키지를 사용하기 위한 준비 과정이라 생각하자. 이번에는 네트웍 프로그래밍을 위한 net/http 패키지 사용법을 정리해 보겠다.

Get 요청하기

브라우져는 사용자가 입력한 url를 통해 해당 웹페이지를 요청한다. 이처럼 웹상의 리소스를 요청하려면 패키지의 Get 함수를 사용한다.
func Get(url string) (resp *Response, err error)
Go
복사
요청 주소 url을 인자로 받아 Response를 반환하는 함수다. 예제로 구글의 robots.txt 파일을 요청해서 응답 결과를 확인해겠다.
url := "https://google.com/robots.txt" resp, _ := http.Get(url) robots, _ := ioutil.ReadAll(resp.Body) resp.Body.Close() fmt.Printf("%s\n", robots)
Go
복사
Get으로 요청하여 서버로 부터 응답을 받으면 데이터를 읽은 뒤 Close 함수로 바디를 닫아 주어야 한다.
터미널에 출력하면 수신한 파일 내용을 확인할 수 있다.
User-agent: * Disallow: /search Allow: /search/about // 생략
Go
복사

Client와 Request로 요청 제어하기

Get 함수는 내부에서 Client 구조체를 사용하고 있다.
func Get(url string) (resp *Response, err error) { return DefaultClient.Get(url) } var DefaultClient = &Client{}
Go
복사
요청에 대한 세부적인 제어를 하려면 Client를 하나 생성해야 한다. 가령 자동으로 리다이렉트하는 서버일 경우 요청단에서 이를 차단하여 한 번만 요청할 수 있다.
client := &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { fmt.Println("redirectPolicyFunc()") return http.ErrUseLastResponse // 자동 리다이렉트 하지 않음 }, }
Go
복사
Client 생성시 CheckRedirect 훅에 리다이렉트 관련 동작을 정의했다. ErrUseLastResponse 값을 리턴하면 리다이렉트를 처리할때 다음 요청을 보내지 않겠다는 의미다.
더불어 Request 구조체도 필요한데 NewRequest 함수로 만들 수 있다.
func NewRequest(method, url string, body io.Reader) (*Request, error)
Go
복사
요청 메소드, 주소, 바디 데이터를 인자로 받아 Request를 생성한다.
req, _ := http.NewRequest("GET", "https://google.com/robots.txt", nil) req.Header.Add("set-cookie", "foo=bar") // 헤더값 설정
Go
복사
반환된 Request는 헤더 정보를 담고있는 Header의 Add 메소드로 요청 헤더를 설정할 수 있다.
이렇게 준비한 Client와 Request로 요청을 보내기 위해 Client의 Do 메소드를 사용하자.
func (c *Client) Do(req *Request) (*Response, error)
Go
복사
이것은 응답 정보를 담은 Response를 반환하기 때문에 ReadAll 함수로 읽을 수 있다.
resp, _ := client.Do(req) robots, _ := ioutil.ReadAll(resp.Body) resp.Body.Close() fmt.Printf("%s\n", robots)
Go
복사
서버를 구동한뒤 요청을 보내보면 리다이렉트 요청을 하지 않았기 때문에 다음과 같은 응답을 받게 될 것이다.
redirectPolicyFunc() <HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8"> <TITLE>301 Moved</TITLE></HEAD><BODY> <H1>301 Moved</H1> The document has moved <A HREF="https://www.google.com/robots.txt">here</A>. </BODY></HTML>
Go
복사

서버 구현을 위한 Handler

브라우져가 서버로 요청을 만들면 서버는 해당 요청을 처리하고 응답한다. 이러한 요청/응답 패턴을 추상화한 것이 바로 Handler 인터페이스다.
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
Go
복사
웹 서버에서 "핸들러"라는 이름은 보통 라우팅할 때 경로에 따른 로직을 가리킬 때 사용한다. Handler 인터페이스도 마찬가지 역할을 하는데 ServeHTTP 메소드가 응답 헤더와 데이터를 ResponseWriter로 보내는 역할을 한다.

서버를 구동하는 ListenAndServe

Handler 인터페이스는 어디에서 사용할까? 가장 쉽게 발견할 수 있는 곳이 바로 서버를 구동하는 ListenAndServe 함수다.
func ListenAndServe(addr string, handler Handler) error
Go
복사
리슨할 주소 정보와 핸들러를 인자로 받아 서버를 요청대기상태로 만드는 일을하는 함수다.

정적 파일 서버를 만들수 있는 FileServe

그럼 Handler 구현체는 뭐가 있을까? 정적 파일을 호스팅하는 FileServer 함수가 이를 반환한다.
func FileServer(root FileSystem) Handler
Go
복사
웹 프론트엔드 개발시 정적서버를 띄워서 작업하는 경우 파이썬의 SimpleHTTPServer 같은 프로그램을 사용해서 로컬 환경에 개발 서버를 띄운다. Go를 이용하면 이런 서버를 만드는데 한 줄이면 충분하다.
http.ListenAndServe(":8080", http.FileServer(http.Dir("./public")))
Go
복사
코드를 실행하면 서버가 구동될 것이다. 브라우져로 확인해 보면 public 폴더의 정적파일이 다운로드 되는것을 확인 할수 있다.
$ tree ./ ./ ├── main.go └── public ├── index.html ├── script.js └── style.css $ go run main.go
PowerShell
복사

06. TCP 통신

Go 언어에서는 기본 패키지에 다양한 네트워크 프로토콜을 제공합니다. 이중 TCP는 네트워크 전송계층에서 가장 핵심적인 부분이라고 할 수 있을 정도로 많이 사용되며, HTTP 프로토콜도 TCP기반으로 동작합니다.
간단하게 서버 / 클라이언트 통신 서버를 만들어 보고 동작하는지 확인해보도록 하겠습니다.

TCP Server

package main import ( f "fmt" "net" ) func main() { f.Println("server running 8888 port") ln, err := net.Listen("tcp", ":8888") // 8888 포트로 리스닝하는 tcp 서버를 생성합니다. if err != nil { f.Println(err) return } defer ln.Close() for { conn, err := ln.Accept() // 클라이언트로 부터 커넥션이 들어올 경우 이를 받아드립니다. if err != nil { f.Println(err) continue } defer conn.Close() go requestHandler(conn) } } func requestHandler(conn net.Conn) { data := make([]byte, 4096) // 클라이언트와 서버간의 데이터 길이를 정의합니다. (바이트 슬라이스) for { n, err := conn.Read(data) if err != nil { f.Println(err) return } f.Println(string(data[:n])) _, err = conn.Write(data[:n]) // 해당 클라이언트로부터 데이터를 전송합니다. if err != nil { f.Println(err) return } } }
Go
복사

TCP Client

package main import ( f "fmt" "net" "strconv" "time" ) func main() { client, err := net.Dial("tcp", "127.0.0.1:8888") // 해당 서버로 접속 시도 if err != nil { f.Println(err) return } defer client.Close() go func(c net.Conn) { data := make([]byte, 4096) // 서버와 클라이언트간의 데이터 길이 동기화 for { n, err := c.Read(data) if err != nil { f.Println(err) return } f.Println(string(data[:n])) time.Sleep(1 * time.Second) } }(client) go func(c net.Conn) { i := 0 for { s := "Hello " + strconv.Itoa(i) _, err := c.Write([]byte(s)) // 서버로 부터 데이터 전송 if err != nil { f.Println(err) return } i++ time.Sleep(1 * time.Second) } }(client) f.Scanln() }
Go
복사

RPC 통신

RPC(Remote Procedure call)이란, 별도의 원격 제어를 위한 코딩 없이 다른 주소 공간에서 리모트의 함수나 프로시저를 실행 할 수 있게 해주는 프로세스간 통신입니다. 즉, 위치에 상관없이 RPC를 통해 개발자는 위치에 상관없이 원하는 함수를 사용할 수 있습니다.
RPC 모델은 분산컴퓨팅 환경에서 많이 사용되어왔으며, 현재에는 MSA(Micro Software Archtecture)에서 마이크로 서비스간에도 많이 사용되는 방식입니다. 서로 다른 환경이지만 서비스간의 프로시저 호출을 가능하게 해줌에 따라 언어에 구애받지 않고 환경에 대한 확장이 가능하며, 좀 더 비지니스 로직에 집중하여 생산성을 증가시킬 수 있습니다.
아래 간단한 RPC 예제를 작성해 보도록 하겠습니다.

RPC Server

package main import ( f "fmt" "net" "net/rpc" ) type Calc int // RPC 서버에 등록하기 위해 임의의 타입으로 정의 type Args struct { // 매개변수 A, B int } type Reply struct { // 리턴값 C int } func main() { f.Println("rpc server running at 6000 port") rpc.Register(new(Calc)) // Calc 타입의 인스턴스를 서버의 등록 ln, err := net.Listen("tcp", ":6000") if err != nil { f.Println(err) return } defer ln.Close() for { conn, err := ln.Accept() if err != nil { continue } defer conn.Close() go rpc.ServeConn(conn) } } func (c *Calc) Sum(args Args, reply *Reply) error { reply.C = args.A + args.B // 두 값을 더하여 리턴값 구조체에 넣어줌 return nil }
Go
복사

RPC Client

package main import ( f "fmt" "net/rpc" ) type Args struct { A, B int } type Reply struct { C int } func main() { client, err := rpc.Dial("tcp", "127.0.0.1:6000") // RPC 서버 접ㅈ속 if err != nil { f.Println(err) return } defer client.Close() // 동기 호출 args := &Args{1, 2} reply := new(Reply) err = client.Call("Calc.Sum", args, reply) // 함수 호출 if err != nil { f.Println(err) return } f.Println(reply.C) // 비동기 호출 args.A = 4 args.B = 9 sumCall := client.Go("Calc.Sum", args, reply, nil) // 고루틴으로 호출 <-sumCall.Done f.Println(reply.C) }
Go
복사

HTTP 서버

Go 언어를 사용한 기본 컨셉을 확인해 보았습니다. Go 언어는 디바이스 제어부터 애플리케이션 계층까지 다양한 분야에서 사용할 수 있습니다. 마지막으로 간단한 HTTP 서버를 작성하여 마루리 하도록 하겠습니다.
package main import ( f "fmt" "net/http" ) func main() { f.Println("running http server at 8080 port") http.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { res.Write([]byte("Hello World")) }) http.ListenAndServe(":8080", nil) }
Go
복사