개인 공부/네트워크

네트워크P-006 (데이터 전송 방식)

Bandit10 2023. 4. 13. 01:16

데이터 전송 방식

응용 프로그램 프로토콜

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

TCP 데이터 전송

  • TCP 통신
    • OS 레벨에서 데이터를 보내면 send 버퍼에 있는 내용물이 recv 버퍼에 들어가는 것 까지 OS에서 보장을 해준다
    • OS 레벨에서 데이터를 보내면 데이터를 받았다는 응답을 다시 보내게 된다
    • 보낸 측에서는 제대로 받았다는 패킷이 오기 전까지 send 버퍼의 내용물을 지우지 않는다
    • 해당 패킷을 확인하고 OS가 버퍼의 내용을 지운다
  • TCP 특징
    • 데이터의 경계가 없고 연속적 전송
    • 1 : 1 통신이라는 것은 연결되어 있는 것 끼리만 데이터를 주고 받음을 통해 확인
    • TCP 프로그램에서 오류가 가장 많이내는 케이스가 데이터 경계가 없는것

메시지 경계 구분

  • 송신자는 항상 고정 길이 데이터를 보낸다
    • 수신자는 항상 고정 길이 데이터를 읽는다
  • 송신자는 가변 길이 데이터를 보내고 끝부분에 EOR(End Of Record)를 붙인다
    •  수신자는 EOR이 나올 때까지 데이터를 읽는
  • 송신자는 보낼 데이터 크기를 고정 길이 데이터로 보내고 이어서 가변 길이 데이터를 보낸다
    • 수신자는 고정 길이 데이터를 읽고 뒤에 가변 길이 데이터를 알아내서 데이터를 읽는다

경계 구분

  • TCP와 같이 메시지 경계 구분을 하지 않는 프로토콜을 사용할 경우, 어플리케이션에서 처리해야한다!!

고정 길이 데이터 사용

송신자 항상 고정 길이 데이터를 보낸다
수신자 항상 고정 길이 데이터를 읽는다
장점 주고 받을 데이터의 길이 변동폭이 크지 않을 경우에 적합하다
구현이 쉽다
주의점 길이가 짧은 데이터를 주고 받을 때는 낭비하는 부분이 생긴

가변 길이 데이터 사용

송신자 가변 길이 데이터를 보내고 끝 부분에 EOR을 붙여서 보낸다
수신자 EOR이 나올 때까지 데이터를 읽은 후 처리한다
장점 생성될 데이터의 길이를 미리 알 수 없을 때 적합하다
주의 데이터 중간에 EOR과 같은 패턴이 들어있으면 안된다
데이터를 효율적으로 수신하는 방식을 고안하지 않으면 성능이 떨어진다

고정 + 길이 데이터 사용

송신자 데이터 크기를 고정 길이 데이터로 보내고, 이어서 가변 데이터를 보낸다
수신자 고정 길이 데이터에서 데이터 길이를 읽고 그 길이 만큼 데이터를 읽는다 
장점 구현의 편의성과 처리 효율면에서 유리하다

전송 후 종료

송신자 가변 길이 데이터 전송 후 접속을 정상 종료
수신자 recv() 함수의 리턴 값이 0(정상종료)이
될 때까지 데이터를 읽는다
장점 한쪽에서 일방적으로 데이터를 보내는 경우에 적합
주의점 데이터를 자주 전송하는 경우에는 연결 설정, 전송, 종료를
반복해야 하므로 비효율적

 

고정 , 가변, 고정+가변


고정 길이 데이터 전송

  • 고정 길이 데이터에 사용할 버퍼의 크기를 정해서 통신한다
#include "Common.h"
#define SERVERPORT 9000
#define BUFSIZE 50

int main() {
	
	WSADATA wsa;

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

	// ===== 소켓 생성 =====
	SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, 0);	// IPv4, TCP 통신 소켓
	if (listenSocket == INVALID_SOCKET)	printf("Socket ERROR!!!\n");

	// ===== bind() =====
	struct sockaddr_in serveraddr;	//IPv4 소켓 주소 구조체
	memset(&serveraddr, 0, sizeof(serveraddr));		// 초기화
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);	// INADRR_ANY : 모든 IP에서 접속 가능!!
	serveraddr.sin_port = htons(SERVERPORT);

	int retval;
	retval = bind(listenSocket, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
	if (retval == SOCKET_ERROR)	printf("BIND() ERROR!!\n");

	// ===== listen() =====
	retval = listen(listenSocket, SOMAXCONN);
	if (retval == SOCKET_ERROR)	printf("LISTEN() ERROR!!!\n");
	printf("Server Listen...");

	// 통신에 사용할 변수 선언
	SOCKET clientSocket;			// 클라이언트 소켓
	struct sockaddr_in clientaddr;	// 클라이언트 소켓 주소(원격 IP / 원격 PORT)
	int addrlen;						// 주소 길이
	char buf[BUFSIZE + 1];			// 받은 데이터를 저장할 응용프로그램 버퍼

	while (1) {
		// ===== accept() =====
		addrlen = sizeof(clientaddr);
		clientSocket = accept(listenSocket, (struct sockaddr*)&clientaddr, &addrlen);
		if (clientSocket == INVALID_SOCKET) {
			printf("ACCEPT() ERROR!!\n");
			break;
		}

		char addr[INET_ADDRSTRLEN];
		inet_ntop(AF_INET, &clientaddr.sin_addr, addr, sizeof(addr));	//  inet_ntop : 네트워크 바이트 정렬
		printf("TCP Connect : IP = %s, PORT = %d\n", addr, ntohs(clientaddr.sin_port));

		// ===== recv() =====
		while (1) {
			retval = recv(clientSocket, buf, BUFSIZE, MSG_WAITALL);	// MSG_WAITALL : 수신버퍼에 길이만큼 다 도착할때 까지 기다렸다가 읽기
			// 수신 버퍼에 있는 값을 읽어 와서 응용프로그램 버퍼에 복사
			if (retval == SOCKET_ERROR) {	// 오류가 있을 경우
				printf("RECV() ERROR!!!\n");
				break;
			}else if (retval == 0)	break; // 정상 종료
	
			buf[retval] = '\0';	// 받은 마지막 데이터 표시
			printf("[TCP : %s : %d] %s\n", addr, ntohs(clientaddr.sin_port), buf);

			// ===== send() =====
			//retval = send(clientSocket, buf, retval, 0);
			if (retval == SOCKET_ERROR) {
				printf("SEND() ERROR!!!\n");
				break;
			}

		}
		// 클라이언트 소켓 닫기
		closesocket(clientSocket);
		printf("Client 종료 IP = %s, Port Number = %d\n", addr, ntohs(clientaddr.sin_port));
	}
	// Listen Socket 닫기
	closesocket(listenSocket);

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

 

기존 TCP 서버 코드와 달라진점

  • BUFSIZE = 50
  • recv() 수정 (MSG_WAITALL : 수신버퍼에 길이만큼 다 도착할때 까지 기다렸다가 읽기)
    • retval = recv(client_sock, buf, BUFSIZE, 0); ->  retval = recv(clientSocket, buf, BUFSIZE, MSG_WAITALL);
  • send() 삭

 

#include "Common.h"
#define SERVERPORT 9000
#define BUFSIZE 50

char* SERVERIP = (char*)"127.0.0.1";

int main() {
	int retval;

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

	// 소켓 생성
	SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock == INVALID_SOCKET)	printf("SOCKET ERORR!!!\n");

	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);
	
	// ===== connect() =====
	retval = connect(sock, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
	if (retval == SOCKET_ERROR)	printf("CONNECT() ERORR!!!\n");

	// 데이터 통신에 사용할 변수 
	char buf[BUFSIZE];
	const char* data[] = {
		"안녕하세요!!!",
		"반가워요!!!",
		"네트워크 공부 중",
		"화이팅!!"
	};
	
	// 서버와 데이터 통신
	for (int i = 0; i < 4; i++) {
		// 데이터 입력(시뮬레이션)
		memset(buf, '#', sizeof(buf));
		strncpy(buf, data[i], strlen(data[i]));

		// ===== send() =====
		retval = send(sock, buf, BUFSIZE,0);
		if (retval == SOCKET_ERROR) {
			printf("SEND() ERROR!!!\n");
			break;
		}
		printf("[TCP Client] %dByte를 보냈습니다\n", retval);
	}
	// 소켓 닫기
	closesocket(sock);

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

기존 TCP 클라이언트 코드와 달라진점

  • BUFSIZE  = 50
  • 데이터를 입력 받던거를 잠시 정해진 데이터로 전송
  • 무한 반복하던 데이터 보내는 것을 데이터 사이즈만큼만 반복해서 데이터를 보냈다
  • send()부분 수정
    • retval = send(sock, buf, (int)strlen(buf), 0); ->  retval = send(sock, buf, BUFSIZE,0);
  • recv() 삭제

결과