이번 포스트에서는 TCP 연결과 해제에 대해 구현한 Go 코드를 살펴보겠습니다.
먼저, TCP 연결과 해제 과정은 다음과 같습니다.


(1) 기본 설정: TCP 서버 생성
먼저, net.Listen 함수를 사용하여 로컬 호스트의 랜덤 포트에서 리스닝을 시작하는 TCP 서버를 생성합니다. 이 서버는 들어오는 연결을 수락하고 처리할 준비를 합니다.
여기서, 127.0.0.1:는 로컬 호스트의 랜덤 포트를 의미합니다.
(2) 고루틴을 사용한 연결 수락
여기서는 go 키워드를 사용하여 고루틴을 시작하고, 무한 루프를 통해 계속해서 들어오는 연결을 수락합니다.
TCP는 1:1 연결을 기반으로 하는 프로토콜이지만, 하나의 서버가 여러 클라이언트와 동시에 연결을 유지하는 것이 가능합니다.
(3) 클라이언트 연결과 TCP Handshakes
TCP 연결은 "3-way handshake"를 통해 설정됩니다. 클라이언트는 net.Dial을 사용하여 서버에 연결을 시도하며, 이는 SYN 패킷을 전송하는 것으로 시작됩니다.
(4) ~ (5) 클라이언트가 작업을 완료하면, "4-way handshake"를 통해 연결을 종료합니다. 클라이언트는 conn.Close()를 호출하여 FIN 패킷을 전송하고 연결 종료를 시작합니다. 이후, 서버 또한 클라이언트와의 listener.Close()를 호출하여 FIN 패킷을 전송하여 연결을 종료하고자 합니다.
package main
import (
"io"
"log"
"net"
)
func main() {
// (1) 랜덤 포트에 리스너 생성 => TCP 서버 생성
listener, err := net.Listen("tcp", "127.0.0.1:")
if err != nil {
log.Fatal("error listening on socker:", err)
}
// 'done' 채널은 고루틴이 완료될 때 신호를 보내기 위해 사용
done := make(chan struct{})
// (2) 고루틴으로 연결 수락
go func() {
defer func() { done <- struct{}{} }()
// 무한 루프를 통해 계속해서 들어오는 연결 수락
for {
conn, err := listener.Accept()
if err != nil {
log.Println("error accepting connection:", err)
return
}
// 새 고루틴에서 각 연결 처리
go func(c net.Conn) {
defer func() {
c.Close()
done <- struct{}{}
}()
// 1024 바이트의 버퍼를 생성하고, 클라이언트로부터 데이터를 읽음
buf := make([]byte, 1024)
for {
n, err := c.Read(buf)
if err != nil {
if err != io.EOF {
log.Println("error reading from c:", err)
}
return
}
log.Printf("received: %q", buf[:n])
}
}(conn)
}
}()
// (3) 클라이언트 연결 생성 및 종료 - SYN 전송(Client to Server) - 3 Way Handshaking 시작
conn, err := net.Dial("tcp", listener.Addr().String())
if err != nil {
log.Fatal("error creating connection: ", err)
}
conn.Close() // (4) FIN 전송 (Client to Server) - 4 Way Handshaking 시작
// 리스너 및 고루틴 종료
<-done
listener.Close() // (5) FIN 전송(Server to Client) - 4 Way Handshaking 3번째 과정
<-done
}