코딩/Network

[네트워크] 데이터 전송, 고정 길이, 가변 길이

나야, 웅이 2023. 4. 15. 20:49
728x90
반응형
SMALL

응용 프로그램 프로토콜

▶응용 프로그램 수준에서 주고받는 데이터의 형식과 의미, 처리 방식을 정의한 프로토콜

 

 

데이터 전송 시 고려할 사항

1. 메시지 경계 구분

a) 송신자는 항상 고정 길이 데이터를 보내고,

  수신자는 항상 고정 길이 데이터를 읽는 경우

- 구현이 쉬움

- 짧은 데이터 전송 시 고정 길이만큼 보내지 않아서 불필요한 자원 낭비가 발생함

- 프로토콜 변경이 어려움

 

b) 송신자는 가변 길이 데이터를 보내면서 끝부분에 EOR (End Of Record)를 붙임,

  수신자는 EOR이 나올 때까지 데이터를 읽음

- 데이터 길이를 알 수 없을 때 효과적임

- 송신자는 데이터 전송 후 마지막으로 EOR 전송

- 데이터 길이를 모르기 때문에 EOR이 나올 때까지 하나씩 읽어가야함, 성능문제가 생김

 

c) 송신자는 보낼 데이터 크기를 고정 길이 데이터로 보내고 이어서 가변 길이 데이터를 보냄,

    수신자는 고정 길이 데이터를 읽어서 뒤따라올 가변 데이터의 길이를 알아내고 이 길이만큼 데이터를 읽음

- 처리가 쉽고 효율적

- retval = recv(client_sock, buf, BUFSIZE, MSG_WAITALL); 을 두 번 호출하여 모든 데이터를 읽을 수 있음

 

 

d) 송신자는 가변 길이 데이터 전송한 후 연결을 정상 종료

    수신자는 recv() 함수의 리턴값이 0(정상 종료)이 될 때까지 데이터를 읽음

- 한 쪽에서 일방적으로 가변 길이 데이터를 보낼 때 적합

- 비효울적 : <연결설정 - 전송 -연결종료> 를 반복해야 함

 

 

 

 

 

2. 바이트 정렬

이기종 시스템 간 데이터 교환 시 바이트 정렬 방식이 통일 되어야 함

주로 빅 엔디언 방식을 추천

 

 

 

 

3. 구조체 멤버 맞춤

a □□□□

b □

c □□□□

d □

컴파일러가 속도를 높이기 위해 char형 b, d를 1byte가 아닌, 4byte로 만듦

a □□□□

b □□□□

c □□□□

d □□□□

a,b,c,d 총 16바이트 하나의 큰 박스처럼 만듦

#pragma pack(1) 은 4바이트로 안 읽고 1바이트는 1바이트로 읽겠다는 선언

a □□□□ 4byte

b □        1byte

c □□□□  4byte

d □        1byrte

a,b,c,d 총 10byte

 

#pragma pack(2) 은 2byte로 읽겠다

a □□□□ 4byte

b □□      2byte

c □□□□  4byte

d □□      2byrte

a,b,c,d 총 12byte


다양한 데이터 전송 방식

고정 길이 데이터 전송

▶서버와 클라이언트 모두 크기가 같은 버퍼를 정의해두고 데이터를 주고 받음

 

 

 

실습 고정길이 데이터 전송 - 서버

#include "../../common.h"
#include <string.h>

#define SERVERPORT 9000
#define BUFSIZE	50

int main(int argc, char* argv[])
{
	int retval = -1;

	//	initialize winsock
	WSADATA wsa;
	if (0 != WSAStartup(MAKEWORD(2, 2), &wsa))
		return 1;

	//	create socket
	SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (INVALID_SOCKET == listen_sock)
		err_quit("socket()");

	//	bind socket
	struct sockaddr_in serveraddr;
	memset(&serveraddr, 0, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	serveraddr.sin_port = htons(SERVERPORT);
	retval = bind(listen_sock, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
	if (retval == SOCKET_ERROR)
		err_quit("bind()");

	// listen
	retval = listen(listen_sock, SOMAXCONN);
	if (SOCKET_ERROR == retval)
		err_quit("listen()");

	//	data communication
	SOCKET client_sock;
	struct sockaddr_in clientaddr;
	int addrlen;
	char buf[BUFSIZE + 1] = { 0, };

	while (1)
	{
		//	accept()
		addrlen = sizeof(clientaddr);
		client_sock = accept(listen_sock, (struct sockaddr*)&clientaddr, &addrlen);
		if (INVALID_SOCKET == client_sock)
		{
			err_display("accept()");
			break;
		}

		//	print the client information
		char addr[INET_ADDRSTRLEN] = { 0, };
		inet_ntop(AF_INET, &clientaddr.sin_addr, addr, sizeof(addr));
		printf("\n[TCP Server] Client IP = %s, port = %d\n", addr, ntohs(clientaddr.sin_port));

		//	send/recv data
		while (1)
		{
			//	recv
			retval = recv(client_sock, buf, BUFSIZE, MSG_WAITALL); //recv flag를 MSG_WAITALL로 줘서 버퍼크기 50까지 계속받음

			//	recv error
			if (SOCKET_ERROR == retval)
			{
				err_display("recv()");
				break;
			}

			//	no data received
			else if (0 == retval)
				break;

			//	print the received data
			buf[retval] = '\0';
			printf("[TCP/%s:%d] %s\n", addr, ntohs(clientaddr.sin_port), buf);

			//	closing condition
			retval = strncmp(buf, "99", sizeof(buf));
			if (0 == retval)
				break;

		}

		// 소켓 닫기
		closesocket(client_sock);
		printf("[TCP server] client close: IP = %s, port =%d\n", addr, ntohs(clientaddr.sin_port));
	}

	//	close socket
	closesocket(listen_sock);

	//	close winsock
	WSACleanup();

	return 0;
}

 

고정 길이 데이터 전송 - 클라이언트

#include "../../common.h"
#include <string.h>

char* SERVERIP = (char*)"127.0.0.1";
#define SERVERPORT 9000
#define BUFSIZE 50

int main(int argc, char* argv[])
{
	int retval;

	//	명령행 인수가 있으면 IP주소로 사용
	//	get IP address from users
	if (argc > 1)
		SERVERIP = argv[1];

	//	윈속 초기화
	WSADATA wsa;
	if (0 != WSAStartup(MAKEWORD(2, 2), &wsa))
		return 1;

	//	소켓 생성
	SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (INVALID_SOCKET == sock)
		err_quit("socket()");

	// connet()
	struct sockaddr_in serveraddr;
	memset(&serveraddr, 0, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	inet_pton(AF_INET, SERVERIP, &serveraddr.sin_addr);
	serveraddr.sin_port = htons(SERVERPORT);
	retval = connect(sock, (struct sockaddr*)&serveraddr, sizeof(serveraddr));)
	if (SOCKET_ERROR == retval)
		err_quit("connect()");

	//	buf
	// 데이터 통신에 사용할 변수
	char buf[BUFSIZE];
	const char* testdata[] = {
		"안녕하세요",
		"반가워요",
		"오늘따라 할 이야기가 많을 것 같네요",
		"저도 그렇네요",
	};

	// 서버와 데이터 통신
	for (int i = 0; i < 4; i++) {
		// 데이터 입력(시뮬레이션)
		memset(buf, '#', sizeof(buf));
		strncpy(buf, testdata[i], strlen(testdata[i]));

		// 데이터 보내기
		retval = send(sock, buf, BUFSIZE, 0);
		if (retval == SOCKET_ERROR) {
			err_display("send()");
			break;
		}
		printf("[TCP 클라이언트] %d바이트를 보냈습니다.\n", retval);
	}

	// 소켓 닫기
	closesocket(sock);

	// 윈속 종료
	WSACleanup();
	return 0;
}

 

 

 

 


 

 

 

가변 길이 데이터 전송

▶가변 길이 데이터 경계를 구분하기 위해 EOR로 사용할 데이터 패턴을 정해야 함

흔히 '\n' or \r \n'을 사용

 

ex) '\n' 을 검출하는 가상 코드

*안쓰는 것이 좋음 -> 성능저하

 

 

 


 

 

고정길이 + 가변길이 데이터 전송

▶송신 측에서 가변 길이 데이터의 크기를 미리 계산할 수 있다면 <고정길이 + 가변길이 데이터> 전송이 효과적임

▶수신 측에서는

1) 고정 길이 데이터 수신

2) 가변 길이 데이터 수신

두 번의 데이터 읽기 작업으로 가변 길이 데이터의 경계를 구분해 읽을 수 있음

 

고정길이 + 가변길이 실습

1) client

#include "../../common.h"
#include <string.h>


char* SERVERIP = (char*)"127.0.0.1";
#define SERVERPORT 9000
#define BUFSIZE    50

int main(int argc, char* argv[])
{
	int retval = -1;

	//	get IP address from users
	if (argc > 1)
		SERVERIP = argv[1];

	//	initialize winsock
	WSADATA wsa;
	if (0 != WSAStartup(MAKEWORD(2, 2), &wsa))
		return 1;

	//	create socket
	SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (INVALID_SOCKET == sock)
		err_quit("socket()");

	// connect()
	struct sockaddr_in serveraddr;
	memset(&serveraddr, 0, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	inet_pton(AF_INET, SERVERIP, &serveraddr.sin_addr);
	serveraddr.sin_port = htons(SERVERPORT);
	retval = connect(sock, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
	if (SOCKET_ERROR == retval)
		err_quit("connect()");

	//	variables declaration
	char buf[BUFSIZE + 1] = { 0, };
	const char* testdata[] = {
		"안녕하세요",
		"반가워요",
		"오늘따라 할 이야기가 많을 것 같네요",
		"저도 그렇네요"
	};
	int len = 0;

	for (int i = 0; i < 4; i++)
	{
		len = (int)strlen(testdata[i]);
		//	서버에 사이즈를 정수로 보냄
		//	첫 데이터 10byte를 정수 10으로 보냄
		strncpy(buf, testdata[i], len);

		retval = send(sock, (char*)&len, sizeof(int), 0);
		if (SOCKET_ERROR == retval)
		{
			err_display("send()");
			break;
		}

		retval = send(sock, buf, len, 0);
		if (SOCKET_ERROR == retval)
		{
			err_display("send()");
			break;
		}
		printf("[TCP Client] %d bytes has been sent.\n", retval);
	}

	//	close socket
	closesocket(sock);

	//	close socket
	WSACleanup();

	return 0;
}

2) Server

#include "../../common.h"
#include <string.h>


#define SERVERPORT 9000
#define BUFSIZE    512

int main(int argc, char* argv[])
{
	int retval = -1;

	//	initialize winsock
	WSADATA wsa;
	if (0 != WSAStartup(MAKEWORD(2, 2), &wsa))
		return 1;

	//	create socket
	SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (INVALID_SOCKET == listen_sock)
		err_quit("socket()");

	//	bind socket
	struct sockaddr_in serveraddr;
	memset(&serveraddr, 0, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	serveraddr.sin_port = htons(SERVERPORT);
	retval = bind(listen_sock, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
	if (retval == SOCKET_ERROR)
		err_quit("bind()");

	// listen
	retval = listen(listen_sock, SOMAXCONN);
	if (SOCKET_ERROR == retval)
		err_quit("listen()");

	//	data communication
	SOCKET client_sock;
	struct sockaddr_in clientaddr;
	int addrlen;
	int len = 0;						// data length of the fixed parts
	char buf[BUFSIZE + 1] = { 0, };		// data length of the variable parts

	while (1)
	{
		//	accept()
		addrlen = sizeof(clientaddr);
		client_sock = accept(listen_sock, (struct sockaddr*)&clientaddr, &addrlen);
		if (INVALID_SOCKET == client_sock)
		{
			err_display("accept()");
			break;
		}

		//	print the client information
		char addr[INET_ADDRSTRLEN] = { 0, };
		inet_ntop(AF_INET, &clientaddr.sin_addr, addr, sizeof(addr));
		printf("\n[TCP Server] Client IP = %s, port = %d\n", addr, ntohs(clientaddr.sin_port));

		//	send/recv data
		while (1)
		{
			//	receive a header
			retval = recv(client_sock, (char*)&len, sizeof(int), MSG_WAITALL);
			//	포인터에 &변수 하면 결국 len이 됨
			//	&이 주소를 의미하니까 거기에 포인터면 결국 len임
			
			//	recv error
			if (SOCKET_ERROR == retval)
			{
				err_display("recv()");
				break;
			}

			else if (0 == retval)
				break;

			//	receive data
			retval = recv(client_sock, buf, len, MSG_WAITALL);

			//	recv error
			if (SOCKET_ERROR == retval)
			{
				err_display("recv()");
				break;
			}

			//	no data received
			else if (0 == retval)
				break;

			//	print the received data
			buf[retval] = '\0';
			printf("[TCP/%s:%d] %s\n", addr, ntohs(clientaddr.sin_port), buf);

			//	closing condition
			retval = strncmp(buf, "99", sizeof(buf));
			if (0 == retval)
				break;
		}

		// 소켓 닫기
		closesocket(client_sock);
		printf("[TCP server] client close: IP = %s, port =%d\n", addr, ntohs(clientaddr.sin_port));
	}

	//	close socket
	closesocket(listen_sock);

	//	close winsock
	WSACleanup();

	return 0;
}

 

 

다양한 데이터 전송 방식

-> 암호화 통신 - 하이브리드 암호화

 

1) C→S 접속 및 공개키 요청

2) S→C 공개키 전송

3) C 난수 생성 후 서버의 공개키로 암호화

4) C→S 암호화된 난수 전송

5) S 자신의 개인키로 복호화

6) S, C 공유한 난수를 기반으로 세션키 유도 (KDF, 키 유도 함수)

7) S→C ACK전송

8) C→S 대칭키 암호 알고리즘을 이용하여 데이터 암호화 후 전송

9) S 수신 후 복호화

 

*C = Client , S = Server

 

 


프로토콜 설계

Packet = Delimiter + Header + payload

프로토콜의 확장성이 있는가?

 

STX var Type Length payload ETX

var을 추가하여 => 프로토콜을 확장

 

728x90
반응형
LIST