개인 공부/네트워크
네트워크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() 삭제