25.3. 프로그램언어 고(Go)의 소켓 프로그래밍

프로그램언어 고(Go)의 소켓 생성과 관리

고(Go) 프로그래밍 언어의 소켓 생성과 관리에 대해 친절하고 상세하게 설명드리겠습니다.


// 서버 소켓 생성
listen, err := net.Listen("tcp", ":8080")
if err != nil {
    log.Fatal(err)
}

// 클라이언트 접속 대기
for {
    conn, err := listen.Accept()
    if err != nil {
        log.Fatal(err)
    }
    
    // 소켓 처리 goroutine 시작
    go handle(conn) 
}

func handle(conn net.Conn) {
    buf := make([]byte, 512)
    for {
        n, err := conn.Read(buf)
        if err != nil {
            return
        }
        
        // 데이터 수신 처리
        process(buf[:n]) 
    }
}

고(Go)에서 net 패키지를 사용하여 TCP 소켓을 생성합니다.
Listen 함수로 지정한 주소와 포트를 리슨하여 서버 소켓을 오픈합니다.
Accept 메서드로 클라이언트의 접속을 기다리다가 접속하면 새로운 소켓을 반환합니다.
반환된 소켓을 별도의 고루틴으로 처리합니다.

서버 소켓을 오픈하고 접속 대기하는 것과 클라이언트 처리를 분리하면 다수의 클라이언트를 효과적으로 처리할 수 있습니다.
각 클라이언트마다 별도의 고루틴에서 소켓을 처리하기 때문입니다.

소켓에서 데이터를 받으려면 conn.Read 메서드를 사용합니다.
수신 버퍼와 읽은 바이트수, 에러를 반환하는데, 에러가 nil이 아니면 소켓이 닫혔다고 판단하고 처리를 종료합니다.

이 외에도 소켓 타임아웃 설정, 쓰기, 닫기 등의 작업이 가능합니다.
타임아웃은 SetReadDeadline, SetWriteDeadline 메서드로 처리합니다.
쓰기를 위해서는 conn.Write 메서드, 닫기 위해서는 conn.Close 메서드를 사용합니다.

고(Go)의 풍부한 표준 라이브러리 덕분에 소켓 프로그래밍이 간단해지고 생산성이 높아집니다.
지속적으로 공부해 고(Go) 개발 실력을 키워나가시기 바랍니다.

프로그램언어 고(Go)에서의 소켓 통신 절차


package main

import (
    "fmt"
    "net"
)

func main() {

    // 서버 주소 지정
    serverAddr, err := net.ResolveTCPAddr("tcp", ":8080")
    if err != nil {
        panic(err)
    }

    // TCP 리스너 시작
    listener, err := net.ListenTCP("tcp", serverAddr)
    if err != nil {
        panic(err)
    }

    // 클라이언트 접속 대기
    for {
        conn, err := listener.Accept()
        if err != nil {
            continue
        }

        // 클라이언트와 데이터 통신
        go handleClient(conn)
    }
}

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

    buf := make([]byte, 512)
    for {
        // conn에서 데이터 읽기
        n, err := conn.Read(buf)
        if err != nil {
            return
        }

        // 읽은 데이터 출력
        data := buf[:n]
        fmt.Println("Received:", string(data))

        // 데이터 쓰기
        conn.Write([]byte("Message received"))
    }
}

고(Go)언어에서 소켓 통신을 하기 위해서는 먼저 net 패키지를 import 해야 합니다.

소켓 서버를 만들기 위해서는 우선 TCP 주소를 지정합니다. 여기서는 “:8080″로 포트 8080번을 사용합니다.

ResolveTCPAddr 함수로 TCPAddr 구조체를 만듭니다.

TCP 리스너를 시작하기 위해 ListenTCP 함수를 호출합니다. serverAddr과 전달합니다.

이로써 서버가 시작되고 클라이언트의 접속을 기다리게 됩니다.

Accept 함수에서 접속 대기를 하다가 클라이언트가 접속하면 새로운 conn을 반환합니다.

반환된 conn 값에서 클라이언트와 데이터 통신을 할 수 있습니다.

여기서는 handleClient 함수를 고루틴으로 실행해주어 동시에 여러 클라이언트를 처리할 수 있게 했습니다.

handleClient 함수 내에서는 주고받은 데이터를 읽고 쓰는 작업을 합니다.

Read 함수로 데이터를 읽어오고 Write 함수로 데이터를 보냅니다.

이와 같은 일련의 과정으로 고(Go)언어를 사용한 소켓 통신이 이루어지게 됩니다.

주석을 달아 절차를 자세히 설명했고, 실제 예제 코드도 포함했습니다.

코드와 주석을 통해 소켓 통신 절차를 보다 쉽고 상세하게 이해하실 수 있으리라 믿습니다.

프로그램언어 고(Go)의 다중 소켓 관리

Go 언어의 다중 소켓 관리는 goroutine과 channel을 활용하여 구현할 수 있습니다.

goroutine을 사용하여 각 소켓에 대한 처리를 별도의 goroutine에서 수행하고, channel을 사용하여 각 goroutine 간에 데이터를 전송합니다.


package main

import (
    "fmt"
    "net"
)

func handleConn(conn net.Conn) {
    // conn과 관련된 처리 
    fmt.Println("Handling new connection")
    // ...
}

func main() {
    ln, _ := net.Listen("tcp", ":8080")
    
    for {
        conn, err := ln.Accept()
        if err != nil {
            // 오류 처리
            continue 
        }
        go handleConn(conn) // goroutine에서 처리
    }
}

위의 예제에서 Accept 메서드를 통해 새로운 소켓 연결이 수립되면 handleConn 함수를 별도의 goroutine에서 호출하여 해당 소켓을 처리합니다.

이렇게 함으로써 메인 goroutine가 소켓 수락 대기 상태를 유지한 채 각 소켓마다 독립적으로 처리가 이루어지게 됩니다.

channel을 사용하면 각 goroutine 간에 데이터를 주고받을 수도 있습니다.


package main

import "fmt"

func handle(ch chan string) {
    msg := <- ch // channel로부터 데이터 수신
    fmt.Println(msg)
}

func main() {
    ch := make(chan string)
    
    go handle(ch) // 별도 goroutine에서 실행

    ch <- "Hello" // channel을 통해 데이터 전송
}

위와 같이 channel ch를 정의하고 이를 통해 메인 goroutine와 handle goroutine 간에 데이터를 주고받을 수 있습니다.

이를 통해 Go 언어의 다중 소켓도 동기적으로 관리가 가능합니다.

goroutine과 channel의 조합을 통해 비동기 처리도 쉽게 구현할 수 있으며, 복잡한 네트워크 프로그램도 효과적으로 개발할 수 있습니다.

프로그램언어 고(Go)에서의 비동기 소켓 프로그래밍

프로그램언어 고(Go)에서 비동기 소켓 프로그래밍은 네트워크 통신을 위해 주로 사용합니다.

비동기 소켓 프로그래밍을 사용하면 서버에서 클라이언트의 요청을 비동기적으로 처리할 수 있습니다. 즉, 요청이 오면 그 요청을 별도의 고루틴에서 처리하여 메인 고루틴이 블로킹되지 않고 계속해서 다른 요청을 받을 수 있습니다.

예를 들어 서버에서 클라이언트의 요청 데이터를 파일로 저장하는 작업이 필요하다고 할 때, 동기적으로 처리하면 요청한 클라이언트는 응답을 받을 때까지 기다려야 하지만 비동기적으로 하면 파일 저장 작업을 별도의 고루틴에서 처리함으로써 요청한 클라이언트에게는 바로 응답을 주고, 메인 고루틴에서는 계속 다른 클라이언트의 요청을 처리할 수 있습니다.

고(Go)에서 비동기 소켓 프로그래밍을 하기 위해서는 net 패키지의 ListenAndServe, ListenAndServeTLS 함수를 사용하여 소켓을 생성하고, HandleFunc 함수로 라우터와 핸들러를 등록합니다.

그리고 핸들러 함수 내부에서 별도의 고루틴을 실행하는 go 키워드를 사용하여 비동기로 처리할 수 있습니다.

아래는 간단한 HTTP 서버의 비동기 소켓 프로그래밍 예제입니다.


package main

import (
    "fmt"
    "net/http"
    "time"
)

// 비동기로 처리할 핸들러 함수
func handler(w http.ResponseWriter, r *http.Request) {

    // 별도 고루틴을 실행하여 비동기 처리
    go func() {
        fmt.Println("Received request")
        time.Sleep(5 * time.Second) // 5초 대기 
        fmt.Println("Done processing") 
    }()

    // 즉시 응답
    w.Write([]byte("Processed"))
}

func main() {

    http.HandleFunc("/process", handler)

    http.ListenAndServe(":8080", nil)
}

위 예제를 보면 handler 함수 내에서 별도의 고루틴을 실행하여 5초 동안 대기하는 작업을 비동기적으로 처리합니다.

메인 고루틴에서는 바로 "Processed"라는 응답을 보내 클라이언트의 요청에 대한 처리를 완료하고, 별도 고루틴을 통해 나머지 작업을 비동기적으로 진행할 수 있습니다.

이러한 방식으로 서버의 처리 시간이 오래 걸리는 작업들을 별도의 고루틴에서 동시에 처리할 수 있기 때문에, 전체 서버의 성능과 확장성을 높일 수 있습니다.

프로그램언어 고(Go)의 소켓 프로그래밍에 대한 보안조치


// TLS 설정
tlsConfig := &tls.Config{
    MinVersion:               tls.VersionTLS12,
    CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
    PreferServerCipherSuites: true,
    CipherSuites: []uint16{
        tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
        tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
        tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
        tls.TLS_RSA_WITH_AES_256_CBC_SHA,
    },
}

// TLS Listener 시작 
l, err := tls.Listen("tcp", ":443", tlsConfig)
if err != nil {
    log.Fatal(err)
}
defer l.Close()

// 소켓 처리
for {
    conn, err := l.Accept()
    if err != nil {
        log.Println(err)
        continue
    }
    handleConnection(conn)
}

Go언어의 소켓 프로그래밍 보안을 위해서는 주로 TLS(Transport Layer Security)를 사용하여 암호화 통신을 구현합니다.

위의 예제코드는 TLS 1.2 버전 이상을 사용하도록 설정하고, 안전한 암호 제품군만 허용하도록 CipherSuites를 구성합니다. 또한 서버 암호 제품군을 우선으로 선택하도록 설정합니다.

이를 통해 많은 보안 이슈를 사전에 차단할 수 있습니다. 특히 CipherSuite 설정은 중요한데, 안전하지 않은 암호 제품군을 허용하면 MITM(Man In The Middle) 공격 등의 위험성이 커집니다.

서버 인증서도 제대로 구성하는 것이 중요합니다. 인증서 관리는 복잡하지만,
인증 기관으로부터 발급받은 유효한 인증서를 사용하는 것이 안전합니다.

전송 상의 데이터 무결성도 확보를 위해 TLS 기능을 사용하는 것이 좋습니다.

송수신되는 모든 데이터가 변조되지 않았다는 것을 확인할 수 있습니다.

보안 취약점 발견 시 최신 보안 패치를 반영하고, 항상 최신 TLS 버전으로 업그레이드하는 것도 보안 확보에 중요합니다.

주기적으로 취약점 진단을 실시하여 개선 포인트를 발굴하는 등 관리 효율화가 필요합니다.

Leave a Comment