출처 : 열혈 TCP/IP 소켓 프로그래밍 윤성우 저
TCP 소켓의 입출력 버퍼
각 소켓은 입력 버퍼와 출력 버퍼를 관리하고 recv()를 통해 입력 버퍼에서 수신된 데이터를 읽어드리고 send()를 통해 출력 버퍼로 송신할 데이터를 쓴다.
소켓을 통한 데이터 수신 처리
- 데이터 수신
int recv(SOCKET s, const char* buf, int len, int flags);
- recv함수는 성공 시 수신된 데이터의 바이트 수만큼 반환하고 그렇지 않으면 SOCKET_ERROR를 반환한다.
// 한번에 100 바이트를 수신함
int recvLen = recv(hServSock, message, 100, 0);
위 코드를 통해 사용자가 수신해야할 데이터가 100바이트라면, 100바이트의 데이터가 입력 버퍼에 완전히 수신되어야 한 번에 수신할 수 있다.
하지만 네트워크 지연으로 입력 버퍼에 완전한 데이터가 수신되어있지 않을 수 도 있기 때문에 다음과 같이 필요한 양만큼 데이터가 수신될 때까지 recv()를 호출해야한다.
int recvLen = 0;
while(recvLen < 100)
{
// recv 함수는 성공시 수신된 바이트를 반환한다.
recvlen += recv(hServSock, &message[recvLen], 100, 0);
}
TCP 프로토콜의 흐름 제어로 입력 버퍼를 초과하여 데이터를 전송하지 않기 때문에 입력 버퍼만 잘 읽어드리는 것으로 완전한 데이터를 수신할 수 있다.
어플리케이션 프로토콜의 사용자 정의
잘 알려진 파일의 확장자는 (이미지: bmp/png/jpg, 음성 파일: mp3/wav, 메쉬: ply, pcd) 그 데이터에 대한 형식을 약속하고 있다. 예를 들어, 어떤 이미지 데이터는 첫 번째 줄에 이미지 가로와 세로 사이즈에 대한 정의와 픽셀 형식 그 다음 줄부터 차례대로 행을 구성하는 각 열의 픽셀 정보가 차례대로 담겨 있을 수 있다.
네트워크를 통해 통신할 데이터도 송신자와 수신자간의 약속된 데이터의 형식이 있어야한다. 가령 구조체로 정의된 어떤 데이터가 같은 정의로 사용되어 멤버만 순서대로 잘 채워 송신하면 수신자는 같은 정의로된 구조체의 바이트 크기만큼 읽어드려 구조체의 정의에 따라 데이터를 수신할 수 있을 것이다.
어떤 방식으로든 송신자와 수신자는 보내고자하는 데이터와 수신하고자 하는 데이터에 대한 약속된 프로토콜이 필요하고 이는 실제 어플리케이션에서 정의하여 원할한 정보를 주고 받도록한다.
TCP 서버-클라이언트 예제 - 2
피연산자와 연산자를 수신하여 그 결과를 전송하는 TCP 서버 클라이언트에 대한 어플리케이션 프로토콜을 정의하고 예시를 보자.
- 클라이언트에서 서버로의 메세지
1. 첫번째 데이터는 1 바이트 크기로 피연산자 수 N를 정의한다.
2. 각 피연산자 수 N에 대해 4 바이트의 정수형을 담는다.
3. 연산자를 1 바이트 크기로 담는다.
- 구현부
// 메세지 전송 부분
/*
계산기를 위한 어플리케이션 프로토콜 사용자 정의
피연산자 갯수 N (1 바이트)
피연산자 (4xN 바이트)
연산자 종류 (1 바이트)
*/
char message[BUF_SIZE] = {};
int operCnt = 0, offset = 0;
cout << "피연산자 갯수 : ";
cin >> operCnt;
message[offset++] = (char)operCnt;
for (int i = 0; i < operCnt; ++i)
{
cout << "피연산자 (정수형) :";
cin >> *((int*)(message + offset));
offset += 4;
}
cin.get();
cout << "연산자 종류 : ";
cin.read(message + offset, 1);
++offset;
send(hSocket, message, offset, 0);
int result = 0;
if(recv(hSocket, (char*)&result, 4, 0) == SOCKET_ERROR)
{
printf("Socket Error\n");
}
printf("Operation result %d\n", result);
system("pause");
- 서버에서 클라이언트 메세지
1. 피연산자 수 N을 수신한다.
2. N에 대해 총 4*N 바이트의 정수형 데이터를 수신한다.
3. 연산 결과를 4 바이트의 정수형으로 보낸다.
- 구현부
// 메세지를 수신하여 계산하는 부분
// 피연산자 갯수 수신
char opndCnt = 0;
recv(hClntSock, &opndCnt, 1, 0);
int recvLen = 0;
// 총 4*N + 1 바이트를 수신
while (recvLen < 4 * opndCnt + 1)
{
int recvCnt = recv(hClntSock, &message[recvLen], BUF_SIZE - 1, 0);
recvLen += recvCnt;
}
const auto calculate = [&]() -> int
{
// 피연산자
int* opnds = reinterpret_cast<int*>(message);
int res = opnds[0], i;
// 연산자
char op = message[recvLen - 1];
switch (op)
{
case '+':
for (i = 1; i < opndCnt; ++i) res += opnds[i];
break;
case '-':
for (i = 1; i < opndCnt; ++i) res -= opnds[i];
break;
case '*':
for (i = 1; i < opndCnt; ++i) res *= opnds[i];
break;
}
return res;
};
// 결과 계산 및 전송
int result = calculate();
int sendLen = send(hClntSock, (char*)&result, 4, 0);
if (sendLen == SOCKET_ERROR)
{
printf("Socket Error Send()\n");
}
else
{
printf("%d bytes sent\n", sendLen);
}
closesocket(hClntSock);
참고: 윤성우 저 TCP/IP 소켓 프로그래밍
'Computer Science 기본 지식 > 소켓 프로그래밍' 카테고리의 다른 글
[TCP/IP 소켓 프로그래밍] (5) TCP 기반 파일 전송 (0) | 2021.03.28 |
---|---|
[TCP/IP 소켓 프로그래밍] (4) UDP 기반 서버 및 클라이언트 (0) | 2021.03.28 |
[TCP/IP 소켓 프로그래밍] (3) TCP 기반 서버 및 클라이언트 - 1 (0) | 2021.03.26 |
[TCP/IP 소켓 프로그래밍] (2) 소켓 프로토콜과 데이터 전송 특성 (2) | 2021.03.26 |
[TCP/IP 소켓 프로그래밍] (1) 소켓 생성 (0) | 2021.03.26 |