소켓 통신을 하다 보면 여러 문제점을 접하게 되는데, 그 중에 하나가 TCP/IP 통신에서 상대방이 제대로 수신하지 못 하면 데이터 전송 중에 블록이 된다는 점입니다. 정확히는 데이터 전송 함수에서 블록됩니다. 저는 소켓 통신뿐만 아니라 시리얼 통신, 미디어, 화면 출력까지 모두 write()함수를 사용하는데, 전송 버퍼에 여유가 없다면, 상대방이 데이터를 가져가서, 그래서 버퍼에 여유가 생기면, 그때서야 복귀됩니다.

write() 함수에서 블록

시리얼 통신과 비교해서 글을 적어 보겠습니다. 시리얼 통신은 상대방이 자료를 수신하든 못하든 계속 블록 없이 write() 함수를 사용할 수 있습니다. 그러나 TCP/IP 통신에서는 상대방의 수신 여부에 영향을 받습니다.

 

이런 이유는 TCP/IP 통신에서 자료를 전송할 때, 시리얼처럼 무책임하게 전송하는 것이 아니라 상대방의 상태를 확인하면서 전송하기 때문에 발생합니다. TCP/IP 프로토콜에는 자료 전송뿐만 아니라 상대방의 상태를 확인하는 부분이 들어 있어서, 상대가 자료를 수신할 수 있는지, 즉, 상대쪽의 수신 버퍼가 자료를 수신할 수 있을 만한 여유가 있다면, 그때 전송합니다. 그렇지 않으면 전송하지 않게 되는데, 버퍼가 가득차게 되면, wirte() 함수는 버퍼가 빌 때까지 대기하게 됩니다.

 

제 욕심 같아서는 블록되지 말고 에러 코드를 반환하고 그냥 복귀되었으면 좋겠는데, 이게 아니라는 것이죠. 프로그램을 작성하다 보면 제일 답답할 때가, 대책없이 블록될 때 입니다.

fdopen() 함수로 해결?

그렇다면 open() 말고 표준 출력함수를 사용하면 어떨까요? fdopen() 함수를 사용하면 소켓 디스크립터에서 FILE * 포인터를 구할 수 있습니다. 그래서 fputs() 와 같은 표준 출력 함수를 사용할 수 있게 되는데, 이 함수를 사용하게 되면, 블록없이 에러값을 반환하고 복귀되지 않을까요?

 

결론은? 표준 출력함수도 블록되기는 마찬가지 입니다.

 

그렇다면 fseek()나 ftell() 함수를 이용하여 미리 출력 버퍼의 남은 용량을 구할 수 있지 않을까요? 해 보시면 아시겠습니다만, 구할 수 없었습니다. 남은 버퍼량이 전송량보다 적으면 write() 함수 호출을 피하면 되는데, 이게 안 되네요. 흠~

poll()의 POLLOUT ?

poll() 함수를 이용하면 어떨까요? 출력이 가능할 때만 데이터를 전송하는 거죠. 그러나 이것도 해결 방법이 못됩니다. 왜냐하면 poll()의 POLLOUT은 버퍼에 1 바이트만 비어도 발생하거든요. 문제는 제가 전송하려는 데이터 크기가 출력 버퍼의 빈 공간보다 1 바이트라도 많으면 블록되고 맙니다. 그렇다고 출력 버퍼의 빈 용량을 구할 방법은 없고, 그래서 결국 이 방법으로도 해결할 수 없었습니다.

send() 함수로 간단히 해결

해결 방법은 의외로 간단합니다. send() 함수를 사용하면 됩니다. send() 함수에는 MSG_DONTWAIT라는 옵션을 사용할 수 있기 때문이죠. send() 함수의 자세한 설명은 "C 라이브러리 함수" 게시판에 자세히 올려져 있습니다.

형태 int send(int s, const void *msg, size_t len, int flags);
인수
int s : 소켓 디스크립터
void *msg : 전송할 데이터
size_t len : 데이터의 바이트 단위 길이
int flags : 아래와 같은 옵션을 사용할 수 있습니다.
flags 옵션 설명
MSG_DONTWAIT 전송할 준비가 전에 대기 상태가 필요하다면 기다리지 않고 -1을 반환하면서 복귀
MSG_NOSIGNAL 상대방과 연결이 끊겼을 때, SIGPIPE 시그널을 받지 않도록 합니다.
반환
-1 이외 : 실제 전송한 바이트 수
-1 : 실패

 

MSG_DONTWAIT를 사용해서 send() 함수를 호출하고, 반환된 값으로 전송 여부를 확인하는 것이죠. UDP/IP 통신뿐만 아니라 TCP/IP에서도 write() 함수 보다는 send() 함수를 애용해야 겠습니다. 물론 저 개인적인 생각입니다.

예제 프로그램

예제가 필요 없는 글이겠습니다만, 제가 테스트하면서 사용한 코드를 올립니다. 예제는 서버와 클라이언트로 나누었으며, 서버는 소켓을 열어 놓아도 키를 눌러 주어야 데이터를 읽도록 했습니다. 키를 누르기 전까지는 소켓 데이터를 읽지 않기 때문에, 누군가 서버에 접속할 수는 있어도 계속 송신만 한다면 에러가 발생할 것입니다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define  BUFF_SIZE   1024

int   main( void)
{
    int   server_socket;
    int   client_socket;
    int   client_addr_size;

    struct sockaddr_in   server_addr;
    struct sockaddr_in   client_addr;

    char   buff_rcv[BUFF_SIZE+5];
    char   buff_snd[BUFF_SIZE+5];
    int    ndx;



    server_socket  = socket( PF_INET, SOCK_STREAM, 0);
    if( -1 == server_socket)
    {
        printf( "server socket 생성 실패n");
        exit( 1);
    }

    memset( &server_addr, 0, sizeof( server_addr));
    server_addr.sin_family     = AF_INET;
    server_addr.sin_port       = htons( 4000);
    server_addr.sin_addr.s_addr= htonl( INADDR_ANY);

    if( -1 == bind( server_socket, (struct sockaddr*)&server_addr, sizeof( server_addr) ) )
    {
        printf( "bind() 실행 에러n");
        exit( 1);
    }

    while( 1)
    {
        if  ( -1 == listen(server_socket, 5))
        {
            printf( "대기상태 모드 설정 실패n");
            exit( 1);
        }

        client_addr_size  = sizeof( client_addr);
        client_socket     = accept( server_socket, (struct sockaddr*)&client_addr, &client_addr_size);

        if  ( -1 == client_socket)
        {
            printf( "클라이언트 연결 수락 실패n");
            exit( 1);
        }

        while ( 1)
        {
            getchar();
            for ( ndx = 0; ndx < 1000; ndx++)
                read ( client_socket, buff_rcv, BUFF_SIZE);
            printf( "receive: %sn", buff_rcv);
        }

    }
    close( client_socket);
}

이번에는 클라이언트 쪽 프로그램입니다. 서버에 접속하고 데이터를 전송합니다. 클라이언트 예제에서는 send() 함수를 사용하고 있습니다. 자료를 계속 전송하다가 전송할 수 없는 상태가 되더라도 블록되지 않고 복귀됩니다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define  BUFF_SIZE   1024

int   main( int argc, char **argv)
{
    char   *str_test = "FF는 FALiNUX의 포럼입니다. ";
    int     client_socket;
    int     ndx;
    int     n_size;
    struct  sockaddr_in   server_addr;
    char    buff[BUFF_SIZE+5];

    client_socket  = socket( PF_INET, SOCK_STREAM, 0);
    if( -1 == client_socket)
    {
        printf( "socket 생성 실패n");
        exit( 1);
    }

    memset( &server_addr, 0, sizeof( server_addr));
    server_addr.sin_family     = AF_INET;
    server_addr.sin_port       = htons( 4000);
    server_addr.sin_addr.s_addr= inet_addr( "127.0.0.1");

    if( -1 == connect( client_socket, (struct sockaddr*)&server_addr, sizeof( server_addr) ) )
    {
        printf( "접속 실패n");
        exit( 1);
    }
    ndx  = 0;
    while ( 1)
    {
        n_size  = send( client_socket, str_test, strlen( str_test), MSG_DONTWAIT);
        if ( 0 < n_size )
            printf( "%d -- sending-size:%d\n", ndx++, n_size);
        else
        {
            printf( "전송을 못했네요. 1초간 대기\n");
            sleep( 1);
         }

    }

    close( client_socket);

    return 0;
}

실행하는 방법은 서버를 실행하고 클라이언트 프로그램을 실행합니다. 서로 다른 터미널에서 실행하면 편리합니다. 서버를 실행한 상태에서 클라이언트를 실행하면, 무조건 클라이언트에서 서버로 자료를 전송하는데, 얼마 안 있어 전송 에러가 발생합니다. 이때 서버에서 키를 눌러 자료를 읽어 들이면, 클라이언트 쪽에서 다시 자료를 전송하는 것을 보실 수 있습니다.

 

그러나 send() 함수를 사용하지 않고 write() 함수를 사용하면, 클라이언트 프로그램이 멈추어 버리는 것을 보실 수 있습니다.


자료출처 : http://forum.falinux.com/zbxe/?mid=network_programming&document_srl=520963

'Tips & Tech > Network' 카테고리의 다른 글

UDP/IP 프로그래밍  (0) 2012.02.27
TCP/IP 프로그래밍 방법  (0) 2012.02.27

1. 명명규칙(naming convention)

- 대소문자가 구분되며 길이에 제한이 없다.

- 예약어를 사용해서는 안 된다.

- 숫자로 시작해서는 안 된다.

- 특수문자는 '_'와 '$'만을 허용한다.

- 클래스 이름의 첫 글자는 항상 대문자로 한다.

- 여러 단어로 이루어진 이름은 단어의 첫 글자를 대문자로 한다.

- 상수의 이름은 모두 대문자로 한다. 여러 단어로 이루어진 경우 '_'로 구분한다.


2. 변수의 타입

- 기본형 : 계산을 위한 실제 값을 저장한다.(boolean, char, byte, short, int, long, float, double)

- 참조형 : 객체의 주소를 저장한다. (4Bytes)

- 문자열 : 문자열 + any type -> 문자열 + 문자열 -> 문자열

'Tips & Tech > JAVA' 카테고리의 다른 글

숫자 중간에 ',(콤마)'찍는 방법  (0) 2012.05.03

%a          부동소수점수, 16진수, p-표기

%A          부동소수점수, 16진수, P-표기

%c          하나의 문자

%d          부호 있는 10진 정수 

%e          부동소수점수 e-표기 

%E          부동소수점수 E-표기

%f           부동소수점수, 10진수 표기 

%g          값에 따라 %f, %e 사용, 지수부가  -4보다 작거나, 정밀도보다 크거나 같으면  %e 사용 

%G          값에 따라 %f, %E 사용, 지수부가  -4보다 작거나, 정밀도보다 크거나 같으면  %E 사용

%i           부호있는 10진정수 (%d와 같다)

%o          비부 없는 8진정수

%p          포인터

%s          문자열

%u          부호없는 10진 정수 

%x          부호없는 16진 정수 10진 숫자 0f 사용 

%X          비부없는 16진 정수 10진 숫자 0F 사용

%%         퍼센트 기호 출력


%Nd : 정수 유형의 데이터를 N칸에 맞게 10진수로 출력

%No : unsigned int 유형의 데이터를 N칸에 맞게 8진수로 출력

%Nx : unsigned int 유형의 데이터를 N칸에 맞게 16진수로 출력

%Nu : unsigned int 유형의 데이터를 N칸에 맞게 10진수로 출력

%Nhd : short int 유형의 데이터를 N칸에 맞게 10진수로 출력

%Nho : unsigned short int 유형의 데이터를 N칸에 맞게 8진수로 출력

%Nhx : unsigned short int 유형의 데이터를 N칸에 맞게 16진수로 출력

%Nhu : unsigned short int 유형의 데이터를 N칸에 맞게 10진수로 출력

%Nld : long int 유형의 데이터를 N칸에 맞게 10진수로 출력

%Nlo : unsigned long int 유형의 데이터를 N칸에 맞게 8진수로 출력

%Nlx : unsigned long int 유형의 데이터를 N칸에 맞게 16진수로 출력

%Nlu : unsigned long int 유형의 데이터를 N칸에 맞게 10진수로 출력

 

%f : float유형의 데이터를 소수점 형태로 출력 (4byte)

%e : float유형의 데이터를 지수형태로 출력

%lf : double 유형의 데이터를 소수점 형태로 출력 (8byte)

%le : double 유형의 데이터를 지수형태로 출력

%Lf : long double 유형의 데이터를 소수점 형태로 출력 (10byte)

%Le : long double 유형의 데이터를 지수형태로 출력


%Nf : 소수점 이하 N개의 숫자만을 출력

 

%s : string형태

%p : pointer형태

%c : char형태


자료출처 : printf() 함수 포맷 지정자|작성자 구구님

http://blog.naver.com/PostView.nhn?blogId=googunet&logNo=10028895422&redirect=Dlog&widgetTypeCall=true

+ Recent posts