Network/Windows 소켓 프로그래밍 입문에서 고성능 서버까지!

파일 송/수신과 프로토콜 설계

Tony Lim 2023. 11. 27. 14:06

파일 송/수신 서비스 구조 설계

 

파일 송/수신 프로토콜 정의

  • 기본 헤더와 확장헤더로 나눠서 정의
  • 기본 헤더는 이어지는 확장 헤더 를 결정하기 위한 코드 값 포함
  • 코드 값에 따른 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를 사용하는것이니 한꺼번에 많이 보낸것이다.