데이터 전송 함수는 크게 데이터를 보내는 함수와 데이터를 받는 함수로 구분할 수 있다.
가장 기본이 되는 함수는 send()와 recv()이며, 그 밖에 WSA*() 형태의 확장 함수가 존재한다.

소켓 데이터 구조체란?
- TCP 소켓과 연간된 데이터 구조체로서, 각각 자신과 상대방의 IP 주소와 포트 번호 외에 데이터 송수신 버퍼가 있다.
송신 버퍼는 데이터를 전송하기 전에 임시로 저장해두는 영역이고,
수신 버퍼는 받은 데이터를 애플리케이션이 처리하기 전까지 임시로 저장해두는 영역이다.
송신 버퍼와 수신 버퍼를 통틀어서 소켓 버퍼라 부르며, send(), recv() 함수는 소켓을 통해 간접적으로 소켓 버퍼를
접근할 수 있도록 만든 함수라고 볼 수 있다.


데이터 전송 함수를 사용할 때는 하부 프로토콜의 특성을 잘 알고 있어야 하는데, TCP 프로토콜은 애플리케이션이 보낸
데이터의 경계를 구분하지 않는다는 특징이 있다. 예를 들어보면,
클라이언트가 100,200,300 바이트 데이터를 차례로 보낼 경우,
서버가 100,200,300 바이트의 경계를 구분하지 못하고 350, 250 바이트 데이터를 읽을 수 있다.
따라서  TCP 서버/클라이언트를 작성할 때는 데이터 경계 구분을 위한 상호 약속이 필요하며,
이를 애플리케이션 수준에서 처리해야만 한다.


send() 함수
- 애플리케이션 데이터를 송신 버퍼에 복사함으로써 궁극적으로 하부 프로토콜(TCP/IP)에 의해 데이터가 전송되도록 한다.
send() 함수는 데이터 복사가 성공적으로 이루어지면 바로 리턴하므로, send() 함수가 성공했다고 실제 데이터 전송이
완료된 것은 아니다.

int send(
SOCKET s,
const char* buf,
int len,
int flags
);

SOCKET s,
- 통신할 대상과 연결된 소켓이다.

const char* buf,
- 보낼 데이터를 담고 있는 애플리케이션 버퍼의 주소다.

int len,
- 보낼 데이터 크기(바이트) 이다.

int flags
- send() 함수의 동작을 바꾸는 옵션이며, 보통 0을 사용한다.
- 드물게 MSG_DONTROUTE 와 MSG_OOB 을 사용하는 경우도 있다.

send() 함수는 첫번째 인자로 사용한 소켓의 특성에 따라 다음과 같이 두 종류의 성공적인 리턴을 할 수 있다.

1. 블로킹 소켓
- 지금까지 생성한 모든 소켓은 블로킹 소켓인데, 블로킹 소켓에 대해 send() 함수를 호출할 때,
송신 버퍼의 여유 공간이 send() 함수의 세 번째 인자인 len 보다 작을 경우 해당 프로세스는 대기 상태가 된다.
송신 버퍼에 충분한 공간이 생기면 프로세스는 깨어나고, len 크기만큼 데이터 복사가 이루어진 후 send() 함수가 리턴한다.
이 경우 send() 함수의 리턴 값은 len과 같게 된다.

2. 넌블로킹 소켓
- ioctlsocket() 함수를 이용하면 블로킹 소켓을 넌블로킹 소켓으로 바꿀 수 있다.
넌블로킹소켓에 대해 send() 함수를 호출하면, 송신 버퍼의 여유 공간만큼 데이터를 복사한 후 실제 복사한 데이터 바이트
수를 리턴한다. 이 경우 send() 함수의 리턴값은 최소 1, 최대 len이 된다.








recv() 함수
- 수신 버퍼에 도착한 데이터를 애플리케이션 버퍼로 복사하는 역활을 하는 함수다.

int recv(
SOCKET s,
char* buf,
int len,
int flags
);

SOCKET s,
- 통신할 대상과 연결된 소켓이다.

char* buf,
- 받은 데이터를 저장할 애플리케이션 버퍼의 주소다.

int len,
- 수신 버퍼로부터 복사할 최대 데이터 크기(바이트) 이다. 이 값은 buf가 가리키는 버퍼의 크기보다 크면 안된다.

int flags
- recv() 함수의 동작을 바꾸는 옵션이며, 보통 0을 사용한다.
- 드물게 MSG_PEEK와 MSG_OOB 를 사용하는 경우가 있다.
- recv() 함수는 수신 버퍼의 데이터를 애플리케이션 버퍼로 복사한 후 해당 데이터를 수신 버퍼에서 삭제한다.
- 그러나 MSG_PEEK 옵션을 사용하면 수신 버퍼에 데이터가 계속 남아 있게 된다.

recv() 함수는 두 종류의 성공적인 리턴을 할 수 있다.
1. 수신 버퍼에 데이터가 도달한 경우
- recv() 함수의 세 번째 인자인 len보다 크지 않은 범위 내에서 가능한 많은 데이터를 애플리케이션 버퍼로 복사한다.
- 이 경우 복사한 바이트 수가 리턴되며, 가능한 최대 리턴값은 len 이 된다.

2. 접속이 정상 종료된 경우
- 상대 애플리케이션이 closesocket() 함수를 사용하여 접속을 종료하면, TCP 프로토콜 수준에서 접속 종료를 위한
패킷 교환 절차가 일어난다. 이 경우 recv() 함수는 0을 리턴한다.
recv() 함수의 리턴값이 0인 경우를 정상 종료라 부른다. (normal close = graceful close) [각주:1]


recv() 함수 사용시 주의할 점은,
세 번째 인자인 len 으로 지정한 크기보다 작은 데이터가 애플리케이션 버퍼로 복사될 수 있다는 사실이다.
이는 TCP 가 메시지 경계를 구분하지 않는다는 특성에 기인한 것이며, 자신이 받을 데이터의 크기를 미리 알고 있다면, 이 크기만큼 받을때까지 recv() 함수를 여러번 호출해야 한다. 예제에서는 사용자 정의 함수인 recvn() 을 정의해서 처리하고 있다.


사용예)

//사용자 정의 데이터 수신 함수
int recvn(SOCKET s, char *buf, int len, int flags)[각주:2]
{
 int received;[각주:3]
 char *ptr = buf;[각주:4]
 int left = len;[각주:5]
 while( left > 0 )[각주:6]
 {
  received = recv(s,ptr,left,flags);[각주:7]
  if(received == SOCKET_ERROR)
  return SOCKET_ERROR;
  else if(received == 0)[각주:8]
  break;[각주:9]
  left -= received;[각주:10]
  ptr += received;[각주:11]
  
 }
 return (len - left);[각주:12]
}















  1. TCP 종료시에 FIN,ACK,FIN,ACK 네 개의 패킷 교환이 일어난다.그러나 때로는 FIN, FIN/ACK, ACK 세 개의 패킷이 교환되기도 한다.먼저 closesocket() 함수를 호출한 소켓의 TCP 포트는 FIN_WAIT 상태를 거친 후 사라진다.보통 FIN_WAIT 상태에 머무르는 시간은 TCP/IP 구현마다 다르지만 일반적으로 5분을 넘지 않는다. [본문으로]
  2. recv() 함수와 동일한 인자를 사용한다. [본문으로]
  3. recv() 함수의 리턴값을 저장하는 변수다. [본문으로]
  4. 포인터 변수 ptr이 애플리케이션 버퍼의 시작 주소를 가리키도록 한다. 데이터를 읽을 때마다 ptr 변수는 증가한다. [본문으로]
  5. left 변수는 아직 읽지 않은 데이터 크기를 나타낸다. [본문으로]
  6. 읽지 않은 데이터가 존재시 계속 루프를 돈다. [본문으로]
  7. recv() 함수를 호출하고, 오류가 발생시 리턴한다. [본문으로]
  8. 함수 리턴값이 0이면 정상종료, 상대가 데이터를 더 보내지 않을 것이므로 루프를 빠져나간다. [본문으로]
  9. 함수 리턴값이 0이면 정상종료, 상대가 데이터를 더 보내지 않을 것이므로 루프를 빠져나간다. [본문으로]
  10. 각각 변수를 갱신한다. [본문으로]
  11. 읽은 바이트 수를 리턴한다.정상 종료를 제외하면 left 변수는항상 0이므로 리턴값은 len이 된다. [본문으로]
  12. 각각 변수를 갱신한다. [본문으로]

'Windows > Network Programming' 카테고리의 다른 글

send(), recv() 사용 분석  (0) 2011.07.16
TCP Client 분석  (0) 2011.07.15
TCP Server 분석  (0) 2011.07.15
TCP Server / Client  (0) 2011.07.15

+ Recent posts