layered

5 TCP 기반 서버/클라이언트 2 본문

네트워크/열혈 TCP IP 소켓 프로그래밍

5 TCP 기반 서버/클라이언트 2

스윗푸들 2023. 5. 1. 16:04

5-1 에코 클라이언트의 완벽 구현!


TCP는 전송되는 데이터의 경계가 없다. 스트림을 구성해서 전달하기 때문이다.

따라서 한 번의 send로 데이터를 보낼 때 내부적으로는 여러 개의 패킷으로 쪼개서 전달할 수도 있고, 이는 recv를 통해서 받을 때에도 마찬가지이다.

 

예를 들면 [안녕하세요!]를 보냈는데 실제로는 [안] [녕하] [세요!]로 쪼개서 보낼 수도 있다는 것이다.

 

이런 특성으로 인해 아직 수신 버퍼에 원하는 만큼의 데이터가 쌓이지 않았는데 그대로 읽어 버릴 수도 있기 때문에, 반복문을 돌려서 보낸 데이터의 길만큼 받도록 해야 한다.

다음과 같이 코드를 수정할 수 있다.

 

int send_len = send(serv_sock, message, strlen(message), 0); // 보낸 데이터 길이 저장
int recv_len = 0;
int recv_cnt = 0;
while (recv_len < send_len) {
	recv_cnt = recv(serv_sock, &message[recv_len], BUF_SIZE - 1, 0); // 데이터를 이어서 저장해야 하므로 recv_len 위치의 주소를 넘겨줌
    if (recv_cnt == 0 || recv_cnt == SOCKET_ERROR)
        break;
    recv_len += recv_cnt;
}

 

 

메모

이렇게 반복문을 안 돌고 수신할 데이터의 크기를 send_len으로 하고, flags를 MSG_WAITALL로 설정해도 될 것 같다. 이러면 send_len만큼의 데이터가 수신 버퍼에 도착해서 응용프로그램 버퍼에 복사할 때까지 기다리기 때문이다.

(하지만 MSG_WAITALL은 사용하는 데 제한이 걸려 있다고 한다.)

 

그런데 이렇게 데이터를 쪼개서 전송하고 받는 게 문제라면 왜 서버는 코드를 수정하지 않아도 되는지 모르겠다. 그리고 왜 전송할 때에는 루프를 돌지 않는지도.

 

음... 데이터를 보낼 때에는 지정한 크기만큼의 데이터를 확실하게 보낸다. 그러니까 데이터를 못 보내는 경우는 없다.

다만 send가 바이트 크기를 반환하는 것과 실제 데이터가 전송되는 것 사이에는 차이가 있다(정확하게 말하자면 send의 반환 시점은 보낼 데이터가 모두 송신 버퍼에 저장되는 때이다). 크기를 반환한다고 해서 데이터의 전송이 완료된 것은 아니라는 의미이다.

 

하지만 받는 건 다르다. recv에 지정하는 데이터의 크기는 어디까지나 최대 크기이기 때문에, 수신 버퍼에 데이터가 있기만 하다면 그냥 읽어 버린다.

그래서 코드 상으로는 send가 반환이 되어서 다음 라인의 recv가 실행되었는데, 네트워크 상으로는 아직 전송 중이어서 일단은 버퍼에 들어 있는 데이터만큼만 읽게 되는 것이다.

 

책에서 다루는 예제는 클라이언트 송신 -> 서버 수신 -> 서버 송신 -> 클라이언트 수신의 순서로 데이터를 다룬다. 그러니 서버에서는 받을 데이터의 크기를 알 수가 없어서 반복문을 돌 수가 없다.

하지만 클라이언트는 자기가 보낸 만큼의 데이터를 다시 돌려받으므로 받을 데이터의 크기를 알고 있기에 반복문을 돌 수 있는 것이다.

 

while ((str_len = read(clnt_sock, message, BUF_SIZE)) != 0)
    write(clnt_sock, message, str_len);

 

책에 나온 서버의 코드는 이렇다. 보면 데이터가 들어오는 대로 바로 읽어서 클라이언트에게 보내는 걸 알 수 있다.

클라이언트처럼 반복문을 돌지 않는 이유는 일단 데이터의 크기를 몰라서인 것도 있고, 데이터의 경계가 없으므로(= 데이터가 서로 어떻게 연결되는지 알지 못하므로) 그냥 순서대로 보내기만 하면 되기 때문이다.

그래서 이 부분은 데이터를 알고 있는 클라이언트에서, 보낸 데이터만큼만 받도록 함으로써 처리하게 되는 것이다.

 

에코 클라이언트 이외의 경우에는? 어플리케이션의 프로토콜의 정의

대부분은 수신할 데이터의 크기를 이전에 파악하지 못하는 경우가 많다. 이때 필요한 것이 어플리케이션 프로토콜의 정의이다.

간단하게 말하자면 데이터의 끝을 파악할 수 있도록 약속해 두자는 것이다.

 

메모

간단한 계산기를 구현하는 예제가 있는데, 이건 char/int를 좀 더 알아보고 해야겠다.

 

5-2 TCP의 이론적인 이야기!


이전에 언급했듯이 각각의 소켓에는 송수신 버퍼가 존재하고, recv/send는 이 버퍼에 접근해 데이터를 읽고 쓰는 함수이다.

그리고 TCP에서는 슬라이딩 윈도우(Sliding Window)라는 프로토콜을 이용해 버퍼를 초과하는 데이터의 전송이 이루어지지 않도록 한다.

 

TCP의 내부 동작원리

소켓이 생성되고 종료되기까지의 과정을 크게 세 가지로 나누면 다음과 같다.

 

1 상대 소켓과의 연결: 3-way handshaking

2 데이터 송수신

3 연결 종료: 4-way handshaking