UDP로 가변 구조체 보내기

theone3의 이미지

통신 프로그램을 하다보면 가변 구조체(header + body)를 보내는 경우가 많습니다.
지금까지는 다 TCP로 해서 별 무리없이 했는데, UDP로 하니 잘 안되는군요.
검색을 해보니, UDP를 통해서 고정길이 구조체를 보내는 예제는 있더군요.
고정길이 구조체는 별 문제가 없으리라 생각됩니다.

보내는 구조체가 header와 body로 구성되고, header에 body의 길이가 있습니다.
보내는 쪽은 header와 body를 한번에 보내고,
받는쪽은 select에서 FD_ISSET을 타고 들어와서,처리를 하게 됩니다.
그런데 처음 header만큼 recvfrom을 호출하면, 잘 받아지는데,
이제 header를 통해서 body의 길이를 알았으므로,
body를 recvfrom를 하려고 하면 프로그램이 block이 되는군요.

원래 UDP에선느 이런 식으로 프로그램을 하면 안되는 것인지,
아니면 다른 원인이 있는지 알려주세요.

chadr의 이미지

UDP는 다음과 같은 특성이 있습니다.

1.패킷 손실
2.패킷 중복
3.패킷 순서 뒤죽박죽

그리고 UDP는 TCP와는 다르게 스트림 방식이 아닌 데이터그램 방식입니다.
즉.. 100바이트를 상대에게 보냈을 때 100바이트가 가거나 안가거나 둘중에 하나만 되지 50바이트 가고 잠시후 다시 나머지 50바이트가 가지는 않습니다. 그렇기 때문에 헤더+바디로 패킷이 나뉠 필요는 없으며 그냥 통채로 보내면 상대에서 받으면 받고 못받으면 못받습니다.

그리고 위의 특징중 3번과 같은 특징이 있으므로 헤더와 바디를 각각 sendto를 하게 되면 서로 뒤바뀌어서 올 수 있으므로 대략 난감한 상황이 발생 할 수 있겠습니다.

그렇기 때문에 가변길이 패킷이라면 패킷의 맨 앞에 패킷의 길이를 기록 하시고 대략 가장 큰 패킷의 크기만큼 메모리를 잡으신 다음에 거기다가 데이터를 저장 하시면 패킷의 맨 앞부분을 읽어서 실제 패킷의 길이를 알아올수 있으므로 원하시는 구현이 될듯합니다.

위의 특징들 때문에 udp로는 중요한 데이터는 보내서는 안되고 중간에 몇개 패킷이 빠져도 전체적인 시스템 구동에 문제가 없는 구조일 때만 사용해야합니다.

만약에 udp를 이용해서 위의 3가지 특징을 커버하도록 할려면 그냥 tcp를 쓰는게 낫습니다. 위의 3가지를 커버하도록 구현하면 사실 그게 tcp이거든요..
-------------------------------------------------------------------------------
It's better to appear stupid and ask question than to be silent and remain stupid.

-------------------------------------------------------------------------------
It's better to appear stupid and ask question than to be silent and remain stupid.

theone3의 이미지

네. 답변을 읽어보니, 제가 특별히 잘못한 것은 없는 것으로 생각됩니다.
보내는 쪽에서는 헤더+바디를 통째로 보내고,
받는 쪽에서는 헤더(고정길이)를 읽고, 헤더에서 바디의 length를 알아내어,
바디를 읽어들입니다.
제가 읽어들인다고 쓴 것이 recvfrom인데 chadr님께서 말씀하시는 것은
가장 큰 패킷의 크기만큼 버퍼를 만들고,
그 크기만큼 recvfrom을 하고,
읽은 버퍼에서 헤더를 읽고, 헤더에서 바디의 length를 알아내어
바디를 읽어들이라는 말로 이해가 되는군요.

당신은 사랑받기 위해 태어난 사람.

theone3의 이미지

제가 말로만 설명을 해서 아무래도 읽으시는 분이 이해가 잘 안될 거라 생각됩니다.
그래서 JOINC에 있는 예제를 제가 원하는대로 변경을 했습니다.

아래는 UDP 서버입니다.

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
 
struct data
{
    int length;
    char str[512];
};
 
int main(int argc, char **argv)
{
    int sockfd;
    int clilen;
    int state;
    int n;
    int sum;
    struct data test_data;
    int tlength = -1;
    char buf[1024];
 
    struct sockaddr_in serveraddr, clientaddr;
 
    clilen = sizeof(clientaddr);
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket error : ");
        exit(0);
    }
 
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serveraddr.sin_port = htons(1234);
 
    state = bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
    if (state == -1)
    {
        perror("bind error : ");
        exit(0);
    }
 
    while(1)
    {
        n =recvfrom(sockfd, (void *)&tlength, sizeof(tlength), 0, (struct sockaddr *)&clientaddr, &clilen);
        printf("\ntlength[%d]", tlength);
        if(n!=sizeof(tlength))
        {
            printf("\nlength error");
        }
        memset(buf, 0x00, sizeof(buf));
        n =recvfrom(sockfd, (void *)&buf, tlength, 0, (struct sockaddr *)
&clientaddr, &clilen);
        printf("\nbuf[%s]", buf);
    }
    close(sockfd);
}

아래는 client입니다.

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
 
typedef struct DATA
{
    int length;
    char str[512];
} data;
 
int main(int argc, char **argv)
{
    int sockfd;
    int clilen;
    int state;
    char buf[1024];
    data * pdata = (data *)buf;
    int total_length = -1;
 
    struct sockaddr_in serveraddr;
   memset(buf, 0x00, sizeof(buf));
 
    clilen = sizeof(serveraddr);
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket error : ");
        exit(0);
    }
 
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serveraddr.sin_port = htons(1234);
 
    strcpy(pdata->str, "This is Test Data");
    pdata->length = strlen(pdata->str);
    total_length = sizeof(pdata->length) + pdata->length;
 
    printf("\npdata->length = [%d]", pdata->length);
    printf("\npdata->str = [%s]", pdata->str);
    printf("\ntotal_length = [%d]", total_length);
 
    sendto(sockfd, (void *)&buf, total_length, 0, (struct sockaddr *)&serveraddr, clilen);
 
 
    close(sockfd);
}

위에 코드를 실행시켜 보면, client는 21(4:length, 17:str)바이트를 한번에 전송합니다.
반면 server는 int크기만큼 읽어 들이고,
그 int값을 보고 str을 읽어들이게 됩니다.
그런데 생각대로 실행이 되지 않는군요.
(첫번째 recvfrom은 값을 제대로 읽어 들이지만(17),
두번째 recvfrom이 제대로 작동을 하지 않습니다.)
이전에 제가 얘기한대로면 int가 헤더가 되고, str이 바디가 되는 형식입니다.
TCP에선 가변 길이의 경우 이런 식으로 주로 처리를 했습니다만,
UDP에서는 이런 방식으로 할수 없는지 알고 싶습니다.
많은 분들의 지도 바랍니다.

당신은 사랑받기 위해 태어난 사람.

kslee80의 이미지

UDP 의 경우,
client 에서 패킷을 sendto() 를 통해서 보내고 나서
받는 측에서 recvfrom() 으로 패킷길이 미만을 받더라도 나머지 데이터가 소켓버퍼에 남아있지 않습니다.

즉, 예시처럼 client 에서 21 byte (length 4, str 17) 를 보내고,
Server 에서 길이를 알기 위해서 첫 4 byte 를 recvfrom() 해 버리면
21 byte 전체가 소켓버퍼에서 사라집니다
따라서, 두번째 recvfrom 은 정상적으로 동작하는 셈입니다.

제대로 처리하기 위해서는,
충분히 큰 버퍼를 만들어서 recvfrom() 으로 패킷 전체를 담은 다음에 처리해야겠죠.

theone3의 이미지

감사합니다.
딱 제가 원하는 답이군요.
stevens책에는 이런 이야기가 나오겠죠? ^^
나중에 정독한번 해봐야겠습니다.

당신은 사랑받기 위해 태어난 사람.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.