Network/소켓통신(Linux)

데이터 전송하기

busy맨 2023. 9. 17. 21:36

1. 응용 프로그램 프로토콜과 데이터 전송

1)응용 프로그램 프로토콜(Application Protocol)

  • 응용 프로그램 수준에서 주고받는 데이터의 형식과 의미, 처리 방식을 정의한 프로토콜
  • 표준화 되어 있지 않음
    • 응용 프로그램마다 다르게 정의함
  • 응용 프로그램 프로토콜이 결정되면, 데이터를 정해진 형식과 절차에 따라 주고 받도록 소켓 함수를 이용하여 구현
  • 주고 받을 데이터를 구조체로 표현
    • 그러나 구조체 정의만으로는 데이터 전송 형식을 만족하지 않음

 

2) 데이터 전송

  • 다음과 같은 추가 정보를 통해 TCP 처럼 메세지 경계를 구분하지 않는 프로토콜을 사용할 때, 응용 프로그램 수준에서 경계를 구분
    1. 경계 구분
    2. 바이트 정렬
    3. 구조체 멤버 맞춤

 

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)