728x90

각 언어마다 tcp소켓정도는 쓸줄 아아야할 것이다.

일단 tcp가 뭔지, 그리고 어떤원리로 동작하는지, 어떠한 이슈가 있는지도 아주아주아주 중요한 이야기이다.

그러나 필자는 여기서 논하진 않겠다.

그런 tcp에 관한 심도깊은 내용을 준비하고 있는데 시간 관계상 못올릴 수도있고 올릴 수도 있고...

이번에는 양방향으로 tcp를 사용하는 법에 대해서 알아보도록 하자.


우리가 작성할 코드는 클라이언트가 입력받아서 서버로 값을 던지고 서버는 그 값을 그대로 클라이언트에게 돌려줄 것이다.


server


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([]byte, 4096)
for {
n, err := conn.Read(recvBuf)
if nil != err {
if io.EOF == err {
log.Println(err);
return
}
log.Println(err);
return
}
if 0 < n {
data := recvBuf[:n]
log.Println(string(data))
_, err = conn.Write(data[:n])
if err != nil {
log.Println(err)
return
}
}
}
}

server의 전문은 위와 같다.

go를 읽다보면 에러처리부분때문에 필자는 좀 읽기 짜증난다는 인상을 받는다.

코드가 살짝 길어보이지만 여느때와 마찬가지로 사실 조금씩 읽어보면 어려운 내용은 없다.


l, err := net.Listen("tcp", ":8000")

소켓을 열어준다.


defer l.Close()

메인 프로세스가 종료되면 소켓이 종료되게 설정한다.


for {
conn, err := l.Accept()
if nil != err {
log.Println(err);
continue
}
defer conn.Close()
go ConnHandler(conn)
}

그 다음 소켓에 연결하고 그 연결을 우리가 만든 connHandler에 인자로 주고 고루틴으로 돌린다.

마찬가지로 연결은 메인프로세스종료시 종료되도록 해준다.


func ConnHandler(conn net.Conn) {
recvBuf := make([]byte, 4096)
for {
n, err := conn.Read(recvBuf)
if nil != err {
if io.EOF == err {
log.Println(err);
return
}
log.Println(err);
return
}
if 0 < n {
data := recvBuf[:n]
log.Println(string(data))
_, err = conn.Write(data[:n])
if err != nil {
log.Println(err)
return
}
}
}
}

이 코드는 조금 기니까 잘라서 보겠다.


n, err := conn.Read(recvBuf)

client가 값을 줄때까지 blocking되어 대기하다가 값을 주면 읽어들인다.


if 0 < n {
data := recvBuf[:n]
log.Println(string(data))
_, err = conn.Write(data[:n])
if err != nil {
log.Println(err)
return
}
}

client가 던진 값을 다시 client에게 던진다.


이렇게 보니까 코드가 별거 없지 않은가??

client


서버만큼이나 클라이언트도 쉽다.


package main

import (
"fmt"
"log"
"net"
"time"
)

func main() {
conn, err := net.Dial("tcp", ":8000")
if nil != err {
log.Println(err)
}

go func() {
data := make([]byte, 4096)

for {
n, err := conn.Read(data)
if err != nil {
log.Println(err)
return
}

log.Println("Server send : " + string(data[:n]))
time.Sleep(time.Duration(3) * time.Second)
}
}()

for {
var s string
fmt.Scanln(&s)
conn.Write([]byte(s))
time.Sleep(time.Duration(3) * time.Second)
}
}

전문은 위와 같으며 마찬가지로 나눠서 설명하겠다.


conn, err := net.Dial("tcp", ":8000")

클라이언트가 서버와 연결을 시도한다.


go func() {
data := make([]byte, 4096)

for {
n, err := conn.Read(data)
if err != nil {
log.Println(err)
return
}

log.Println("Server send : " + string(data[:n]))
time.Sleep(time.Duration(3) * time.Second)
}
}()

고루틴을 생성해서 서버가 값을 던질때까지 기다렸다가 던지면 값을 출력한다.


for {
var s string
fmt.Scanln(&s)
conn.Write([]byte(s))
time.Sleep(time.Duration(3) * time.Second)
}

사용자의 입력이 들어올때까지 blocking했다가 입력을 마치면 서버로 전송한다.


자 이제 테스트 해보자.



tcp통신이 되는걸 확인할 수 있다.


+ Recent posts