Connecting

Go 언어 간단한 TCP / RPC / HTTP 서버 만들기 본문

Go 언어

Go 언어 간단한 TCP / RPC / HTTP 서버 만들기

팬도라 2020. 7. 30. 18:15
반응형

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
        }
    }
}

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()

}

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

}

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)

}

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)
}

마지막 인사

지금까지 본 튜토리얼을 따라와 주셔서 감사합니다. 본 문서에서 수정해야하는 부분이 있다면 seongwon@edu.hanbat.ac.kr로 연락주시면 빠르게 수정할 수 있도록 하겠습니다. 감사합니다.

Comments