Notice
Recent Posts
Recent Comments
Link
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
Archives
Today
Total
관리 메뉴

Code Habit

go ) 간단한 TCP echo 서버/클라이언트 예제 본문

카테고리 없음

go ) 간단한 TCP echo 서버/클라이언트 예제

코드베어 2020. 9. 1. 06:42

go를 이용하여 간단한 tcp echo 서버/클라이언트를 만들어 보겠다.

 

server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// main.go
package main
 
import (
    "io"
    "log"
    "net"
)
 
func main() {
    l, err := net.Listen("tcp"":8000")
    if nil != err {
        log.Println(err)
    }
    defer l.Close()
 
    for {
        conn, err := l.Accept()
        if nil != err {
            log.Println(err)
            continue
        }
        defer conn.Close()
 
        go ConnHandler(conn)
    }
}
 
func ConnHandler(conn net.Conn) {
    recvBuf := make([]byte4096)
    for {
        n, err := conn.Read(recvBuf)
        if nil != err {
            if io.EOF == err {
                log.Println("err(eof):", err)
                break
            }
            log.Println("err:", err)
            break
        }
 
        if 0 < n {
            data := recvBuf[:n]
            log.Println(string(data))
            _, err = conn.Write(data[:n])
            if err != nil {
                log.Println("err:", err)
                break
            }
        }
    }
}

 

  • 11) l, err = net.Listen("tcp", ":8000") : 로컬에 8000번 포트로 tcp 프로토콜 방식의 서버를 연다. 
  • 18) conn, err= l.Accept() : 클라이언트 연결을 대기한다. 연결되면 연결 정보가 conn에 반환되며 이는 c의 socket처럼 쓰인다.
  • 32) n, err := conn.Read(recvBuf) : 입력버퍼에 데이터가 들어올 때까지 대기한다. 즉, 클라이언트의 전송을 대기한다. 데이터를 읽으면 recvBuf에 n데이터 길이만큼 []byte 타입으로 데이터가 채워진다.
  • 45) _, err = conn.Write(data[:n]) : 받은 데이터를 출력 후 그대로 클라이언트로 전송한다. ( echo 기능 )

 

 

client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// client.go
package main
 
import (
    "fmt"
    "log"
    "net"
)
 
func main() {
    conn, err := net.Dial("tcp"":8000")
    if nil != err {
        log.Println("err:", err)
        return
    }
 
    go func() {
        data := make([]byte4096)
        for {
            n, err := conn.Read(data)
            if err != nil {
                log.Println(err)
                break
            }
 
            log.Println("Server send:" + string(data[:n]))
        }
    }()
 
    for {
        var s string
        fmt.Scanln(&s)
        conn.Write([]byte(s))
    }
}

 

  • 11) conn, err := net.Dial("tcp", ":8000") : localhost에 8000번 포트로 tcp 방식으로 연결 요청한다. 연결되면 연결 정보가 conn에 반환된다.
  • 20) n, err := conn.Read(data) : 입력버퍼에 데이터가 들어오기를 기다린다. 해당 함수는 go 루틴 내 반복문내에 위치해 데이터가 들어올때마다 읽어들일 수 있다.
  • 33) conn.Write([]byte(s)) : 사용자에게 입력받은 데이터 (s)를 tcp 출력버퍼에 보낸다. 즉, server로 전송한다. 

메시지를 입력하면 그대로 돌려 받는 간단한 에코 예제를 작성해 보았다. 이렇게 go 에서는 c에 비해 비교적 간단하게 tcp 서버/클라이언트를 구현할 수 있다.  서버를 열고, 접속하는 함수 및 인자들이 보다 깔끔하고 직관적이며 go 루틴을 사용하여 멀티로 접속하는 클라이언트들을 보다 쉽게 처리할 수 있다.

 

위 예제는 기본 예제로 몇가지 문제점이 존재하는데 그 중 가장 큰 문제는 주고 받는 데이터의 크기를  알 수 없다는 것이다. 예제를 돌려보면 워낙 데이터 양이 작기도 하고 local에서 테스트를 하기 때문에 별 문제 없겠지만 실제 서버를 돌리다보면 부하가 발생시 클라이언트에서 보내주는 데이터를 한번에 다 받지 못할 수도 있다. 이렇게 되면 데이터가 짤릴 수 있어 의도한대로 프로그램이 동작하지 않을 수 있다.

 

이 예제에서는 go에서 tcp 프로토콜을 어떻게 다루는지 기본적인 부분을 살펴본 것이므로 다음 포스팅에서 위 문제를 해결할 수 있는 방법과 코드를 제시해보겠다.