데이터 전송
고정 길이 + 가변 길이 데이터 전송
- 송신 측에서 가변 길이 데이터 크기를 바로 계산할 수 있다면 고정 길이 + 가변 길이 데이터 전송이 효과적이다!!
- 두번의 데이터 수신으로 가변 길이 데이터의 경계를 구분해 읽을 수 있다
<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;
}
- 데이터를 보낼때 마다 반복하면서 소켓생성, 서버접속, 종료를 한다
'개인 공부 > 네트워크' 카테고리의 다른 글
네트워크P-008(스레드 & [이전 실습] 파일 전송) (1) | 2023.04.16 |
---|---|
네트워크P-008 (두 데이터 합 & 1:1 간단한 TCP 채팅 ) (0) | 2023.04.15 |
네트워크P-006 (데이터 전송 방식) (1) | 2023.04.13 |
네트워크P-005(TCP Server, TCP Client ) (2) | 2023.04.12 |
네트워크P-004(소켓 구조체 & 바이트 정렬) (0) | 2023.04.10 |