[C 언어] UDP 소켓통신과 sleep (in linux)

Date:     Updated:

카테고리:

태그:

1. 개념

UDP란?

  • TCP는 1대 1의 연결을 필요로 하지만 UDP는 연결의 개념이 존재하지 않는다.
  • 서버 소켓과 클라이언트의 소켓을 구분하지 않는다.
  • 연결 개념 존재하지 않기에 하나의 소켓으로 둘 이상의 영역과 데이터 송수신 가능 ( TCP와 다르게 한 번 보내면 한 번 받아야하고, 5번 보내면 5번 받아야한다 ! )
  • 경계의 구분이 없으므로 half_close 개념과 딜레이를 주는 sleep() 를 사용하여 개념을 익힐 수 있다.

딜레이 함수 sleep()

  • 리눅스에서는 sleep(5) 라고 사용하면 선언한 곳으로부터 5초의 대기시간을 가지는 간단한 함수이다.
  • 윈도우에서는 5라고 쓰면 5초가 아니다 !!

half_close 개념

  • close/closesocket 함수의 기능
    • 소켓 완전 소멸 -> 더 이상의 입출력 불가능
    • 상대방 상태에 상관 없이 일방적인 종료
    • 그러므로 상대 호스트의 데이터 송수신이 아직 완료되지 않은 상황 이라면 문제가 발생하게 됨

이러한 문제들을 해결하기 위한 개념이 half_close 이다.

소켓의 Half-close란?

  • 종료를 원할 때 : 더 이상 전송할 데이터가 없으니 출력 스트림은 종료 시키면 됨
  • 일반적으로 Half-close는 입력 스트림을 종료시킨다.

half_close의 사용방법

리눅스

#include <sys/socket.h>
int shutdown(int sock, int howto);<br>
-> 성공  0, 실패  -1 반환

howto: 종료방법

  • SHUT_RD 입력 스트림 종료
  • SHUT_WR 출력 스트림 종료
  • SHUT_RDWR 입출력 스트림 종료

윈도우

#include <winsock2.h>
int shutdown(SOCKET s, int howto);
-> 성공  0, 실패  SOCKET_ERROR 반환

howto: 종료방법

  • SD_RECEIVE 입력 스트림 종료
  • SD_SEND 출력 스트림 종료
  • SD_BOTH 입출력 스트림 종료

2. 문제

1. sleep() 사용

UDP 소켓통신으로 문자열 3개를 클라이언트에서 서버로 보낸다.
이때, 서버는 5초의 대기시간을 가진 다음 데이터를 받는다.

어떻게 결과가 나오는가?
클라이언트 정답
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>


void error_handling(char* message);

int main(int argc, char* argv[])
{
        int sock;
        struct sockaddr_in serv_addr;
        int str_len;

        if (argc != 3){
                printf("Usage : %s <IP> <port> \n", argv[0]);
        }

        sock = socket(PF_INET, SOCK_DGRAM, 0);
        if (sock == -1)
                error_handling("socket() error");


        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
        serv_addr.sin_port = htons(atoi(argv[2]));

//      if(connect (sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
//              error_handling("connect() error");



        // new def for sleep
        char msg1[] = "first bro";
        char msg2[] = "second bro";
        char msg3[] = "third bro";
        char* msg[3] = {msg1, msg2, msg3};

        int i, chk, size;

        printf("go\n");
        // go
        for( i = 0; i < 3; i++)
        {
                printf("size : %ld \n", strlen(msg[i]));
                size = strlen(msg[i]);
                sendto(sock, &size, sizeof(int), 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
                sendto(sock, msg[i], strlen(msg[i]), 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
        }

        close(sock);

        return 0;
}

void error_handling(char* message)
{
        fputs(message, stderr);
        fputc('\n', stderr);
        exit(1);
}
서버 정답
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF 1024

void error_handling(char* message);

int main(int argc, char* argv[])
{
        int serv_sock;
        int clnt_sock;
        struct sockaddr_in serv_addr;
        struct sockaddr_in clnt_addr;

        socklen_t serv_addr_size;
//      socklen_t clnt_addr_size;

//      char message[] = "Hell World!";

        if(argc != 2){
                printf("Usage : %s <port> \n", argv[0]);
                exit(1);
        }

        serv_sock = socket(PF_INET, SOCK_DGRAM, 0);
        if (serv_sock == -1)
                error_handling("socket() error");

        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        serv_addr.sin_port = htons(atoi(argv[1]));

        if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
                error_handling("bind() error");

//      if(listen(serv_sock, 5) == -1)
//              error_handling("listen() error");

//      clnt_addr_size = sizeof(clnt_addr);
//      clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
//      if(clnt_sock == -1)
//              error_handling("accept() error");



        // new def for sleep
        char recv_str[BUF]; // BUF 1024

        int i, cnt;
        serv_addr_size = sizeof(clnt_addr);
        for(i = 0; i < 3; i++)
        {
                sleep(5);
                recvfrom(serv_sock, &cnt, sizeof(int), 0, (struct sockaddr*)&serv_addr, &serv_addr_size);
                recvfrom(serv_sock, recv_str, cnt, 0, (struct sockaddr*)&serv_addr, &serv_addr_size);
        //      len = strlen(recv_str); -> don't try like this
        //      printf("len : %d\n", len); -> don't try like this
                printf("cnt : %d\n", cnt);
                recv_str[cnt] = '\0';
                printf("%s \n", recv_str);
        }



        close(clnt_sock);
        close(serv_sock);

        return 0;
}


void error_handling(char* message)
{
        fputs(message, stderr);
        fputc('\n', stderr);
        exit(1);
}

1번문제 피드백

  • 문자열을 넘겨줄 때 사이즈를 같이 넘겨주지 않으면 이상한 문자들이 포함되어 전송되었을 경우 반대쪽에서 길이를 구하기가 어려워진다.
  • TCP와 UDP 소켓통신의 구현 기본적인 차이를 잘 알아놓아야했다.
  • 문자열을 담아둔 포인터 배열 을 사용했을 때 안에 변수를 넣었으면 주소가 담겨있다 는 것을 꼭!! 인지하도록 하자.

2. shutdown() 사용

클라이언트가 서버에게 파일을 다운받는다.

서버는 클라이언트에게 파일을 전송하고, 전송이 끝나면 shutdown() 함수를 사용하여,
출력 스트림을 종료한다.

텍스트든, 바이너리든, 문자열이든 상관없고 모두 전송이 되면,
클라이언트가 서버에게 It's done 이라는 문자열을 보낸다.
그리고 클라이언트는 스트림을 모두 종료한다.


서버는 전송완료 문자열을 받고 남은 소켓을 종료시킨다.
클라이언트 정답
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

// new
#define BUF 1024

void error_handling(char* message);

int main(int argc, char* argv[])
{
        int sock;
        struct sockaddr_in serv_addr;
//      char message[30];
        int str_len;

        if (argc != 3){
                printf("Usage : %s <IP> <port> \n", argv[0]);
        }

        sock = socket(PF_INET, SOCK_STREAM, 0);
        if (sock == -1)
                error_handling("socket() error");


        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
        serv_addr.sin_port = htons(atoi(argv[2]));

        if(connect (sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
                error_handling("connect() error");


        // New Question definition
        char recv_file[BUF]; // BUF
        char done[BUF] = "It's done ! Thank you ";

        int cnt_r, str_tot;
        ssize_t ck_cnt_r_len, ck_str_r_len;

        // read cnt
        ck_cnt_r_len = read(sock, &cnt_r, sizeof(int));

        // read str
        ck_str_r_len = read(sock, recv_file, cnt_r-1);


        printf("%s", recv_file);

        write(sock, done, sizeof(done));


        // close
        close(sock);

        return 0;
}

void error_handling(char* message)
{
        fputs(message, stderr);
        fputc('\n', stderr);
        exit(1);
}
서버 정답
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

// new
#define BUF 1024


void error_handling(char* message);

int main(int argc, char* argv[])
{
        int serv_sock;
        int clnt_sock;
        struct sockaddr_in serv_addr;
        struct sockaddr_in clnt_addr;

        socklen_t clnt_addr_size;

//      char message[] = "Hell World!";

        if(argc != 2){
                printf("Usage : %s <port> \n", argv[0]);
                exit(1);
        }

        serv_sock = socket(PF_INET, SOCK_STREAM, 0);
        if (serv_sock == -1)
                error_handling("socket() error");

        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        serv_addr.sin_port = htons(atoi(argv[1]));

        if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
                error_handling("bind() error");

        if(listen(serv_sock, 5) == -1)
                error_handling("listen() error");

       clnt_addr_size = sizeof(clnt_addr);
        clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
        if(clnt_sock == -1)
                error_handling("accept() error");


        // def
        char file_name[BUF]; // BUF 1024
        char file_str[BUF];

        int cnt, cnt_fread;
        int cnt_w;

        ssize_t ck_cnt_r_len, ck_str_r_len;
        ssize_t ck_cnt_w_len, ck_str_w_len;


        // FILE*
        FILE* text_fp;
        text_fp = fopen("hi.txt", "rt");

        while( ( cnt_fread = fread(file_str, sizeof(char), sizeof(file_str), text_fp) ) > 0)
        {
                printf("%s", file_str);
                // write cnt
                cnt_w = strlen(file_str);
                ck_cnt_w_len = write(clnt_sock, &cnt_w, sizeof(int));

                // write str
                ck_str_w_len = write(clnt_sock, file_str, cnt_w);
        }
        shutdown(clnt_sock, SHUT_WR);
        shutdown(serv_sock, SHUT_WR);

        // new def
        char recv_msg[BUF];
        int len;

        read(clnt_sock, recv_msg, sizeof(recv_msg)-1);
        printf("%s", recv_msg);



        close(clnt_sock);
        close(serv_sock);

        return 0;
}


void error_handling(char* message)
{
        fputs(message, stderr);
        fputc('\n', stderr);
        exit(1);
}

피드백

  • 마지막에 소켓을 완전히 종료시킬 때는 close를 써야만한다.

C 카테고리 내 다른 글 보러가기

댓글 남기기