TCP 기반으로 서버에 연결한 뒤, 명령 코드(Command Code)와 확장 헤더를 통해 파일 목록을 요청하고, 특정 파일을 선택하여 다운로드 받는 과정을 보여줍니다. 서버 측과 동일한 프로토콜을 사용해, 명령 코드를 통해 어떤 동작을 할지 결정하고, 필요한 추가 정보를 확장 헤더 형태로 주고받습니다.
1. 전반적인 구조
- 서버에 연결
- 클라이언트는 서버 ip:port에 연결 요청(connect).
- 연결 실패 시 에러 처리.
- 파일 목록 요청
- 명령 코드(CMD_GET_LIST)를 서버로 송신.
- 서버는 파일 목록을 전송(CMD_SND_FILELIST).
- 파일 선택 및 요청
- 사용자 입력을 받아, CMD_GET_FILE 명령을 서버로 송신.
- 서버는 선택된 파일을 전송.
- 파일 수신
- 클라이언트는 받아야 할 데이터 크기를 확인.
- recv를 통해 데이터(파일) 수신 후 fwrite로 저장.
2. 주요 코드 구조
int _tmain(int argc, _TCHAR* argv[])
{
// 1. WinSock 초기화
WSADATA wsa = {0};
if (::WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
ErrorHandler("윈속 초기화 실패");
// 2. 소켓 생성 및 서버 연결
SOCKET hSocket = ::socket(AF_INET, SOCK_STREAM, 0);
/* ... IP 주소와 포트 설정, connect 호출 ... */
// 3. 파일 목록 요청 및 수신
GetFileList(hSocket);
// 4. 파일 요청 및 수신
GetFile(hSocket);
// 5. 소켓 종료 및 WinSock 해제
::closesocket(hSocket);
::WSACleanup();
return 0;
}
설명
- GetFileList(hSocket): 서버에 CMD_GET_LIST 명령을 보내고, 파일 목록을 받아 화면에 출력.
- GetFile(hSocket): 사용자가 선택한 파일 인덱스를 서버로 전송(CMD_GET_FILE), 해당 파일을 수신.
3. 파일 목록 요청: GetFileList
void GetFileList(SOCKET hSocket)
{
// 1. 서버에 파일 리스트를 요청
MYCMD cmd = { CMD_GET_LIST, 0 };
::send(hSocket, (const char*)&cmd, sizeof(cmd), 0);
// 2. 서버의 응답(파일 목록 전송) 수신
::recv(hSocket, (char*)&cmd, sizeof(cmd), 0);
if (cmd.nCode != CMD_SND_FILELIST)
ErrorHandler("서버에서 파일 리스트를 수신하지 못했습니다.");
// 3. 파일 리스트 헤더 수신 (파일 개수 등)
SEND_FILELIST filelist;
::recv(hSocket, (char*)&filelist, sizeof(filelist), 0);
// 4. 파일 정보들 수신 및 화면 출력
FILEINFO fInfo;
for (unsigned int i = 0; i < filelist.nCount; ++i)
{
::recv(hSocket, (char*)&fInfo, sizeof(fInfo), 0);
printf("%d\t%s\t%d\n",
fInfo.nIndex, fInfo.szFileName, fInfo.dwFileSize);
}
}
- 설명
- CMD_GET_LIST 전송: 서버에 파일 목록 요청.
- 응답 헤더 수신: 서버가 CMD_SND_FILELIST로 응답해야 정상.
- 파일 목록 헤더(SEND_FILELIST) 수신: 파일 개수(nCount).
- 개별 파일 정보(FILEINFO) 수신: 파일명, 크기 등을 출력.
4. 파일 요청 및 수신: GetFile
void GetFile(SOCKET hSocket)
{
// 1. 사용자 입력으로 파일 인덱스 결정
int nIndex;
printf("수신할 파일의 인덱스(0~2)를 입력하세요.: ");
scanf_s("%d", &nIndex);
// 2. 서버에 파일 전송 요청 (CMD_GET_FILE + GETFILE 확장 헤더)
BYTE *pCommand = new BYTE[sizeof(MYCMD) + sizeof(GETFILE)];
memset(pCommand, 0, sizeof(MYCMD)+sizeof(GETFILE));
MYCMD *pCmd = (MYCMD*)pCommand;
pCmd->nCode = CMD_GET_FILE;
pCmd->nSize = sizeof(GETFILE);
GETFILE *pFile = (GETFILE*)(pCommand + sizeof(MYCMD));
pFile->nIndex = nIndex;
// 2-1. 합쳐진 메모리를 한 번에 전송
::send(hSocket, (const char*)pCommand, sizeof(MYCMD) + sizeof(GETFILE), 0);
delete [] pCommand;
// 3. 파일 정보 수신 (CMD_BEGIN_FILE or CMD_ERROR)
MYCMD cmd = {0};
FILEINFO fInfo = {0};
::recv(hSocket, (char*)&cmd, sizeof(cmd), 0);
if (cmd.nCode == CMD_ERROR)
{
ERRORDATA err;
::recv(hSocket, (char*)&err, sizeof(err), 0);
ErrorHandler(err.szDesc);
}
else
{
// 파일 정보를 추가로 수신
::recv(hSocket, (char*)&fInfo, sizeof(fInfo), 0);
}
// 4. 파일 데이터를 recv로 반복 수신
printf("%s 파일 수신을 시작합니다!\n", fInfo.szFileName);
FILE *fp = NULL;
if (fopen_s(&fp, fInfo.szFileName, "wb") != 0)
ErrorHandler("파일 생성 실패");
char byBuffer[65536];
int nRecv;
DWORD dwTotalRecv = 0;
while ((nRecv = ::recv(hSocket, byBuffer, 65536, 0)) > 0)
{
fwrite(byBuffer, nRecv, 1, fp);
dwTotalRecv += nRecv;
putchar('#');
// 파일 크기만큼 받았으면 종료
if (dwTotalRecv >= fInfo.dwFileSize)
{
putchar('\n');
puts("파일 수신 완료!");
break;
}
}
fclose(fp);
}
설명
- 파일 인덱스 입력: 사용자에게 다운로드할 파일 번호 받기.
- CMD_GET_FILE 명령 전송:
- 기본 헤더(MYCMD) + 확장 헤더(GETFILE)를 합쳐 한 번에 전송.
- 서버 응답 확인:
- CMD_ERROR면 에러 정보(ERRORDATA) 수신 및 처리.
- CMD_BEGIN_FILE이면 파일 정보(FILEINFO) 수신.
- 파일 실제 데이터 수신:
- recv로 받은 데이터를 파일로 기록.
- 수신한 바이트 수가 dwFileSize에 도달하면 종료.
5. 프로토콜 디자인 포인트
- 명령 코드와 확장 헤더:
- 명령 코드(nCode)로 요청 종류 구분.
- 요청에 필요한 추가 데이터(GETFILE, SEND_FILELIST 등)는 확장 헤더 구조체에 분리.
- 단일 메모리 블록 전송:
- 전송 시 기본 헤더 + 확장 헤더를 한 번에 send → 성능과 관리 이점.
- 명령별 응답 처리:
- switch-case나 함수 포인터 테이블로 명령 분기.
- 응답(CMD_ERROR, CMD_BEGIN_FILE 등)은 서버에서 결정.
6. 데이터 흐름 요약
- 클라이언트 시작: 서버와 연결 (connect).
- 파일 목록 요청 (CMD_GET_LIST):
- 서버는 파일 목록(CMD_SND_FILELIST) 응답 → 클라이언트는 파일 목록 출력.
- 파일 요청 (CMD_GET_FILE + GETFILE 확장 헤더):
- 서버는 파일 전송 시작(CMD_BEGIN_FILE) + 파일 정보 전송.
- 클라이언트는 실질적인 파일 데이터를 recv 반복으로 수신.
- 수신 완료: 파일 크기만큼 받으면 종료.
- 명령 코드(nCode)와 확장 헤더 분리로 데이터 해석을 직관적으로 함.
- 기본 헤더 + 확장 헤더를 단일 메모리 블록으로 묶어 효율적으로 send.
- 서버가 응답할 때도 동일한 구조로 헤더(CMD_XXX) + 데이터(FILEINFO, ERRORDATA 등)를 전송.
이러한 방식으로 설계하면 유지보수나 확장이 편리하고, 파일 전송, 목록 요청 등 다양한 명령을 한 프로토콜로 깔끔하게 처리할 수 있습니다.
'프로그래밍 > 소켓 프로그래밍 입문' 카테고리의 다른 글
윈도우 운영체제에서의 파일 I/O 구조: 요청과 처리 (0) | 2025.01.24 |
---|---|
실제 파일 송수신 테스트 해보기 (0) | 2025.01.21 |
프로토콜이 적용된 파일 송신 서버 구조 (0) | 2025.01.21 |
응용 프로그램 프로토콜 디자인: 헤더와 확장 헤더 설계해보기 (0) | 2025.01.21 |
파일 송/수신 서비스 구조 설계 (0) | 2025.01.21 |