파일 송/수신 서비스 구조 설계
파일 송/수신 프로토콜 정의
- 기본 헤더와 확장헤더로 나눠서 정의
- 기본 헤더는 이어지는 확장 헤더 를 결정하기 위한 코드 값 포함
- 코드 값에 따른 switch case
- 각 경우에 대한 함수(처리기) 및 해석 방법 적용
json으로 was에서 처리할것이 아니라면 native c++ 에서는 위와 같은 구조를 갖는게 일반적이다.
send 1회 recv multiple이 일반적이다. 받을 때는 끊어내는 구조로 가야한다.
응용 프로그램 프로토콜 디자인
header에 맞는 구조체를 recv(&mcd) 로 읽어와서 Code를 읽어와서 Ext1,2,3 중 어떤 case인지 판별하여 진행하게 된다.
프로토콜이 적용된 파일 송신 서버 제작
//MYCMD 구조체의 nCode 멤버에 적용될 수 있는 값
typedef enum CMDCODE {
CMD_ERROR = 50, //에러
CMD_GET_LIST = 100, //C->S: 파일 리스트 요구
CMD_GET_FILE, //C->S: 파일 전송 요구
CMD_SND_FILELIST = 200, //S->C: 파일 리스트 전송
CMD_BEGIN_FILE //S->C: 파일 전송 시작을 알림.
} CMDCODE;
/////////////////////////////////////////////////////////////////////////
//기본헤더
typedef struct MYCMD
{
int nCode; //명령코드
int nSize; //데이터의 바이트 단위 크기
} MYCMD;
/////////////////////////////////////////////////////////////////////////
//확장헤더: 에러 메시지 전송헤더
typedef struct ERRORDATA
{
int nErrorCode; //에러코드: ※향후 확장을 위한 멤버다.
char szDesc[256]; //에러내용
} ERRORDATA;
/////////////////////////////////////////////////////////////////////////
//확장헤더: S->C: 파일 리스트 전송
typedef struct SEND_FILELIST
{
unsigned int nCount; //전송할 파일정보(GETFILE 구조체) 개수
} SEND_FILELIST;
/////////////////////////////////////////////////////////////////////////
//확장헤더: CMD_GET_FILE
typedef struct GETFILE
{
unsigned int nIndex; //전송받으려는 파일의 인덱스
} GETFILE;
/////////////////////////////////////////////////////////////////////////
//확장헤더:
typedef struct FILEINFO
{
unsigned int nIndex; //파일의 인덱스
char szFileName[_MAX_FNAME]; //파일이름
DWORD dwFileSize; //파일의 바이트 단위 크기
} FILEINFO;
기본헤더 및 확장헤더들의 정의이다. 파일 바디를 보내기전에 어떤 파일을 보낼건지, 크기는 얼마인지 실제로 존재하는 파일인지 등등을 알려줌으로서 client가 좀 더 수월하게 처리하도록 돕는다.
//파일 송신 시작을 알리는 정보를 전송한다.
cmd.nCode = CMD_BEGIN_FILE;
cmd.nSize = sizeof(FILEINFO);
send(hClient, (const char*)&cmd, sizeof(cmd), 0);
//송신하는 파일에 대한 정보를 전송한다.
send(hClient, (const char*)&g_aFInfo[nIndex], sizeof(FILEINFO), 0);
cmd(header, 파일 크기)를 먼저 보내고 확장 header (파일의 정보)를 보내게되는데 send를 2번 호출했지만 앵간하면 하나로 합쳐져서 가게 될 것 이다.
프로토콜이 적용된 파일 수신 클라이언트 제작
void GetFileList(SOCKET hSocket)
{
//서버에 파일 리스트를 요청한다.
MYCMD cmd = { CMD_GET_LIST, 0 };
::send(hSocket, (const char*)&cmd, sizeof(cmd), 0);
//서버로부터 파일 리스트를 수신한다.
::recv(hSocket, (char*)&cmd, sizeof(cmd), 0);
if (cmd.nCode != CMD_SND_FILELIST)
ErrorHandler("서버에서 파일 리스트를 수신하지 못했습니다.");
SEND_FILELIST filelist;
::recv(hSocket, (char*)&filelist, sizeof(filelist), 0);
//수신한 리스트 정보를 화면에 출력한다.
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);
}
}
nCode를 확인해서 server가 보내준 확장헤더가 내가 원하는 값에 매칭이되는지 먼저 확인을 하게 된다.
//1. 서버에 파일 전송을 요청
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;
//두 헤더를 한 메모리에 묶어서 전송한다!
::send(hSocket,
(const char*)pCommand, sizeof(MYCMD) + sizeof(GETFILE), 0);
delete [] pCommand;
client 가 전송할 때 한번에 send 를 통해 header + 확장 header를 보낸다.
- pCommand가 전체 메모리를 동적으로 선언하고
- pcmd 로 제일 처음을 가르키게하고 data 넣은후에
- MYCMD 사이즈만큼 포인터를 옮겨서 index를 적게된다.
파일 송수신 테스트
file index를 요청한다음 실제 파일을 요청 했을 때 wireshark로 본 packet들이다.
보통 인터넷을 사용하면 MTU 같이 제한이 있어서 한 패킷이 1500을 넘지 않게 되어있는데 현재는 loopback interface를 사용하는것이니 한꺼번에 많이 보낸것이다.
'Network > Windows 소켓 프로그래밍 입문에서 고성능 서버까지!' 카테고리의 다른 글
UDP와 브로드캐스트 (0) | 2023.11.30 |
---|---|
서버 성능 개선 (0) | 2023.11.28 |
파일 송/수신 (0) | 2023.11.23 |
TCP 채팅 서버 - 성능 개선 (0) | 2023.11.22 |
TCP 채팅 서버 - 기본 이론 (0) | 2023.11.20 |