Datagram Socket 통신

Stream Socket은 연결 지향형이고 Datagram은 비연결형의 Socket 통신 방법입니다. 신뢰성은 그만큼 떨어지지만 단순하고 빠른 전송을 위한 것으로 UDP 프로토콜을 사용합니다. Stream Socket과는 다르게 Datagram 소켓을 사용하여 간단히 Server - Client 통신 소스코드를 짜보도록 합시다. Stream Socket은 서버와 클라이언트 사이에 연결을 맺어주느라 소스 코드의 길이가 길지요. 지금 볼 datagram socket은 이러한 연결(Connection) 과정이 생략이 됩니다.

그러니까 Stream Socket과는 다르게 listen, accept를 datagram socket에서 사용할 필요가 없어졌습니다. 그래서 다행이라고 할까요? Stream socket보다는 코드의 길이가 짧습니다.

서버 코드

아래에서 구현한 소스코드는 client의 메시지를 받아서 그대로 client쪽으로 돌려주는 echo 서버의 소스코드입니다.

//dgram-server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#define PORT 12346
#define BUF_SIZE 128

void err_exit(const char *err){
        perror(err);
        exit(1);
}

int main(void){
        int socket_fd, n;
        struct sockaddr_in host_addr, client_addr;
        socklen_t size;
        char buffer[BUF_SIZE] = {0,};

        //Datagram socket 생성
        socket_fd=socket(PF_INET,SOCK_DGRAM,0);
        if(socket_fd < 0) err_exit("socket error ");

        //server ip와 bind하기 위해 주소 설정
        memset(&host_addr, 0, sizeof(host_addr));
        host_addr.sin_family=AF_INET;
        host_addr.sin_port=htons(PORT); 
        host_addr.sin_addr.s_addr=0;    //자신의 주소로 자동으로 설정 

        //socket과 host_addr과 bind
        if(bind(socket_fd,(struct sockaddr *)&host_addr,sizeof(struct sockaddr)) < 0)
                err_exit("bind error ");

        //Client의 메시지를 그대로 다시 돌려준다. 
        memset(&client_addr, 0, sizeof(struct sockaddr));
        while(1){
                size = sizeof(struct sockaddr);

                //accept에서 client_addr을 얻어온 stream socket방식과 다르게
                //accept가 없는 datagram socket은 
                //recvfrom에서 client 정보를 얻어올 수 있다. 
                n = recvfrom(socket_fd, buffer, BUF_SIZE, 0,
                                (struct sockaddr*)&client_addr, &size);

                if(n < 0) err_exit("recvfrom error ");

                //Client의 접속 정보를 출력
                printf("Client Info : IP %s, Port %d\n", 
                        inet_ntoa(client_addr.sin_addr),
                        ntohs(client_addr.sin_port));


                //quit라는 메시지를 받으면 종료 
                if(!strcmp(buffer,"quit") || n == 0) break;

                //보낼 client_addr 객체까지 있어야 전달 가능
                n = sendto(socket_fd, buffer, n, 0,
                                (struct sockaddr*)&client_addr, sizeof(struct sockaddr));
                if(n < 0)err_exit("send error ");

        }

        shutdown(socket_fd, SHUT_RDWR);
        return 0;

}

 

이전 stream socket과 다른 점이 있다면, SOCK_STREAM이 아닌 SOCK_DGRAM을 사용한다는 점입니다. 

socket_fd=socket(PF_INET,SOCK_DGRAM,0);

stream socket은 send, recv를 사용하였는데, Datagram socket에서는 sendto, recvfrom을 사용하는 것을 알 수 있네요.

이 두 함수들의 끝 2개는 sockaddr의 구조체와 크기입니다. Server는 accept과정이 없기 때문에 client의 주소를 알 방법이 없습니다. 그래서 다시 echo할 주소가 없다는 거죠. recvfrom은 메시지를 받을때 송신자의 주소를 가져오기 위해서 사용합니다. sendto도 마찬가지로 보낼 곳을 지정할때 사용합니다. 이 주소 정보가 없다면 아래와 같은 관계가 성립됩니다.

send(sockfd, buf, len, flags) -> sendto(sockfd, buf, len, flags, NULL, 0)
recv(sockfd, buf, len, flags) ->  recvfrom(sockfd, buf, len, flags, NULL, NULL)

 

클라이언트 코드

이에 대응하는 client의 소스코드입니다. 

//dgram-client.c
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h> 

#define BUF_SIZE 128

void err_exit(const char *err){
        perror(err);
        exit(1);
}

int main(int argc, char *argv[])
{
        int sockfd = 0, n = 0;
        uint16_t port;
        char buffer[BUF_SIZE] = {0,};
        struct sockaddr_in server_addr;
        int size; 

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

        port = atoi(argv[2]);

        //Internet용 socket(Datagram)을 연다.
        if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
                err_exit("socket error ");

        //server_addr의 구조체를 깔끔히 0으로 도배 
        memset(&server_addr, 0, sizeof(server_addr)); 
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(port); 
        //127.0.0.1은 본인의 IP이다. 
        inet_aton(argv[1], (struct in_addr*)&server_addr.sin_addr);


        while(1){
                while(1){
                        printf("message:");
                        fgets(buffer, BUF_SIZE, stdin);
                        if(strlen(buffer) >= 100)
                                printf("message too large\n");
                        else break;

                };
                // \n을 \0로 처리 
                buffer[strlen(buffer)-1] = '\0';

                size = sizeof(struct sockaddr);
                //서버에게 메시지 전송
                //connect 콜이 없어서 server ip로 직접 보낸다.
                n = sendto(sockfd, buffer, strlen(buffer)+1, 0,
                                (struct sockaddr*)&server_addr, sizeof(struct sockaddr));
                
                if(n < 0) err_exit("send error ");
                if(!strcmp(buffer, "quit")) break;

                //서버로부터 에코된 메시지를 받음
                n = recvfrom(sockfd, buffer, BUF_SIZE, 0,
                                (struct sockaddr*)&server_addr, &size);

                if(n < 0) err_exit("recv error ");

                printf("%s\n", buffer);


        }

        shutdown(sockfd, SHUT_RDWR);
        return 0;
}

결과 화면

Server Client
# ./server
Client Info : IP 127.0.0.1, Port 60112
Client Info : IP 127.0.0.1, Port 60112
Client Info : IP 127.0.0.1, Port 60112
# ./client 127.0.0.1 12346
message:hello datagram server~~
hello datagram server~~
message:good good
good good
message:quit

 

여기까지 간단하게 UDP를 사용하는 server - client 통신 구현을 해보았습니다. 여러 클라이언트를 받기 위해서는 다중 프로세스, 다중 쓰레드, 다중입출력을 활용해야겠죠. 다중입출력 방식 중 하나인 epoll에 관한 설명도 저의 블로그에 있으니 확인해보세요.

https://reakwon.tistory.com/236

 

[LINUX] epoll의 개념과 이를 활용한 다중입출력 방식의 서버, 클라이언트

epoll 뿐만 아닌 다중 입출력의 설명과 코드를 아래의 note에서 확인하실 수 있습니다. https://reakwon.tistory.com/233 리눅스 프로그래밍 note 배포 티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포

reakwon.tistory.com

 

반응형
블로그 이미지

REAKWON

와나진짜

,