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

와나진짜

,

UDP(User Datagram Protocol)

TCP가 신뢰성에 기반을 둔 전송 계층 프로토콜이라면 UDP는 빠른 데이터 전송 위주의 프로토콜입니다. 속도를 떨어뜨리는 요소를 없애 TCP처럼 각종 흐름제어, 오류제어, 혼잡 제어를 하지 않습니다. 따라서 UDP는 비신뢰성(Unreliable) 프로토콜입니다.

 

이 UDP 프로토콜, 저와 같은 세대시라면 어디서 많이 보셨죠? 저는 뮤컨을 위해서 스타크래프트의 UDP는 필수였습니다. 이렇게 데이터는 그다지 중요하지 않으면서 속도가 빠른 데이터 전송이 필요할때 UDP를 사용합니다.

 

데이터 전송과정을 보게 되면 이렇습니다.

만일 송신에서 데이터를 1, 2, 3 순으로 전송했다고 할때 네트워크의 사용량에 따라 어떤 데이터그램은 늦게 도착할 수 있습니다. 3번 데이터그램이 가장 좋은 경로를 타고 와서 먼저 도착하게 됩니다. 그 이후에는 1번 데이터그램이 도착하겠네요. 마지막으로 가장 부하가 많은 경로를 타고 들어온 2번 데이터그램이 도착합니다. 

결국 수신자는 3, 1, 2 데이터그램 순으로 전달받게 됩니다. 연결과정 자체를 갖지 않기 때문에 데이터의 순서를 보장하지 않습니다.

 

 

 

헤더 정보(UDP Header)

빠른 전송을 요구할때 쓰이는 UDP는 헤더의 구조가 너무나 간단합니다. 헤더는 단 4가지 정보만 존재합니다.

 

UDP 헤더 정보

너무 간단합니다.

- Source Port : 송신지의 Port 번호입니다.

- Destination Port : 목적지의 Port 번호입니다.

- Total Length : 헤더를 포함한 전체 데이터그램의 크기를 의미하는 필드입니다.

- Checksum : 데이터그램의 오류를 확인하기 위한 필드입니다.

 

UDP 특징

UDP의 특징을 요약하면 다음과 같습니다.

1) TCP와 같이 연결을 성립하는 3-way-handshake와 같은 과정이 없습니다. 따라서 비연결형인 특징을 갖습니다. 연결을 확립하는 TCP는 회선을 연결한듯 정확한 연결을 요구하므로 가상회선 방식이라 하며 UDP는 그런 과정없이 데이터를 쪼개 보내므로 데이터그램 방식이라 합니다.

2) 따라서 데이터의 순서, 유실을 보장하지 않고, 데이터 수신 실패시 재전송을 요구하지 않습니다. 데이터를 수신할때 순서가 뒤바뀔 수도 있습니다. 이는 곧 비신뢰성을 의미합니다. 

3) 데이터를 일방적으로 수신받기 때문에 속도가 매우 빠릅니다.

4) 데이터의 checksum으로 최소한의 오류만을 검출합니다. 그러니까 받는 데이터는 검사를 하지만 유실된 데이터는 모른다는 것이지요.

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

TCP/IP

인터넷 프로그램들이 서로 통신을 하는데 있어서 여러 프로토콜이 있습니다. 인터넷 프로토콜에서 가장 많이 사용하는 대표적인 프로토콜은 여러분들도 많이 아시다시피 IP입니다. 여기서 중요한 것은 TCP/IP는 계층이 아니라 프로토콜이라는 사실이라는 사실을 주의해주세요.


TCP/IP는 OSI7 계층과는 조금은 다른 TCP/IP의 구조적인 계층 위에서 동작합니다.


지난 번에 OSI7 계층에 대해서 알아보았는데요. TCP/IP 계층은 OSI7계층과 비교하여 어떤 점이 다른지 살펴보는 시간을 가져보도록 하지요.






OSI7 계층과는 조금은 다른 모습을 볼 수 있습니다. 보세요.

우선 계층의 수 부터가 다릅니다. OSI는 7계층인데 반해 TCP/IP 계층은 4계층이 전부라는 것을 알 수 있습니다.


이제 조금 더 세세하게 살펴보도록 하지요.




1. 네트워크 인터페이스 계층 (Network Interface Layer)

이 계층은 Node-To-Node간의 신뢰성 있는 데이터 전송을 담당하는 계층입니다. OSI7 계층의 물리 계층과 데이터링크 계층의 역할을 바로 이 계층이 담당하는 것으로 볼 수 있네요.


따라서 MAC주소가 이 계층에서 사용됩니다. MAC주소는 OSI7 계층에서 데이터링크 계층의 주소였죠?? 


네트워크 인터페이스 계층이 바로 데이터링크 계층까지 담당하니까 MAC 어드레스가 사용되는 겁니다.


혹시 랜카드라고 들어보셨나요? 바로 이거말이에요.




정확한 명칭은 NIC라고 하여 Network Interface Card입니다. 바로 이 랜카드가 있어야만 네트워크 통신을 할 수 있는데, 이름에서도 알 수 있듯이 네트워크 인터페이스 계층에서 동작하는 장비입니다.


주요 프로토콜을 무엇이 있을까요?

LAN상에서는 Ethernet, TokenRing, FDDI 등이 있으며 WAN 상에서는 X.25, Frame Relay, PPP 등이 있습니다.


2. 인터넷 계층 (Internet Layer)

OSI7계층의 네트워크 계층을 담당하는 계층입니다. OSI7 계층처럼 호스트간의 라우팅을 담당하지요. 


인터넷 계층에서 동작하는 프로토콜에는 무엇이 있을까요? 대표적인 몇가지 프로토콜을 살짝 알아보도록 합시다.


IP(Internet Protocol) : 비신뢰성, 비연결지향 데이터그램 프로토콜입니다. 

ARP(Address Resolution Protocol) : 주소변환 프로토콜입니다. IP주소를 MAC주소로 변환하는 프로토콜이지요.

RARP(Reverse ARP) : 반대로 MAC주소로 IP주소를 찾는 프로토콜입니다.

ICMP(Internet Control Message Protocol) : 상태 진단 메시지 프로토콜인데요. 이 프로토콜을 이용하는 대표적인 프로그램이 ping입니다.

IGMP(Internet Group Message Protocol) : 멀티캐스트용 프로토콜입니다.




3. 전송 계층 (Transport Layer)

OSI7 계층의 전송계층과 같습니다. 프로세스간의 신뢰성 있는 데이터 전송을 담당하는 계층입니다.


process-to-process 전송을 담당하기 위해서는 논리적 주소가 필요한데요. process가 사용하는 포트 번호를 그 논리적 주소로 사용합니다.


전송 계층에서 프로토콜은 무엇이 있을까요?


TCP (Transmission Control Protocol) : 신뢰성있는 연결지향형 프로토콜입니다. 신뢰성있다는 말은 그 페킷에 대한 오류처리나 재전송따위로 에러를 복구하는 것을 말합니다. 그때문에 TCP의 헤더에 붙는 정보가 많습니다.

UDP (User Datagram Protocol) : 비신뢰성 비연결형 프로토콜입니다. 페킷을 잃거나 오류가 있어도 대처하지 않는 것을 말합니다. 따라서 UDP헤더는 간단한 구조를 갖고 있습니다.



4. 응용 계층 (Application Layer)

사용자와 가장 가까운 계층입니다. OSI7계층의 5계층부터 7계층까지의 기능을 담당하고 있지요.

서버나 클라이언트 응용 프로그램이 이 계층에서 동작합니다. 우리가 알고 있는 브라우저나 텔넷같은 서비스가 이 계층에 동작하며, 동작하기 위해서는 전송계층의 주소, 즉 포트번호를 사용합니다.


이를테면 http는 포트번호 80번을 사용하지요.


역시 프로토콜은 무엇이 있나 살펴볼까요?


HTTP (Hyper-Text Transfer Protocol) : TCP기반의 프로토콜로 포트번호 80번을 사용합니다.

Telnet : TCP 포트번호 23번을 사용합니다. 원격 터미널을 접속할때 이 포로토콜을 사용합니다.

SSH (Secure Shell) : 텔넷과 같은 서비스는 보안에 취약합니다. 비밀번호가 암호화되지 않아 그대로 노출이 되기 때문이지요. 이것을 보완한것이 SSH입니다. 포트번호 22번을 사용합니다.

FTP(File Transfer Protocol) : 파일 전송 프로토콜입니다. 파일을 받거나 올릴때 FTP를 사용하지요. FTP는 파일을 올리거나 내려받을때 신뢰성을 중요시하기 때문에 TCP에서 동작하구요. 2개의 포트를 사용합니다. 

TCP 포트 20번은 데이터 전송을 위한 용도, TCP 포트 21번은 제어용으로 사용합니다.

SMTP (Simple Mail Transfer Protocol) : 메일 전송 프로토콜입니다. TCP 상에서 동작하며 포트는 25번을 사용합니다.

POP3 (Post Office Protocol Version3) : 메일 수신용 프로토콜입니다. 아웃룩같은 프로그램이 POP3라는 프로토콜을 사용하여 동작합니다. TCP 포트 110번을 사용합니다.

DNS (Domain Name System) : 도메인명에 대한 호스트 정보를 제공해줍니다. 기본적으로 UDP상에서 동작합니다. 기본적으로 실패하면 다시 한번 요청하면 되며 그렇게 중요한 정보가 아니기 때문이죠. 하지만 신뢰성을 요할 경우에는 TCP상에서도 동작합니다. 데이터의 길이가 길 경우같은 때 TCP 기반으로 동작할 수 있습니다.

UDP, TCP 포트 53번을 사용합니다.




이와 같이 포트번호가 특정 프로토콜이 사용해서 우리가 쓸 수 없는 포트들이 있습니다. 이런 포트들을 well-known port라고 합니다.


프로토콜 헤더 정보를 잘 읽고 분석할 수 있다면 네트워크를 더 잘 이해할 수 있을 겁니다.


따라서 다음 시간에는 헤더를 보고 무슨 정보가 있는지 살펴보는 기회를 갖도록 하겠습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,