본문 바로가기

개인 공부/네트워크

네트워크P-007 (데이터 전송 실습)

데이터 전송

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

  • 송신 측에서 가변 길이 데이터 크기를 바로 계산할 수 있다면 고정 길이 + 가변 길이 데이터 전송이 효과적이다!! 
  • 두번의 데이터 수신으로 가변 길이 데이터의 경계를 구분해 읽을 수 있다

<TCPServer_FV.cpp>

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

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;						// 주소 길이
	int len;
	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, (char*)&len, sizeof(int), MSG_WAITALL);	
			if (retval == SOCKET_ERROR) {	// 오류가 있을 경우
				printf("FIXED_LENGHT_DATA RECV() ERROR!!!\n");
				break;
			} else if (retval == 0)	break; // 정상 종료

			// === 가변 길이 데이터 받기 ===
			retval = recv(clientSocket, buf, len, MSG_WAITALL);
			if (retval == SOCKET_ERROR) {
				printf("VARIABLE_LENGTH_DATA 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, len, 0);
			// send() : 응용 프로그램 버퍼를 송신 버퍼에 복사해서 전송!!

			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;
}
  • 고정길이 데이터의 길이는 len 변수에, 가변길이 데이터 길이는 buf[] 변수에 저장
  • 첫 번째 고정길이 데이터 recv()함수를 호출해서 고정 길이 데이터를 읽는다
  • len 변수에 고정 길이 다음에 오는 가변길이 데이터 크기를 저장한다

<TCPClient_FV.cpp>

#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");

	// 데이터 통신에 사용할 변수 
	int len;
	char buf[BUFSIZE];
	const char* data[] = {
		"안녕하세요!!!",
		"반가워요!!!",
		"네트워크 프로그래밍 공부 중",
		"화이팅!!"
	};

	// 서버와 데이터 통신
	for (int i = 0; i < 4; i++) {
		len = (int)strlen(data[i]);
		strncpy(buf, data[i], len);

	// ===== send() =====
		// 고정길이 데이터 보내기
		retval = send(sock, (char *)&len, sizeof(int), 0);
		if (retval == SOCKET_ERROR) {
			printf("FIXED_LENGHT_DATA SEND() ERROR!!!\n");
			break;
		}

		// 가변길이 데이터 보내기
		retval = send(sock, buf, len, 0);
		if (retval == SOCKET_ERROR) {
			printf("VARIABLE_LENGTH_DATA SEND() ERROR!!!\n");
			break;
		}

		printf("[TCP Client] %dByte + %dByte 를 보냈습니다\n", retval, (int)strlen(buf));
	}
	// 소켓 닫기
	closesocket(sock);

	// 윈속 종료
	WSACleanup();
	return 0;
}
  • 우선 send()에 32비트 고정 길이 데이터를 보낸다
  • 그 후 가변 길이 데이터를 보낸다


가변 길이 데이터 전송

  • 데이터 길이가 얼마나 오고 갈지 모를 때 사용하기 훨씬 효율적이다
  • 가변 길이 데이터 경계를 구분하기 위해 EOR로 사용할 데이터 패턴을 정해야한다
    • 흔히 '\n'이나 '\r\n'을 사용한다!!
  • 성능을 높이려면 소켓 수신 버퍼에서 데이터를 한 번에 많이 읽어 1Byte씩 리턴해주는 사용자 정의 함수가 필요하다!!

<TCPServer_Var.cpp>

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

// ===== 1Byte 씩 읽어줄 함수 정의 =====
int _recv_ahead (SOCKET s, char* p) {
	static int nbyte = 0;	// 읽어야 하는 바이트 수
	static char buf[1024];	// 저장되는 버퍼 수
	static char* ptr;	// 버퍼의 시작 위치

	if (nbyte == 0 || nbyte == SOCKET_ERROR) {
		nbyte = recv(s, buf, sizeof(buf), 0);	// 버퍼 사이즈 만큼 읽을 바이트 수
		if (nbyte == SOCKET_ERROR)	return SOCKET_ERROR;
		else if (nbyte == 0)	return 0;
		ptr = buf;
	}
	--nbyte;	// 바이트 수 감소
	*p = *ptr++;	// 다음 위치로 이동
	return 1;
}

int recvline(SOCKET s, char* buf, int maxlen) {
	int n, nbyte;	// 반복체크, 일거야하는 바이트 수
	char c, * ptr = buf;

	for (n = 1; n < maxlen; n++) {
		nbyte = _recv_ahead(s, &c);
		if (nbyte == 1) {
			*ptr++ = c;
			if (c == '\n')	break;
		} else if (nbyte == 0) {
			*ptr = 0;
			return n - 1;
		} else return SOCKET_ERROR;
	}
	*ptr = 0;
	return n;
}



int main() {

	WSADATA wsa;
	int retval;

	// ===== 윈속 초기화 =====
	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;	// IPv4
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);	// INADRR_ANY : 모든 IP에서 접속 가능!!
	serveraddr.sin_port = htons(SERVERPORT);	// h -> n Port Number 설정

	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...\n");

	// 통신에 사용할 변수 선언
	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));
		// inet_ntop 네트워크 바이트 정렬(숫자) -< 문자열로
		// inet_btop(주소체계, IP주소, 저장공간, 크기)

		while (1) {
			// ===== recv() =====
			retval = recvline(clientSocket, buf, BUFSIZE + 1);
			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);
		}
		// 클라이언트 소켓 닫기
		closesocket(clientSocket);
		printf("Client 종료 IP = %s, Port Number = %d\n", addr, ntohs(clientaddr.sin_port));
	}
	// Listen Socket 닫기
	closesocket(listenSocket);

	// 윈속 종료
	WSACleanup();
	return 0;
}
  • 수신 버퍼에서 데이터를 한꺼번에 읽은 후 1Byte씩 리턴해주는 함수를 정의해줘야한다!
  • _recv_ahead(), recvline() : 함수 정의
  • 지금 구현한 서버 프로그램은 EOR을 '\n'으로 정했다

<TCPClient_Var.cpp>

#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];
	int len;
	const char* data[] = {
		"안녕하세요!!!",
		"반가워요!!!",
		"네트워크 공부 중",
		"화이팅!!"
	};

	// 서버와 데이터 통신
	for (int i = 0; i < 4; i++) {
		len = (int)strlen(data[i]);
		strncpy(buf, data[i], len);
		buf[len++] = '\n';

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

	// 윈속 종료
	WSACleanup();
	return 0;
}
  • 문자열 데이터를 버퍼에 복사하고 정의했던 (EOR) '\n'을 추가해준다
  • len크기 만큼 데이터를 보낸다 -> len 문자열에 '\n'을 붙인 데이터 길이를 나타낸다

데이터 전송 후 종료

  • 일종의 가변 길이 데이터 전송 방식
  • EOR로 특별한 데이터 패턴 대신 연결 종료를 사용!!!

<TCPServer_Close.cpp>

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

int main() {

	WSADATA wsa;
	int retval;
	// ===== 윈속 초기화 =====
	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);

	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);
			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() =====
			/*if (retval == SOCKET_ERROR) {
				printf("SEND() ERROR!!!\n");
				break;
			
			*/
			//retval = send(clientSocket, buf, len, 0);
			// send() : 응용 프로그램 버퍼를 송신 버퍼에 복사해서 전송!!
		}
		// 클라이언트 소켓 닫기
		closesocket(clientSocket);
		printf("Client 종료 IP = %s, Port Number = %d\n", addr, ntohs(clientaddr.sin_port));
	}
	// Listen Socket 닫기
	closesocket(listenSocket);

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

<TCPClient_Close.cpp>

#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;

	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);

	// 데이터 통신에 사용할 변수 
	int len;
	char buf[BUFSIZE];
	const char* data[] = {
		"안녕하세요!!!",
		"반가워요!!!",
		"네트워크 프로그래밍 공부 중",
		"화이팅!!"
	};

	// 서버와 데이터 통신
	for (int i = 0; i < 4; i++) {
		SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
		if (sock == INVALID_SOCKET)	printf("SCOKET ERROR!!!\n");

		// ===== connect() ===== 
		retval = connect(sock, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
		if (sock == SOCKET_ERROR)	printf("CONNECT() ERROR!!\n");

		// 데이터 입력(시뮬레이션)
		len = (int)strlen(data[i]);
		strncpy(buf, data[i], len);

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

		printf("[TCP Client] %dByte 를 보냈습니다\n", retval);
		// 소켓 닫기
		closesocket(sock);
	}
	// 윈속 종료
	WSACleanup();
	return 0;
}
  • 데이터를 보낼때 마다 반복하면서 소켓생성, 서버접속, 종료를 한다