1. 응용 프로그램 프로토콜과 데이터 전송
1)응용 프로그램 프로토콜(Application Protocol)
- 응용 프로그램 수준에서 주고받는 데이터의 형식과 의미, 처리 방식을 정의한 프로토콜
- 표준화 되어 있지 않음
- 응용 프로그램마다 다르게 정의함
- 응용 프로그램 프로토콜이 결정되면, 데이터를 정해진 형식과 절차에 따라 주고 받도록 소켓 함수를 이용하여 구현
- 주고 받을 데이터를 구조체로 표현
- 그러나 구조체 정의만으로는 데이터 전송 형식을 만족하지 않음
2) 데이터 전송
- 다음과 같은 추가 정보를 통해 TCP 처럼 메세지 경계를 구분하지 않는 프로토콜을 사용할 때, 응용 프로그램 수준에서 경계를 구분
- 경계 구분
- 바이트 정렬
- 구조체 멤버 맞춤
2. 다양한 데이터 전송 방식
1) 고정 길이 데이터 전송
- 서버와 클라이언트 모두 크기가 같은 버퍼를 정의
Server Code)
#include "../Common.h"
#define SERVERPORT 9000
#define BUFSIZE 50
int main(int argc, char *argv[])
{
int retval;
// 소켓 생성
SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock == INVALID_SOCKET) err_quit("socket()");
// bind()
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 (retval == SOCKET_ERROR) err_quit("listen()");
// 데이터 통신에 사용할 변수
SOCKET client_sock;
struct sockaddr_in clientaddr;
socklen_t addrlen;
char buf[BUFSIZE + 1];
while (1) {
// accept()
addrlen = sizeof(clientaddr);
client_sock = accept(listen_sock, (struct sockaddr *)&clientaddr, &addrlen);
if (client_sock == INVALID_SOCKET) {
err_display("accept()");
break;
}
// 접속한 클라이언트 정보 출력
char addr[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &clientaddr.sin_addr, addr, sizeof(addr));
printf("\n[TCP 서버] 클라이언트 접속: IP 주소=%s, 포트 번호=%d\n",
addr, ntohs(clientaddr.sin_port));
// 클라이언트와 데이터 통신
while (1) {
// 데이터 받기
retval = recv(client_sock, buf, BUFSIZE, MSG_WAITALL);
if (retval == SOCKET_ERROR) {
err_display("recv()");
break;
}
else if (retval == 0)
break;
// 받은 데이터 출력
buf[retval] = '\0';
printf("[TCP/%s:%d] %s\n", addr, ntohs(clientaddr.sin_port), buf);
}
// 소켓 닫기
close(client_sock);
printf("[TCP 서버] 클라이언트 종료: IP 주소=%s, 포트 번호=%d\n",
addr, ntohs(clientaddr.sin_port));
}
// 소켓 닫기
close(listen_sock);
return 0;
}
Client Code)
#include "../Common.h"
char *SERVERIP = (char *)"127.0.0.1";
#define SERVERPORT 9000
#define BUFSIZE 50
int main(int argc, char *argv[])
{
int retval;
// 명령행 인수가 있으면 IP 주소로 사용
if (argc > 1) SERVERIP = argv[1];
// 소켓 생성
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) 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 (retval == SOCKET_ERROR) err_quit("connect()");
// 데이터 통신에 사용할 변수
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);
}
// 소켓 닫기
close(sock);
return 0;
}
Result)
2) 가변 길이 데이터 전송
- 가변 길이 데이터를 주고받으려면 EOR(End Of Record)로 사용할 데이터 패턴을 정해야 함
- 보통 '\n'이나 '\r\n'을 사용
- 읽은 데이터가 EOR이라면 루프를 빠져나오고, 아니라면 응용 프로그램 버퍼에 저장
Server Code)
#include "../Common.h"
#define SERVERPORT 9000
#define BUFSIZE 512
// 내부 구현용 함수
int _recv_ahead(SOCKET s, char *p)
{
static __thread int nbytes = 0;
static __thread char buf[1024];
static __thread char *ptr;
if (nbytes == 0 || nbytes == SOCKET_ERROR) {
nbytes = recv(s, buf, sizeof(buf), 0);
if (nbytes == SOCKET_ERROR) {
return SOCKET_ERROR;
}
else if (nbytes == 0)
return 0;
ptr = buf;
}
--nbytes;
*p = *ptr++;
return 1;
}
// 사용자 정의 데이터 수신 함수
int recvline(SOCKET s, char *buf, int maxlen)
{
int n, nbytes;
char c, *ptr = buf;
for (n = 1; n < maxlen; n++) {
nbytes = _recv_ahead(s, &c);
if (nbytes == 1) {
*ptr++ = c;
if (c == '\n')
break;
}
else if (nbytes == 0) {
*ptr = 0;
return n - 1;
}
else
return SOCKET_ERROR;
}
*ptr = 0;
return n;
}
int main(int argc, char *argv[])
{
int retval;
// 소켓 생성
SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock == INVALID_SOCKET) err_quit("socket()");
// bind()
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 (retval == SOCKET_ERROR) err_quit("listen()");
// 데이터 통신에 사용할 변수
SOCKET client_sock;
struct sockaddr_in clientaddr;
socklen_t addrlen;
char buf[BUFSIZE + 1];
while (1) {
// accept()
addrlen = sizeof(clientaddr);
client_sock = accept(listen_sock, (struct sockaddr *)&clientaddr, &addrlen);
if (client_sock == INVALID_SOCKET) {
err_display("accept()");
break;
}
// 접속한 클라이언트 정보 출력
char addr[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &clientaddr.sin_addr, addr, sizeof(addr));
printf("\n[TCP 서버] 클라이언트 접속: IP 주소=%s, 포트 번호=%d\n",
addr, ntohs(clientaddr.sin_port));
// 클라이언트와 데이터 통신
while (1) {
// 데이터 받기
retval = recvline(client_sock, buf, BUFSIZE + 1);
if (retval == SOCKET_ERROR) {
err_display("recv()");
break;
}
else if (retval == 0)
break;
// 받은 데이터 출력
printf("[TCP/%s:%d] %s", addr, ntohs(clientaddr.sin_port), buf);
}
// 소켓 닫기
close(client_sock);
printf("[TCP 서버] 클라이언트 종료: IP 주소=%s, 포트 번호=%d\n",
addr, ntohs(clientaddr.sin_port));
}
// 소켓 닫기
close(listen_sock);
return 0;
}
Client Code)
#include "../Common.h"
char *SERVERIP = (char *)"127.0.0.1";
#define SERVERPORT 9000
#define BUFSIZE 50
int main(int argc, char *argv[])
{
int retval;
// 명령행 인수가 있으면 IP 주소로 사용
if (argc > 1) SERVERIP = argv[1];
// 소켓 생성
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) 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 (retval == SOCKET_ERROR) err_quit("connect()");
// 데이터 통신에 사용할 변수
char buf[BUFSIZE];
const char *testdata[] = {
"안녕하세요",
"반가워요",
"오늘따라 할 이야기가 많을 것 같네요",
"저도 그렇네요",
};
int len;
// 서버와 데이터 통신
for (int i = 0; i < 4; i++) {
// 데이터 입력(시뮬레이션)
len = (int)strlen(testdata[i]);
strncpy(buf, testdata[i], len);
buf[len++] = '\n';
// 데이터 보내기
retval = send(sock, buf, len, 0);
if (retval == SOCKET_ERROR) {
err_display("send()");
break;
}
printf("[TCP 클라이언트] %d바이트를 보냈습니다.\n", retval);
}
// 소켓 닫기
close(sock);
return 0;
}
Result)
3) 고정 길이 + 가변 길이 데이터 전송
- 송신 측에서 가변 길이 데이터의 크기를 미리 계산할 수 있다면, 이 방식이 효과적
Server Code)
#include "../Common.h"
#define SERVERPORT 9000
#define BUFSIZE 512
int main(int argc, char *argv[])
{
int retval;
// 소켓 생성
SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock == INVALID_SOCKET) err_quit("socket()");
// bind()
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 (retval == SOCKET_ERROR) err_quit("listen()");
// 데이터 통신에 사용할 변수
SOCKET client_sock;
struct sockaddr_in clientaddr;
socklen_t addrlen;
int len; // 고정 길이 데이터
char buf[BUFSIZE + 1]; // 가변 길이 데이터
while (1) {
// accept()
addrlen = sizeof(clientaddr);
client_sock = accept(listen_sock, (struct sockaddr *)&clientaddr, &addrlen);
if (client_sock == INVALID_SOCKET) {
err_display("accept()");
break;
}
// 접속한 클라이언트 정보 출력
char addr[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &clientaddr.sin_addr, addr, sizeof(addr));
printf("\n[TCP 서버] 클라이언트 접속: IP 주소=%s, 포트 번호=%d\n",
addr, ntohs(clientaddr.sin_port));
// 클라이언트와 데이터 통신
while (1) {
// 데이터 받기(고정 길이)
retval = recv(client_sock, (char *)&len, sizeof(int), MSG_WAITALL);
if (retval == SOCKET_ERROR) {
err_display("recv()");
break;
}
else if (retval == 0)
break;
// 데이터 받기(가변 길이)
retval = recv(client_sock, buf, len, MSG_WAITALL);
if (retval == SOCKET_ERROR) {
err_display("recv()");
break;
}
else if (retval == 0)
break;
// 받은 데이터 출력
buf[retval] = '\0';
printf("[TCP/%s:%d] %s\n", addr, ntohs(clientaddr.sin_port), buf);
}
// 소켓 닫기
close(client_sock);
printf("[TCP 서버] 클라이언트 종료: IP 주소=%s, 포트 번호=%d\n",
addr, ntohs(clientaddr.sin_port));
}
// 소켓 닫기
close(listen_sock);
return 0;
}
Client Code)
#include "../Common.h"
char *SERVERIP = (char *)"127.0.0.1";
#define SERVERPORT 9000
#define BUFSIZE 50
int main(int argc, char *argv[])
{
int retval;
// 명령행 인수가 있으면 IP 주소로 사용
if (argc > 1) SERVERIP = argv[1];
// 소켓 생성
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) 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 (retval == SOCKET_ERROR) err_quit("connect()");
// 데이터 통신에 사용할 변수
char buf[BUFSIZE];
const char *testdata[] = {
"안녕하세요",
"반가워요",
"오늘따라 할 이야기가 많을 것 같네요",
"저도 그렇네요",
};
int len;
// 서버와 데이터 통신
for (int i = 0; i < 4; i++) {
// 데이터 입력(시뮬레이션)
len = (int)strlen(testdata[i]);
strncpy(buf, testdata[i], len);
// 데이터 보내기(고정 길이)
retval = send(sock, (char *)&len, sizeof(int), 0);
if (retval == SOCKET_ERROR) {
err_display("send()");
break;
}
// 데이터 보내기(가변 길이)
retval = send(sock, buf, len, 0);
if (retval == SOCKET_ERROR) {
err_display("send()");
break;
}
printf("[TCP 클라이언트] %d+%d바이트를 "
"보냈습니다.\n", (int)sizeof(int), retval);
}
// 소켓 닫기
close(sock);
return 0;
}
Result)
'Network > 소켓통신(Linux)' 카테고리의 다른 글
멀티스레드 개념 (2) | 2023.11.19 |
---|---|
TCP 동작 과정 및 함수 정리 (0) | 2023.11.19 |
TCP 서버 - 클라이언트 구조 (2) | 2023.09.02 |
23.03.09) TCP/IP 소켓 프로그래밍(Linux) 2 (0) | 2023.03.09 |
23.03.08) TCP/IP 소켓 프로그래밍(Linux) 1 (0) | 2023.03.09 |