Go 언어로 구현하는 네트워크 프로토콜 🚀

콘텐츠 대표 이미지 - Go 언어로 구현하는 네트워크 프로토콜 🚀

 

 

안녕하세요, 여러분! 오늘은 정말 흥미진진한 주제로 여러분과 함께 시간을 보내려고 해요. 바로 Go 언어를 사용해 네트워크 프로토콜을 구현하는 방법에 대해 알아볼 거예요. 🎉

네트워크 프로토콜이라고 하면 뭔가 어렵고 복잡할 것 같죠? 하지만 걱정 마세요! 우리는 마치 레고 블록을 조립하듯이, 차근차근 재미있게 배워나갈 거예요. 😊

Go 언어는 구글에서 개발한 프로그래밍 언어로, 특히 네트워크 프로그래밍에 강점을 가지고 있어요. 마치 슈퍼히어로가 특별한 능력을 가진 것처럼, Go는 네트워크 관련 작업을 수행하는 데 탁월한 능력을 가지고 있답니다! 🦸‍♂️

이 글을 통해 여러분은 Go 언어의 기본부터 시작해서, 실제로 동작하는 네트워크 프로토콜을 구현하는 방법까지 배우게 될 거예요. 마치 요리 레시피를 따라 맛있는 요리를 만드는 것처럼, 우리도 함께 멋진 네트워크 프로그램을 만들어볼 거예요! 🍳

그리고 혹시 여러분 중에 이런 기술을 배우고 나서 "어떻게 활용할 수 있을까?" 고민하시는 분들이 계신다면, 재능넷(https://www.jaenung.net)이라는 플랫폼을 소개해드리고 싶어요. 여기서 여러분의 새로운 기술을 다른 사람들과 공유하고, 또 다른 재능을 배울 수 있답니다! 🌟

자, 그럼 이제 본격적으로 Go 언어의 세계로 들어가볼까요? 안전벨트 꽉 매세요, 신나는 여행이 시작됩니다! 🚗💨

1. Go 언어 소개: 네트워크 프로그래밍의 슈퍼히어로 🦸‍♂️

여러분, Go 언어에 대해 들어보신 적 있나요? 아직 모르신다고요? 걱정 마세요! 지금부터 Go 언어가 얼마나 멋진 녀석인지 소개해드릴게요. 😎

Go 언어의 특징:

  • 간결하고 읽기 쉬운 문법 📚
  • 빠른 컴파일 속도 ⚡
  • 강력한 동시성 지원 🔄
  • 효율적인 가비지 컬렉션 🗑️
  • 풍부한 표준 라이브러리 📦

Go 언어는 2009년에 구글의 천재 프로그래머들이 만든 언어예요. 그들은 "왜 프로그래밍이 이렇게 복잡해야 하지?"라고 생각했죠. 그래서 탄생한 것이 바로 Go 언어랍니다!

Go는 마치 슈퍼히어로처럼 여러 가지 특별한 능력을 가지고 있어요:

  • 초스피드 컴파일: Go는 번개처럼 빠르게 컴파일돼요. C++이나 Java가 커피 한 잔 마실 동안 컴파일되는 동안, Go는 이미 실행을 마치고 "다 했어요!"라고 말할 거예요. ☕➡️🚀
  • 동시성의 마법사: Go의 고루틴(goroutine)과 채널(channel)을 사용하면, 복잡한 동시성 프로그래밍도 마법처럼 쉬워져요. 마치 여러 분신을 만들어 동시에 여러 일을 처리하는 것과 같죠! 🧙‍♂️✨
  • 가비지 컬렉션의 청소부: 메모리 관리? 걱정 마세요! Go의 가비지 컬렉터가 알아서 깔끔하게 처리해줘요. 여러분은 그저 코딩에만 집중하세요. 🧹✨
  • 표준 라이브러리의 보물창고: Go의 표준 라이브러리는 마치 도라에몽의 4차원 주머니 같아요. 필요한 도구가 거의 다 들어있죠! 특히 네트워크 프로그래밍에 필요한 도구들이 풍성하답니다. 🎒💎

이런 특징들 때문에 Go는 특히 네트워크 프로그래밍에 아주 적합해요. 복잡한 네트워크 통신도 Go를 사용하면 마치 레고 블록을 조립하듯 쉽고 재미있게 만들 수 있답니다! 🧱🔧

그럼 이제 Go 언어의 기본 문법을 살펴볼까요? Go 언어의 "Hello, World!"를 만나볼 시간이에요!


package main

import "fmt"

func main() {
    fmt.Println("Hello, 네트워크 세상!")
}
  

와! 정말 간단하죠? 이 코드를 실행하면 콘솔에 "Hello, 네트워크 세상!"이라고 출력될 거예요. 🌍

이제 여러분은 Go 언어의 매력에 푹 빠지셨나요? 😍 Go 언어는 정말 재미있고 강력한 도구예요. 특히 네트워크 프로그래밍을 할 때 그 진가를 발휘한답니다.

다음 섹션에서는 Go 언어를 사용해 실제로 네트워크 프로그래밍을 어떻게 하는지 알아볼 거예요. 준비되셨나요? 우리의 네트워크 모험이 이제 막 시작됐어요! 🚀

🌟 재능넷 Tip: Go 언어를 배우고 나면, 여러분의 새로운 기술을 재능넷에서 공유해보는 건 어떨까요? 다른 사람들에게 Go 언어의 매력을 전파하고, 동시에 여러분의 지식을 나누는 멋진 경험이 될 거예요!

2. Go로 시작하는 네트워크 프로그래밍: 첫 걸음 👣

자, 이제 본격적으로 Go를 사용해 네트워크 프로그래밍을 시작해볼까요? 😃 네트워크 프로그래밍이라고 하면 뭔가 어렵고 복잡할 것 같지만, Go와 함께라면 그리 어렵지 않아요!

우리의 첫 번째 미션은 간단한 TCP 서버와 클라이언트를 만드는 것이에요. 마치 전화기의 송수신기를 만드는 것과 비슷하답니다. 🎧📞

2.1 TCP 서버 만들기

먼저 TCP 서버를 만들어볼게요. 이 서버는 클라이언트의 연결을 기다리고, 연결이 되면 "안녕하세요!"라는 메시지를 보내줄 거예요.


package main

import (
    "fmt"
    "net"
)

func main() {
    // 8080 포트에서 TCP 연결을 수신합니다.
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        fmt.Println("서버를 시작하는 데 문제가 발생했어요:", err)
        return
    }
    defer listener.Close()

    fmt.Println("서버가 8080 포트에서 실행 중입니다...")

    for {
        // 클라이언트의 연결을 기다립니다.
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("클라이언트 연결에 문제가 발생했어요:", err)
            continue
        }

        // 고루틴을 사용해 클라이언트를 처리합니다.
        go handleClient(conn)
    }
}

func handleClient(conn net.Conn) {
    defer conn.Close()

    // 클라이언트에게 메시지를 보냅니다.
    conn.Write([]byte("안녕하세요! Go 네트워크 서버입니다!\n"))
}
  

와! 우리의 첫 번째 TCP 서버가 완성됐어요. 🎉 이 서버는 다음과 같이 동작해요:

  1. 8080 포트에서 TCP 연결을 기다립니다.
  2. 클라이언트가 연결하면, 새로운 고루틴을 만들어 클라이언트를 처리합니다.
  3. 클라이언트에게 "안녕하세요! Go 네트워크 서버입니다!" 메시지를 보냅니다.
  4. 연결을 닫고 다음 클라이언트를 기다립니다.

고루틴(goroutine)을 사용해 각 클라이언트를 처리하는 것에 주목해주세요. 이렇게 하면 여러 클라이언트를 동시에 처리할 수 있어요. 마치 레스토랑에서 여러 웨이터가 동시에 여러 테이블을 서빙하는 것과 같죠! 🍽️👨‍🍳👩‍🍳

2.2 TCP 클라이언트 만들기

이제 서버에 연결할 클라이언트를 만들어볼까요?


package main

import (
    "bufio"
    "fmt"
    "net"
    "os"
)

func main() {
    // 서버에 연결합니다.
    conn, err := net.Dial("tcp", "localhost:8080")
    if err != nil {
        fmt.Println("서버에 연결하는 데 문제가 발생했어요:", err)
        return
    }
    defer conn.Close()

    // 서버로부터 메시지를 읽습니다.
    message, _ := bufio.NewReader(conn).ReadString('\n')
    fmt.Print("서버로부터 받은 메시지: ", message)

    // 사용자 입력을 받아 서버로 보냅니다.
    for {
        reader := bufio.NewReader(os.Stdin)
        fmt.Print("서버에 보낼 메시지를 입력하세요: ")
        text, _ := reader.ReadString('\n')
        fmt.Fprintf(conn, text + "\n")
    }
}
  

짜잔! 🎭 우리의 TCP 클라이언트도 완성됐어요. 이 클라이언트는:

  1. localhost의 8080 포트로 연결을 시도합니다.
  2. 서버로부터 받은 환영 메시지를 출력합니다.
  3. 사용자로부터 입력을 받아 서버로 전송합니다.

이제 우리는 완전한 TCP 통신 시스템을 만들었어요! 서버와 클라이언트가 서로 대화를 나눌 수 있게 된 거죠. 마치 두 사람이 전화로 이야기를 나누는 것과 같아요. 📞👥

🌟 실습 팁: 이 코드들을 직접 실행해보세요! 두 개의 터미널 창을 열어 하나는 서버를, 다른 하나는 클라이언트를 실행해보세요. 그리고 클라이언트에서 메시지를 입력해보세요. 어떤 일이 일어나나요?

우와, 정말 신기하지 않나요? 우리가 방금 만든 이 간단한 프로그램들이 실제로 인터넷의 기본 원리와 똑같이 동작한다는 사실! 🌐

이제 여러분은 Go를 사용해 기본적인 네트워크 프로그램을 만들 수 있게 됐어요. 이것이 바로 모든 복잡한 네트워크 애플리케이션의 기초랍니다. 페이스북, 트위터, 심지어 재능넷같은 플랫폼도 이런 기본 원리를 확장해서 만들어진 거예요!

다음 섹션에서는 좀 더 복잡한 네트워크 프로토콜을 구현해볼 거예요. HTTP 서버를 만들어볼 준비가 되셨나요? 우리의 네트워크 여행은 계속됩니다! 🚀

3. Go로 HTTP 서버 만들기: 웹의 세계로! 🌐

자, 이제 우리는 한 단계 더 나아가 볼 거예요. TCP 통신을 넘어서 실제 웹 서버를 만들어볼 시간이에요! 😃 HTTP(HyperText Transfer Protocol)는 웹의 기본이 되는 프로토콜이에요. 우리가 매일 사용하는 웹사이트들은 모두 이 프로토콜을 기반으로 동작하죠.

Go 언어는 강력한 표준 라이브러리를 제공하는데, 그 중에서도 net/http 패키지는 HTTP 클라이언트와 서버를 쉽게 구현할 수 있게 해줘요. 마치 레고 블록으로 집을 짓는 것처럼 간단하답니다! 🏠

3.1 간단한 HTTP 서버 만들기

먼저, 가장 기본적인 HTTP 서버를 만들어볼게요. 이 서버는 모든 요청에 대해 "안녕하세요, Go 웹 서버예요!"라고 응답할 거예요.


package main

import (
    "fmt"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "안녕하세요, Go 웹 서버예요!")
}

func main() {
    http.HandleFunc("/", handler)
    fmt.Println("서버가 8080 포트에서 실행 중입니다...")
    http.ListenAndServe(":8080", nil)
}
  

와! 정말 간단하죠? 😲 이 코드는 다음과 같이 동작해요:

  1. handler 함수는 모든 HTTP 요청을 처리합니다.
  2. http.HandleFunc("/")는 루트 경로("/")로 오는 모든 요청을 handler 함수로 보냅니다.
  3. http.ListenAndServe(":8080", nil)는 8080 포트에서 HTTP 서버를 시작합니다.

이 서버를 실행하고 브라우저에서 http://localhost:8080에 접속하면, "안녕하세요, Go 웹 서버예요!"라는 메시지를 볼 수 있어요. 축하합니다! 여러분의 첫 웹 서버가 탄생한 거예요! 🎉

3.2 동적 컨텐츠를 제공하는 HTTP 서버

이제 조금 더 재미있는 걸 해볼까요? 이번에는 방문자의 이름을 받아서 개인화된 인사말을 보내는 서버를 만들어볼게요.


package main

import (
    "fmt"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    name := r.URL.Query().Get("name")
    if name == "" {
        name = "익명의 방문자"
    }
    fmt.Fprintf(w, "안녕하세요, %s님! Go 웹 서버에 오신 것을 환영합니다!", name)
}

func main() {
    http.HandleFunc("/", handler)
    fmt.Println("서버가 8080 포트에서 실행 중입니다...")
    http.ListenAndServe(":8080", nil)
}
  

이 서버는 URL 파라미터로 전달된 이름을 읽어서 개인화된 인사말을 보내줘요. 예를 들어, http://localhost:8080/?name=철수로 접속하면 "안녕하세요, 철수님! Go 웹 서버에 오신 것을 환영합니다!"라고 응답할 거예요.

🚨 보안 주의: 실제 서비스에서는 사용자 입력을 그대로 출력하는 것은 위험할 수 있어요. XSS(Cross-Site Scripting) 공격의 위험이 있기 때문이죠. 항상 사용자 입력을 검증하고 이스케이프 처리를 해야 해요!

3.3 정적 파일 서빙하기

웹 서버의 중요한 기능 중 하나는 HTML, CSS, 자바스크립트, 이미지 등의 정적 파일을 제공하는 거예요. Go의 net/http 패키지는 이것도 아주 쉽게 할 수 있게 해줘요!


package main

import (
    "fmt"
    "net/http"
)

func main() {
    // 정적 파일 서빙을 위한 핸들러 등록
    fs := http.FileServer(http.Dir("static"))
    http.Handle("/static/", http.StripPrefix("/static/", fs))

    // 메인 페이지 핸들러
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "안녕하세요! 정적 파일은 /static/ 경로에서 확인하세요.")
    })

    fmt.Println("서버가 8080 포트에서 실행 중입니다...")
    http.ListenAndServe(":8080", nil)
}
  

이 서버는 static 디렉토리에 있는 파일들을 /static/ 경로로 서빙해요. 예를 들어, static/image.jpg 파일은 http://localhost:8080/static/image.jpg로 접근할 수 있어요.

와! 우리가 만든 서버가 점점 더 멋져지고 있어요, 그렇죠? 🌟

3.4 RESTful API 구현하기

현대 웹 개발에서 RESTful API는 정말 중요한 개념이에요. Go를 사용하면 RESTful API도 쉽게 구현할 수 있어요. 간단한 TODO 리스트 API를 만들어볼까요?


package main

import (
    "encoding/json"
    "fmt"
    "net/http"
)

type Todo struct {
    ID   int    `json:"id"`
    Task string `json:"task"`
}

var todos []Todo

func main() {
    todos = []Todo{
        {ID: 1, Task: "Go 언어 배우기"},
        {ID: 2, Task: "RESTful API 만들기"},
    }

    http.HandleFunc("/todos", handleTodos)
    fmt.Println("서버가 8080 포트에서 실행 중입니다...")
    http.ListenAndServe(":8080", nil)
}

func handleTodos(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case "GET":
        json.NewEncoder(w).Encode(todos)
    case "POST":
        var todo Todo
        json.NewDecoder(r.Body).Decode(&todo)
        todo.ID = len(todos) + 1
        todos = append(todos, todo)
        w.WriteHeader(http.StatusCreated)
        json.NewEncoder(w).Encode(todo)
    default:
        w.WriteHeader(http.StatusMethodNotAllowed)
    }
}
  

이 API는 다음과 같은 기능을 제공해요:

  • GET /todos: 모든 TODO 항목을 JSON 형식으로 반환
  • POST /todos: 새로운 TODO 항목을 추가

이제 curl이나 Postman 같은 도구를 사용해 API를 테스트해볼 수 있어요!

💡 실습 아이디어: 이 TODO 리스트 API를 확장해보는 건 어떨까요? 항목 삭제(DELETE), 수정(PUT) 기능을 추가해보세요. 더 나아가 데이터를 파일이나 데이터베이스에 저장하는 기능을 구현해볼 수도 있어요!

여기까지 오신 여러분, 정말 대단해요! 👏 우리는 이제 Go를 사용해 기본적인 웹 서버부터 RESTful API까지 구현할 수 있게 됐어요. 이런 기술들은 재능넷과 같은 플랫폼을 만드는 데에도 사용될 수 있어요. 예를 들어, 재능 거래를 위한 API를 만들거나, 사용자 프로필을 관리하는 서비스를 구현하는 데 이런 기술들이 활용될 수 있죠.

다음 섹션에서는 더 복잡한 네트워크 프로토콜을 구현해볼 거예요. WebSocket을 사용한 실시간 통신에 대해 알아볼 준비가 되셨나요? 우리의 Go 네트워크 프로그래밍 여행은 계속됩니다! 🚀

4. WebSocket으로 실시간 통신 구현하기: 대화의 새로운 차원 🚀

여러분, 지금까지 정말 잘 따라오셨어요! 👏 이제 우리는 더 흥미진진한 영역으로 들어갈 거예요. 바로 WebSocket을 이용한 실시간 통신이에요. 이건 마치 텔레파시처럼 즉각적으로 메시지를 주고받을 수 있게 해주는 기술이에요! 😮

4.1 WebSocket이란?

WebSocket은 클라이언트와 서버 사이에 지속적인 양방향 연결을 제공하는 프로토콜이에요. 일반적인 HTTP 통신과는 달리, 한 번 연결이 established되면 양쪽에서 자유롭게 데이터를 주고받을 수 있어요. 이는 실시간 채팅, 실시간 게임, 실시간 주식 정보 등을 구현하는 데 아주 유용하답니다.

💡 WebSocket vs HTTP: HTTP는 클라이언트가 요청을 보내고 서버가 응답하는 방식이에요. 반면 WebSocket은 연결이 유지되는 동안 양쪽에서 언제든 메시지를 보낼 수 있어요. 마치 전화 통화처럼요! 📞

4.2 Go로 WebSocket 서버 만들기

Go로 WebSocket 서버를 만들기 위해 우리는 gorilla/websocket 패키지를 사용할 거예요. 이 패키지는 WebSocket 구현을 매우 쉽게 만들어줘요.

먼저, 다음 명령어로 패키지를 설치해주세요:

go get github.com/gorilla/websocket

이제 간단한 WebSocket 에코 서버를 만들어볼게요. 이 서버는 클라이언트로부터 메시지를 받으면 그대로 다시 보내줄 거예요.


package main

import (
    "fmt"
    "net/http"
    "github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
    ReadBufferSize:  1024,
    WriteBufferSize: 1024,
}

func main() {
    http.HandleFunc("/echo", handleWebSocket)
    fmt.Println("서버가 8080 포트에서 실행 중입니다...")
    http.ListenAndServe(":8080", nil)
}

func handleWebSocket(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer conn.Close()

    for {
        messageType, p, err := conn.ReadMessage()
        if err != nil {
            return
        }
        if err := conn.WriteMessage(messageType, p); err != nil {
            return
        }
    }
}
  

이 코드는 다음과 같이 동작해요:

  1. upgrader.Upgrade()를 사용해 HTTP 연결을 WebSocket 연결로 업그레이드해요.
  2. 무한 루프 안에서 메시지를 읽고(conn.ReadMessage()) 그대로 다시 보내요(conn.WriteMessage()).

4.3 실시간 채팅 서버 구현하기

이제 우리의 지식을 활용해 간단한 실시간 채팅 서버를 만들어볼까요? 이 서버는 연결된 모든 클라이언트에게 메시지를 브로드캐스트할 거예요.


package main

import (
    "fmt"
    "net/http"
    "sync"
    "github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
    ReadBufferSize:  1024,
    WriteBufferSize: 1024,
}

type Client struct {
    conn *websocket.Conn
    send chan []byte
}

type ChatRoom struct {
    clients    map[*Client]bool
    broadcast  chan []byte
    register   chan *Client
    unregister chan *Client
}

func newChatRoom() *ChatRoom {
    return &ChatRoom{
        broadcast:  make(chan []byte),
        register:   make(chan *Client),
        unregister: make(chan *Client),
        clients:    make(map[*Client]bool),
    }
}

func (cr *ChatRoom) run() {
    for {
        select {
        case client := <-cr.register:
            cr.clients[client] = true
        case client := <-cr.unregister:
            if _, ok := cr.clients[client]; ok {
                delete(cr.clients, client)
                close(client.send)
            }
        case message := <-cr.broadcast:
            for client := range cr.clients {
                select {
                case client.send <- message:
                default:
                    close(client.send)
                    delete(cr.clients, client)
                }
            }
        }
    }
}

func (cr *ChatRoom) handleWebSocket(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        fmt.Println(err)
        return
    }

    client := &Client{conn: conn, send: make(chan []byte, 256)}
    cr.register <- client

    go client.writePump(cr)
    go client.readPump(cr)
}

func (c *Client) readPump(cr *ChatRoom) {
    defer func() {
        cr.unregister <- c
        c.conn.Close()
    }()

    for {
        _, message, err := c.conn.ReadMessage()
        if err != nil {
            break
        }
        cr.broadcast <- message
    }
}

func (c *Client) writePump(cr *ChatRoom) {
    defer c.conn.Close()

    for {
        select {
        case message, ok := <-c.send:
            if !ok {
                c.conn.WriteMessage(websocket.CloseMessage, []byte{})
                return
            }

            w, err := c.conn.NextWriter(websocket.TextMessage)
            if err != nil {
                return
            }
            w.Write(message)

            n := len(c.send)
            for i := 0; i < n; i++ {
                w.Write(<-c.send)
            }

            if err := w.Close(); err != nil {
                return
            }
        }
    }
}

func main() {
    chatRoom := newChatRoom()
    go chatRoom.run()

    http.HandleFunc("/ws", chatRoom.handleWebSocket)

    fmt.Println("채팅 서버가 8080 포트에서 실행 중입니다...")
    http.ListenAndServe(":8080", nil)
}
  

와! 우리가 만든 채팅 서버를 보세요. 😍 이 서버는 다음과 같이 동작해요:

  1. 새로운 클라이언트가 연결되면 ChatRoom에 등록돼요.
  2. 클라이언트가 메시지를 보내면, 그 메시지는 모든 연결된 클라이언트에게 브로드캐스트돼요.
  3. 클라이언트가 연결을 끊으면 ChatRoom에서 제거돼요.

🌟 실습 아이디어: 이 채팅 서버에 더 많은 기능을 추가해보는 건 어떨까요? 예를 들어, 사용자 이름을 추가하거나, 개인 메시지 기능을 구현하거나, 이모지 지원을 추가해볼 수 있어요!

이제 우리는 실시간으로 통신하는 강력한 채팅 서버를 가지게 됐어요. 이런 기술은 재능넷과 같은 플랫폼에서 실시간 메시징 시스템을 구현하는 데 사용될 수 있어요. 예를 들어, 재능 거래 중 실시간으로 판매자와 구매자가 대화를 나누는 기능을 만들 수 있죠.

WebSocket을 사용한 실시간 통신은 현대 웹 애플리케이션의 핵심 기술 중 하나예요. 여러분이 이 기술을 마스터하면, 훨씬 더 역동적이고 상호작용이 풍부한 웹 애플리케이션을 만들 수 있을 거예요!

다음 섹션에서는 우리가 배운 모든 것을 종합해서 더 복잡한 네트워크 애플리케이션을 만들어볼 거예요. 준비되셨나요? 우리의 Go 네트워크 프로그래밍 여행은 계속됩니다! 🚀

5. 종합 프로젝트: 미니 SNS 서비스 구현하기 🌟

축하합니다! 여러분은 이제 Go를 사용한 네트워크 프로그래밍의 기본을 모두 마스터했어요. 🎉 이제 우리의 지식을 총동원해서 미니 SNS 서비스를 만들어볼 거예요. 이 프로젝트는 HTTP 서버, RESTful API, WebSocket, 그리고 데이터베이스 연동까지 모든 것을 포함할 거예요!

5.1 프로젝트 구조

우리의 미니 SNS는 다음과 같은 기능을 가질 거예요:

  • 사용자 등록 및 로그인 (RESTful API)
  • 게시물 작성, 조회, 수정, 삭제 (RESTful API)
  • 실시간 채팅 (WebSocket)
  • 데이터베이스 연동 (PostgreSQL 사용)

5.2 데이터베이스 설정

먼저, PostgreSQL 데이터베이스를 설정하고 연결해볼게요. Go에서는 database/sql 패키지와 lib/pq 드라이버를 사용할 거예요.


go get github.com/lib/pq
  

데이터베이스 연결 코드:


package main

import (
    "database/sql"
    "fmt"
    "log"

    _ "github.com/lib/pq"
)

var db *sql.DB

func initDB() {
    var err error
    db, err = sql.Open("postgres", "user=username dbname=miniSNS sslmode=disable")
    if err != nil {
        log.Fatal(err)
    }

    if err = db.Ping(); err != nil {
        log.Fatal(err)
    }

    fmt.Println("데이터베이스 연결 성공!")
}
  

5.3 사용자 관리 API

사용자 등록과 로그인을 위한 API를 만들어볼게요.


package main

import (
    "encoding/json"
    "net/http"
)

type User struct {
    ID       int    `json:"id"`
    Username string `json:"username"`
    Password string `json:"password"`
}

func registerHandler(w http.ResponseWriter, r *http.Request) {
    var user User
    json.NewDecoder(r.Body).Decode(&user)

    // 실제 구현에서는 비밀번호를 해시화해야 해요!
    _, err := db.Exec("INSERT INTO users(username, password) VALUES($1, $2)", user.Username, user.Password)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(map[string]string{"message": "사용자 등록 성공"})
}

func loginHandler(w http.ResponseWriter, r *http.Request) {
    var user User
    json.NewDecoder(r.Body).Decode(&user)

    var dbUser User
    err := db.QueryRow("SELECT id, username, password FROM users WHERE username = $1", user.Username).Scan(&dbUser.ID, &dbUser.Username, &dbUser.Password)
    if err != nil {
        http.Error(w, "사용자를 찾을 수 없습니다", http.StatusUnauthorized)
        return
    }

    if user.Password != dbUser.Password {
        http.Error(w, "비밀번호가 일치하지 않습니다", http.StatusUnauthorized)
        return
    }

    json.NewEncoder(w).Encode(map[string]string{"message": "로그인 성공"})
}
  

5.4 게시물 관리 API

게시물을 작성, 조회, 수정, 삭제하는 API를 만들어볼게요.


package main

import (
    "encoding/json"
    "net/http"
    "strconv"
)

type Post struct {
    ID      int    `json:"id"`
    UserID  int    `json:"user_id"`
    Content string `json:"content"`
}

func createPostHandler(w http.ResponseWriter, r *http.Request) {
    var post Post
    json.NewDecoder(r.Body).Decode(&post)

    err := db.QueryRow("INSERT INTO posts(user_id, content) VALUES($1, $2) RETURNING id", post.UserID, post.Content).Scan(&post.ID)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(post)
}

func getPostHandler(w http.ResponseWriter, r *http.Request) {
    postID, _ := strconv.Atoi(r.URL.Query().Get("id"))

    var post Post
    err := db.QueryRow("SELECT id, user_id, content FROM posts WHERE id = $1", postID).Scan(&post.ID, &post.UserID, &post.Content)
    if err != nil {
        http.Error(w, "게시물을 찾을 수 없습니다", http.StatusNotFound)
        return
    }

    json.NewEncoder(w).Encode(post)
}

// updatePostHandler와 deletePostHandler도 비슷한 방식으로 구현할 수 있어요.
  

5.5 실시간 채팅 구현

WebSocket을 사용해 실시간 채팅 기능을 구현해볼게요.


package main

import (
    "fmt"
    "net/http"

    "github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
    ReadBufferSize:  1024,
    WriteBufferSize: 1024,
}

var clients = make(map[*websocket.Conn]bool)
var broadcast = make(chan Message)

type Message struct {
    Username string `json:"username"`
    Content  string `json:"content"`
}

func handleConnections(w http.ResponseWriter, r *http.Request) {
    ws, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer ws.Close()

    clients[ws] = true

    for {
        var msg Message
        err := ws.ReadJSON(&msg)
        if err != nil {
            delete(clients, ws)
            break
        }
        broadcast <- msg
    }
}

func handleMessages() {
    for {
        msg := <-broadcast
        for client := range clients {
            err := client.WriteJSON(msg)
            if err != nil {
                fmt.Printf("error: %v", err)
                client.Close()
                delete(clients, client)
            }
        }
    }
}
  

5.6 모든 것을 하나로 합치기

이제 우리가 만든 모든 컴포넌트를 하나의 애플리케이션으로 합쳐볼게요.


package main

import (
    "fmt"
    "net/http"
)

func main() {
    initDB()

    http.HandleFunc("/register", registerHandler)
    http.HandleFunc("/login", loginHandler)
    http.HandleFunc("/post", createPostHandler)
    http.HandleFunc("/post/get", getPostHandler)
    http.HandleFunc("/ws", handleConnections)

    go handleMessages()

    fmt.Println("서버가 8080 포트에서 실행 중입니다...")
    http.ListenAndServe(":8080", nil)
}
  

와! 우리가 만든 미니 SNS 서비스를 보세요! 🎉 이 서비스는 사용자 관리, 게시물 관리, 실시간 채팅 기능을 모두 갖추고 있어요. 물론 이 코드는 기본적인 구조만을 보여주는 것이고, 실제 서비스를 위해서는 더 많은 기능과 보안 처리가 필요할 거예요.

🚀 다음 단계: 이 프로젝트를 더 발전시켜보는 건 어떨까요? 예를 들어, 사용자 인증을 위한 JWT 토큰 구현, 파일 업로드 기능 추가, 프론트엔드 개발 등을 해볼 수 있어요!

여러분, 정말 대단해요! 👏 우리는 Go를 사용해 기본적인 네트워크 프로그래밍부터 복잡한 웹 서비스까지 구현해봤어요. 이런 기술들은 재능넷과 같은 플랫폼을 만드는 데 실제로 사용되는 기술들이에요. 여러분이 배운 이 기술들을 활용해 자신만의 독특한 서비스를 만들어보는 건 어떨까요?

Go 언어와 네트워크 프로그래밍의 세계는 정말 넓고 깊어요. 우리가 배운 것은 그 중 일부에 불과해요. 계속해서 학습하고, 실험하고, 새로운 것을 만들어보세요. 여러분의 상상력이 곧 한계예요!

Go의 세계에서 여러분의 모험은 이제 막 시작됐어요. 앞으로 어떤 멋진 프로젝트를 만들어낼지 정말 기대되네요. 화이팅! 🚀