프로그래밍/VC++ 개발 코딩

BSD Socket 사용방법

강태공97 2014. 1. 17. 21:54

0.시작하면서...


BSD 소켓 프로그래밍은 MFC 소켓에 비하면 쓰기가 까다롭고 알아야할 부분이 많다. 특히나 윈도우 프로그래밍을 하면 MFC 소켓에 익숙해지기때문에 까먹기가 십상이다.


이번에 NDS 소켓 프로그래밍을 하면서 우연히 다시 볼 기회가 생겨 정리한다.


 


1.참고 함수들


1.1 select 함수


Single Thread로 Multi-Socket을 컨트롤 하는 방법은 여러가지가 있겠지만, 가장 대표적인 것이 select이다. select는 아래와 같은 원형을 가지고 있다.



  1. int select(

    int nfds,

    fd_set FAR* readfds,

    fd_set FAR* writefds,

    fd_set FAR* exceptfds,

    const struct timeval FAR* timeout

    );

select 함수는 nfds에 설정된 소켓의 수만큼 소켓을 체크하므로 반드시 가장 큰 소켓 번호 + 1의 크기만큼을 nfds로 넘겨줘야 함을 잊지 말자( ex: fd + 1 )


return 값은 아래와 같은 의미를 가진다.




  • 양수 : readfds or writefds or exceptfds 중에 양수 개의 fd가 이벤트가 발생했다.



    • fds에 이벤트가 발생한 fd만 플래그가 설정되므로 FD_ISSET 매크로를 이용해서 해당 socket을 찾을 수 있다.
    • timeout에 남은 시간이 저장되므로 이를 활용하면 추가적인 처리가 가능하다


  • 0 : timeout이 되었다. timeout 값의 경우 0으로 설정되면 무한대로 대기한다.
  • 음수 : readfds에 닫힌 소켓이 있거나 기타 에러가 발생했다.

 


fd_set 및 timeval은 구조체로 아래와 같은 형태를 가진다.



  1. typedef struct fd_set {

    u_int fd_count;

    SOCKET fd_array[FD_SETSIZE];

    } fd_set;

  2. struct timeval {

    long tv_sec; // second 단위

    long tv_usec; // millisecond 단위

    };

timeval은 위에서 보는 것 그대로 second/millisecond 단위로 설정해서 사용하면 된다.


 


하지만 fd_set과 같은 경우 어떻게 사용해야할지 좀 막막하다. 다행이 이를 처리해주는 매크로가 있으니 아래와 같다.



  • FD_ZERO( fd_set* fdset ) : fdset을 초기화. 처음에는 반드시 한번 호출해야 함
  • FD_SET( int fd, fd_set* fdset ) : fdset에 fd 소켓을 등록한다.
  • FD_CLR( int fd, fd_set* fdset ) : fdset에 fd 소켓을 삭제한다.
  • FD_ISSET( int fd, fd_set* fdset ) : fdset에 fd 소켓이 있는가 확인한다.

자주 사용하는 매크로이니 한번쯤은 읽어두자. 자세한 사용 방법은 아래의 Linux 예제를 보면 된다.


 


2.윈도우(Window) 환경


2.1 서버(Server)



  1. #include <winsock2.h>

    #include <stdio.h>

    #include <string.h>
  2. #define DEFAULT_PORT 2924

    #define DEFAULT_BUFFER_SIZE 4096
  3. int main()

    {

    char Buffer[DEFAULT_BUFFER_SIZE + 1];

    WSAData wsd;
  4. if(WSAStartup(MAKEWORD(2, 2), &wsd) != 0) {

    printf("Winsock 초기화 에러!\n");

    return -1;

    }
  5. SOCKET ls = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  6. if (ls == INVALID_SOCKET) {

    printf("소켓 생성 실패!\n");

    return -1;

    }
  7. sockaddr_in service;

    memset(&service, 0, sizeof(service));

    service.sin_family = AF_INET;

    service.sin_addr.s_addr = INADDR_ANY;

    service.sin_port = htons(DEFAULT_PORT);



    if (bind(ls, (SOCKADDR*) &service, sizeof(service)) == SOCKET_ERROR) {

    printf("bind 실패!\n");

    return -1;

    }



    if (listen(ls, 1) == SOCKET_ERROR) {

    printf("listen() 실패!\n");

    return -1;

    }
  8. SOCKET as;
  9. printf("클라이언트 연결 대기.\n");
  10. while (1) {

    as = accept(ls, NULL, NULL);

    if (as == SOCKET_ERROR) continue;

    printf("클라이언트 연결됨.\n");
  11. int nbyte = recv(as, Buffer, DEFAULT_BUFFER_SIZE, 0);
  12. if (nbyte <= 0) {

    printf("recv 에러!\n");

    break;

    }



    Buffer[nbyte] = '\0';

    printf("에코 : %s\n", Buffer);
  13. send(as, Buffer, nbyte, 0);
  14. if (strncmp(Buffer, "quit", 4) == 0) {

    printf("클라이언트의 요청에 의해 서버 종료\n");

    break;

    }



    closesocket(as);

    printf("클라이언트 연결 해제.\n새로운 클라이언트 연결 대기.\n");

    }
  15. closesocket(ls);

    WSACleanup();



    return 0;

    }

 


2.2 클라이언트(Client)



  1. #include <winsock2.h>

    #include <stdio.h>

    #include <string.h>
  2. #define DEFAULT_PORT 2924

    #define DEFAULT_BUFFER_SIZE 4096
  3. int main(int argc, char** argv)

    {

    char Buffer[DEFAULT_BUFFER_SIZE + 1];

    WSAData wsd;
  4. if (argc != 2) {

    printf ("사용법 : %s [IP 주소]\n", argv[0]);

    return -1;

    }



    if(WSAStartup(MAKEWORD(2, 2), &wsd) != 0) {

    printf("Winsock 초기화 에러!\n");

    return -1;

    }



    SOCKET cs = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);



    if (cs == INVALID_SOCKET) {

    printf("소켓 생성 실패!\n");

    return -1;

    }



    sockaddr_in client;

    memset(&client, 0, sizeof(client));



    client.sin_family = AF_INET;

    client.sin_addr.s_addr = inet_addr(argv[1]);

    client.sin_port = htons(DEFAULT_PORT);



    if (connect(cs, (SOCKADDR *)&client, sizeof(client)) == SOCKET_ERROR) {

    printf("connect 에러!\n");

    return -1;

    }
  5. printf("입력 : ");

    gets(Buffer);
  6. send(cs, Buffer, strlen(Buffer), 0);

    int nbyte = recv(cs, Buffer, DEFAULT_BUFFER_SIZE, 0);
  7. Buffer[nbyte] = '\0';

    printf("에코 : %s", Buffer);
  8. closesocket(cs);

    WSACleanup();



    return 0;

    }

 


3.Linux or Unix or NDS


3.1 서버(Server)


윈도우쪽 소스를 조금 수정했다.



  1. #include <sys/socket.h>

    #include <netinet/in.h>

    #include <netdb.h>
  2. int main()

    {

    char Buffer[256 + 1];
  3. int ls = socket(AF_INET, SOCK_STREAM, 0);

    if (ls == INVALID_SOCKET) {

    printf("소켓 생성 실패!\n");

    return -1;

    }
  4. sockaddr_in service;

    memset(&service, 0, sizeof(service));

    service.sin_family = AF_INET;

    service.sin_addr.s_addr = INADDR_ANY;

    service.sin_port = htons(DEFAULT_PORT);



    if (bind(ls, (SOCKADDR*) &service, sizeof(service)) == SOCKET_ERROR) {

    printf("bind 실패!\n");

    return -1;

    }



    if (listen(ls, 1) == SOCKET_ERROR) {

    printf("listen() 실패!\n");

    return -1;

    }

    int as;

    printf("클라이언트 연결 대기.\n");

    while (1) {

    as = accept(ls, NULL, NULL);

    if (as == SOCKET_ERROR) continue;

    printf("클라이언트 연결됨.\n");

    int nbyte = recv(as, Buffer, DEFAULT_BUFFER_SIZE, 0);

    if (nbyte <= 0) {

    printf("recv 에러!\n");

    break;

    }



    Buffer[nbyte] = '\0';

    printf("에코 : %s\n", Buffer);

    send(as, Buffer, nbyte, 0);

    if (strncmp(Buffer, "quit", 4) == 0) {

    printf("클라이언트의 요청에 의해 서버 종료\n");

    break;

    }



    close(as);

    printf("클라이언트 연결 해제.\n새로운 클라이언트 연결 대기.\n");

    }

    close(ls);



    return 0;

    }

 


3.2 클라이언트(Client)


NDS에서 사용하는 예제를 조금 수정했다.



  1. #include <sys/socket.h>

    #include <netinet/in.h>

    #include <netdb.h>
  2. int main(void)

    {
  3. //////////////////////////////////////////////////////////////////////////

    // Let's send a simple HTTP request to a server and print the results!
  4. // store the HTTP request for later

    const char * request_text =

    "GET / HTTP/1.1\r\n\r\n";

    // "Host: www.akkit.org\r\n"

    // "User-Agent: Nintendo DS\r\n\r\n";
  5. // Find the IP address of the server, with gethostbyname

    // DNS를 이용해서 Name으로 IP를 얻는다.

    // 2007/10/24 현재, 아직 잘 안되는 것 같다.

    iprintf( "DNS Resolve Start\n" );

    //struct hostent * myhost = gethostbyname( "www.google.org" );

    //iprintf("Found IP Address![www.google.org] [%08X]\n",

    // myhost->h_addr_list[0] );



    // Tell the socket to connect to the IP address we found, on port 80 (HTTP)

    struct sockaddr_in sain;

    sain.sin_family = AF_INET;

    sain.sin_port = htons(80);

    // Host Resolve가 끝났으면 아래와 같이 사용한다.

    //sain.sin_addr.s_addr= *( (unsigned long *)(myhost->h_addr_list[0]) );

    // 아래는 google의 IP 주소이다.

    sain.sin_addr.s_addr = inet_addr( "72.14.235.99" );



    // Create a TCP socket

    int my_socket;

    fd_set readfd;

    fd_set tempfd;

    struct timeval stTime;

    struct timeval stTempTime;

    int iRet;



    stTime.tv_sec = 5;

    stTime.tv_usec = 0;

    Retry:



    my_socket = socket( AF_INET, SOCK_STREAM, 0 );

    iprintf("Created Socket!\n");



    iprintf( "Try To Connect\n" );

    connect( my_socket,(struct sockaddr *)&sain, sizeof(sain) );

    iprintf("Connected to server!\n");
  6. // send our request

    send( my_socket, request_text, strlen(request_text), 0 );

    iprintf("Sent our request!\n");
  7. // Print incoming data

    iprintf("Printing incoming data:\n");
  8. int recvd_len;

    char incoming_buffer[256];
  9. iprintf("Recv Start\n");

    FD_ZERO( &readfd );

    FD_SET( my_socket, &readfd );

    while( 1 )

    {

    tempfd = readfd;

    stTempTime = stTime;

    iRet = select( my_socket + 1, &tempfd, NULL, NULL, &stTempTime );

    if( iRet > 0 )

    {

    recvd_len = recv( my_socket, incoming_buffer, 255, 0 );

    iprintf("Recv End Size[%d]\n", recvd_len );

    if( recvd_len > 0 )

    { // data was received!

    incoming_buffer[ recvd_len ] = 0; // null-terminate

    iprintf( incoming_buffer );

    }

    }

    // Time Expired

    else if( iRet == 0 )

    {

    iprintf( "Time Expired If You Press B, Exit Receiving Process\n" );

    if( ~REG_KEYINPUT & KEY_B )

    {

    break;

    }

    }

    else

    {

    iprintf( "Error~~!!\n" );

    break;

    }

    }



    iprintf("Other side closed connection!\n");
  10. shutdown(my_socket,0); // good practice to shutdown the socket.
  11. close(my_socket); // remove the socket.



    iprintf( "Press Any A Key To Retry\n" );

    while( REG_KEYINPUT & KEY_A ) ;



    goto Retry;
  12. while(1);
  13. return 0;

    }

 


4.마치면서...


간단하게나마 코드 조각을 정리해 보았다. 이제 구글링할 필요없이 바로 붙여넣고 쓰면된다. 다시한번 네트워크의 세계로 빠져보자.