08 BSD 소켓 프로그래밍(Socket Programming) 예제

원문 : http://kkamagui.springnote.com/pages/551093

 

들어가기 전에...

 

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.마치면서... 

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

 

 

이 글은 스프링노트에서 작성되었습니다.

+ Recent posts