앞으로 몇 회에 걸쳐서 EZ-ETIO 기능 중에 카메라 영상을 P.C.나 다른 시스템으로 전송하는 방법에 대해 올리려 합니다. EZ-ETIO는 이미 "임베디드 학습을 위한 보드, EZ-ETIO"글에 소개한 적이 있는 임베디드 보드를 학습하기 위한 보드입니다.

EZ-X5에 확장으로 연결되며, 각종 스위치로 입력을 받을 수 있고, LED로 출력을 학습할 수 있습니다. 또한 리모트 수신부와 함께 카메라도 장작되어 있는데, 이 카메라로 촬영된 이미지를 보드에 접속한 시스템으로 영상을 전달하도록 하겠습니다.

강좌 진행에 대한 말씀 이번 시간에 카메라 영상을 전송하고 수신하는 예제를 보여 드리면 말씀 드릴 것도 많고 매우 복잡하므로, 우선 서버와 클라이언트로 나누어 프로그램을 작성하고, 클라이언트가 서버로 접속이 성공하면 클라언트에서 서버로 문자열을 전송해 보겠습니다.

서버는 POLL을 이용하여 대기 시간을 가지면서 클라이언트의 연결 요청에 수락하고 클라이언트의 자료를 수신하고 다시 전송합니다.

아래의 예제는 단순히 문자열을 송수신하는 간단한 예제입니다만 다음 시간에는 파일을 전송하고, 마지막 3번째 시간에는 카메라 영상을 전송하도록 하겠습니다.

우선 전체 프로그램 소스입니다.

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

#define  BUFF_SIZE         1024
#define  POLL_MAX_COUNT    2
#define  POLL_SERVER       0
#define  POLL_CLIENT       1

int      server_socket;
int      client_socket;

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

struct   pollfd  poll_array[POLL_MAX_COUNT];                // poll 배열

void  connect_client( void)
{
   struct   sockaddr_in   client_addr;
   int      client_addr_size;

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

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

   poll_array[POLL_CLIENT].fd       = client_socket;        // 클라이언트 소켓은 아직 없음
   poll_array[POLL_CLIENT].events   = POLLIN ¦ POLLERR ¦ POLLHUP;
   poll_array[POLL_CLIENT].revents  = 0;
}

void  disconnect_client( void)
{
   if ( 0 < client_socket)
   {
      printf( "클라이언트 연결 종료\n");

      close( client_socket);
   }
   poll_array[POLL_CLIENT].fd    = -1;
}

void  read_client( void)
{
   int sz_read;

   sz_read  = read ( client_socket, buff_rcv, BUFF_SIZE);
   if ( 0 < sz_read)
   {
      buff_rcv[sz_read+1]  = '\0';                          // 입력 문자열 끝에 NULL이 없을 경우를 위해
      printf( "receive: %s\n", buff_rcv);

      sprintf( buff_snd, "%d : %s", strlen( buff_rcv), buff_rcv);
      write( client_socket, buff_snd, strlen( buff_snd)+1); // +1: NULL까지 포함해서 전송
   }
   else                                                     // POLLIN 임에도 읽을 자료가 없다면
   {                                                        // 연결이 끊긴 것임
      disconnect_client();
   }
}

int   main( void)
{
   struct   sockaddr_in   server_addr;
   int      option;
   int      poll_state;

   server_socket  = socket( PF_INET, SOCK_STREAM, 0);
   option         = 1;                                      // SO_REUSEADDR 의 옵션 값을 TRUE 로
   setsockopt( server_socket, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));

   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);
   }

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

   poll_array[POLL_SERVER].fd       = server_socket;        // poll 에 디스크립터를 등록한다.
   poll_array[POLL_SERVER].events   = POLLIN;

   poll_array[POLL_CLIENT].fd       = -1;                   // 클라이언트 소켓은 아직 없음

   while( 1)
   {
      poll_state = poll( (struct pollfd *)&poll_array, POLL_MAX_COUNT, 1000);
      if ( 0 > poll_state)
      {
         printf( "치명적인 에러 발생\n");
         return -1;
      }
      if ( 0 < poll_state)
      {
         if( poll_array[POLL_SERVER].revents & POLLIN)      // 연결 요청을 받음
         {
            connect_client();
         }
         if( poll_array[POLL_CLIENT].revents & POLLIN )     // 클라이언트로부터 자료 수신
         {
            read_client();
         }
         if( poll_array[POLL_CLIENT].revents & ( POLLHUP ¦ POLLERR )) // 라인이 끊어 졌거나 에러 발생
         {
            disconnect_client();
         }
      }
   }
   return 0;
}

이제 프로그램 하나하나 분석해 보겠습니다.

클라이언트의 연결 요청에 응답하기 위한 서버 소켓과 클라이언트와의 통신을 위한 클라이언트 소켓을 위해 struct pollfd를 2개 준비하겠습니다.

#define  POLL_MAX_COUNT    2
#define  POLL_SERVER       0
#define  POLL_CLIENT       1

POLL_MAX_COUNT 를 2로 해서 pollfd 배열을 정의했습니다.

struct   pollfd  poll_array[POLL_MAX_COUNT];

poll_array[]에서,

  • poll_array[POLL_SERVER]는 클라이언트 연결 요청에 응답하기 위한 서버 소켓을 위한 POLL입니다.
  • poll_array[POLL_CLIENT]는 클라이언트와 통신을 위한 소켓입니다.

main()함수에서 서버 소켓부터 생성합니다. 프로그램 종료 후에 기다림없이 같은 포트를 사용할 수 있도록 SO_REUSEADDR의 값을 TRUE로 설정햇습니다.

소켓 생성 후에 bind()와 listen()함수까지 호출하여 클라이언트 연결 요청을 받아 들일 수 있도록 준비합니다. bind()와 client()에 대해 자세한 내용을 알고 싶으시면 "TCP/IP 프로그래밍 방법"글을 참고하여 주십시오.

int   main( void)
{
           :
   server_socket  = socket( PF_INET, SOCK_STREAM, 0);
   option         = 1;                                      // SO_REUSEADDR 의 옵션 값을 TRUE 로
   setsockopt( server_socket, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));

           :
   if( -1 == bind( server_socket, (struct sockaddr*)&server_addr, sizeof( server_addr) ) )
           :
   if( -1 == listen(server_socket, 5))
           :
}

이제 서버 소켓을 POLL에 등록하면, 그리고 클라이언트가 연결 요청을 하면 POLLIN이 발생하게 됩니다. 프로그램 소스에서 이 부분만 정리해 보겠습니다.

int   main( void)
{
                 :
   poll_array[POLL_SERVER].fd       = server_socket;        // poll 에 디스크립터를 등록한다.
   poll_array[POLL_SERVER].events   = POLLIN;

   while( 1)
   {
      poll_state = poll( (struct pollfd *)&poll_array, POLL_MAX_COUNT, 1000);
                 :
      if ( 0 < poll_state)
      {
         if( poll_array[POLL_SERVER].revents & POLLIN)      // 연결 요청을 받음
         {
            connect_client();
         }
                 :
   }
   return 0;
}

이렇게 POLL의 변동 사항을 확인해 보니 server_socket의 POLL 변화에서 POLLIN 이 발생했다면 클라이언트가 연결을 요청한 것입니다. 클라이언트와 연결하기 위해서 connect_client()를 호출했습니다.

connect_client()에서는 accept() 함수를 이용하여 클라이언트와 연결하고, accept() 실행 결과로 클라이언트와 연결되는 소켓을 POLL에 등록합니다. 클라이언트의 데이터 전송을 확인하기 위해 POLLIN과 클라이언트와의 연결이 종료되거나 에러를 확인하기 위해 POLLERR와 POLLHUP까지 설정합니다.

void  connect_client( void)
{
   struct   sockaddr_in   client_addr;
   int      client_addr_size;

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

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

   poll_array[POLL_CLIENT].fd       = client_socket;        // 클라이언트 소켓은 아직 없음
   poll_array[POLL_CLIENT].events   = POLLIN ¦ POLLERR ¦ POLLHUP;
   poll_array[POLL_CLIENT].revents  = 0;
}

다시 main()에서는 클라이언트로부터 자료 수신이 있다면 read_client()를 실행하고 연결이 끊기거나 에러가 발생하면 클라이언트의 소켓을 닫음(close) 으로서 연결을 종료합니다.

int   main( void)
{
                             :
while( 1) { :
if ( 0 < poll_state) { if( poll_array[POLL_SERVER].revents & POLLIN) // 연결 요청을 받음 { connect_client(); } if( poll_array[POLL_CLIENT].revents & POLLIN ) // 클라이언트로부터 자료 수신 { read_client(); } if( poll_array[POLL_CLIENT].revents & ( POLLHUP ¦ POLLERR )) // 라인이 끊어 졌거나 에러 발생 { disconnect_client(); } } } return 0; }

read_client()에서는 클라이언트 소켓을 이용하여 클라이언트가 전송한 데이터를 읽을 수 있습니다. 그러나 POLLIN이 발생해서 자료를 읽었는데, 읽어 들인 자료가 없다면 클라이언트가 연결을 중지한 것입니다. 그러므로 이 때에도 클라이언트 소켓을 close합니다.

void  read_client( void)
{
   int sz_read;

   sz_read  = read ( client_socket, buff_rcv, BUFF_SIZE);
   if ( 0 < sz_read)
   {
      buff_rcv[sz_read+1]  = '\0';                          // 입력 문자열 끝에 NULL이 없을 경우를 위해
      printf( "receive: %s\n", buff_rcv);

      sprintf( buff_snd, "%d : %s", strlen( buff_rcv), buff_rcv);
      write( client_socket, buff_snd, strlen( buff_snd)+1); // +1: NULL까지 포함해서 전송
   }
   else                                                     // POLLIN 임에도 읽을 자료가 없다면
   {                                                        // 연결이 끊긴 것임
      disconnect_client();
   }
}

클라이언트가 전송한 문자열을 화면에 출력하고, 원래의 문자열에 문자열 길이와 함께 다시 반송합니다. 이 소스는 이전에 올려 드린 강좌 "TCP/IP 프로그래밍 방법"에 소개된 서버 예제에 대해 POLL을 접목한 것입니다.

프로그램을 컴파일하고 실행하신 후에, "TCP/IP 프로그래밍 방법"에 소개된 클라이언트 프로그램이나 아래의 프로그램을 이용하여 문자열을 전송하면 클라이언트에서 전송된 문자열을 반송되는 테스트를 하실 수 있습니다.

#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)
{
   int      client_socket;
   struct   sockaddr_in   server_addr;
   char     buff[BUFF_SIZE+5];
   int      ndx;

   if ( 1 == argc)
   {
      printf( "사용하는 방법: ./a.out 문자열1 문자열2 문자열3 ....\n");
      return 1;
   }

   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);
   }
   
   for ( ndx = 1; ndx < argc; ndx++)
   {
      write( client_socket, argv[ndx], strlen( argv[ndx])+1);      // +1: NULL까지 포함해서 전송
      read ( client_socket, buff, BUFF_SIZE);
      printf( "%s\n", buff);
   }
   
   close( client_socket);
   
   return 0;
}

이제 두 프로그램을 컴파일을 한 후 서버 프로그램부터 실행시킵니다. 그리고 이후에 클라이언트 프로그램을 실행할 때 마다 클라이언트에서 전송한 문자열이 반송되는 것을 보실 수 있습니다.

서버 프로그램 클라이언트 프로그램

]$ ./server_sample

 
  ]$ ./client_sample 123 456 789
클라이언트 연결 성공
receive: 123
receive: 456
receive: 789
클라이언트 연결 종료

 

3 : 123
3 : 456
3 : 789

  ]$ ./client_sample abc defg hijkl
클라이언트 연결 성공
receive: abc
receive: defg
receive: hijkl
클라이언트 연결 종료

 

3 : abc
4 : def
5 : hijkl

  ]$

결언

이번시간에는 TCP/IP를 이용하여 클라이언트와 연결하고 데이터를 주고 받는 부분을 POLL로 구성해 보았습니다. 다음 시간에는 기능을 추가해서 파일을 전송해 보겠습니다.