C언어 소켓으로부터 자료 수신 함수 recv()

2020. 3. 16. 10:20 컴퓨터/프로그래밍

C함수 소켓으로부터 자료 수신 recv()

recv() 함수는 소켓으로부터 데이터를 수신합니다..

  • 헤더: sys/types.h, sys/socket.h
  • 형태: int recv(intsockfd, void *buff, size_t len, int flags)
  • 인수: int sockfd 소켓 디스크립터
    void *buff 수신할 버퍼 포인터
    size_t len 버퍼의 바이트 단위 길이
    int flags 아래와 같은 옵션을 사용할 수 있습니다.

    flags 설명
    MSG_DONTWAIT 수신 데이터가 없다면 기다리지 않고 -1을 반환하면서 바로 복귀
    MSG_NOSIGNAL 상대방과 연결이 끊겼을 때, SIGPIPE 시그널을 받지 않도록 합니다.
  • 반환: -1 == 실패, -1 이외 실l제 수신한 바이트 수
caution

인수 추가 설명

int socket(int domain, int type, int protocol);

int domain : 인터넷을 통해 통신할 지, 같은 시스템 내에서 프로세스 끼리 통신할 지의 여부를 설정합니다.


domain 내용
PF_INET, AF_INET IPv4 인터넷 프로토콜을 사용합니다.
PF_INET6 IPv6 인터넷 프로토콜을 사용합니다.
PF_LOCAL, AF_UNIX 같은 시스템 내에서 프로세스 끼리 통신합니다.
PF_PACKET Low level socket 을 인터페이스를 이용합니다.
PF_IPX IPX 노벨 프로토콜을 사용합니다.

int type : 데이터의 전송 형태를 지정하며 아래와 같은 값을 사용할 수 있습니다.


type 내용
SOCK_STREAM TCP/IP 프로토콜을 이용합니다.
SOCK_DGRAM UDP/IP 프로토콜을 이용합니다.

TCP/IP 통신 함수 사용 순서

TCP/IP 예제 소개

TCP/IP 예제를 서버와 클라이언트로 나누어서 설명을 드리도록 하겠습니다.

  1. 서버와 클라이언트는 port 4000번 사용
  2. 클라이언트프로그램에서 서버에 접속하면 실행할 때 입력받은 문자열을 전송
  3. 서버는 클라이언트로부터 자료를 수신하면 문자열 길이와 함께 수신한 문자열을 클라이언트로 전송

서버 프로그램

서버 프로그램에서 사용해야할 함수와 순서는 아래와 같습니다.

우선 socket 부터 만들어야 합니다. TCP/IP에서는 SOCK_STREAM을, UDP/IP에서는 SOCK_DGRAM을 사용합니다.

int     server_socket;

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

bind() 함수를 이용하여 socket에 server socket 에 필요한 정보를 할당하고 커널에 등록

  1. 만들어진 server_socket 은 단지 socket 디스크립터일 뿐입니다.
  2. 이 socket에 주소를 할당하고 port 번호를 할당해서 커널에 등록해야 합니다.
  3. 커널에 등록해야 다른 시스템과 통신할 수 있는 상태가 됩니다.
  4. 더 정확히 말씀드린다면 커널이 socket 을 이용하여 외부로부터의 자료를 수신할 수 있게 됩니다.
  5. socket에 주소와 port 를 할당하기 위해 sockaddr_in 구조체를 이용합니다.
    struct sockaddr_in server_addr;
    
    memset( &server_addr, 0, sizeof( server_addr);
    server_addr.sin_family      = AF_INET;              // IPv4 인터넷 프로토롤
    server_addr.sin_port        = htons( 4000);         // 사용할 port 번호는 4000
    server_addr.sin_addr.s_addr = htonl( INADDR_ANY);   // 32bit IPV4 주소
    
    if( -1 == bind( server_socket, (struct sockaddr*)&server_addr, sizeof( server_addr) ) ){
       printf( "bind() 실행 에러\n");
       exit( 1);
    }
  6. htonl( INADDR_ANY) 는 주소를 지정해 주는 것으로 inet_addr( "내 시스템의 IP ")로도 지정할 수 있습니다. 그러나 프로그램이 실행되는 시스템 마다 IP 가 다를 것이므로 주소 지정을 고정 IP로 하지 않고 htonl( INADDR_ANY) 를 사용하는 것이 편리합니다.

이제 listen() 함수로 클라이언트 접속 요청을 확인합니다.

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

클라이언트 접속 요청에 따라 accept()로 접속을 허락합니다. 

  1.  accept()로 접속 요청을 허락하게 되면 클라이언트와 통신을 하기 위해서 커널이 자동으로 소켓을 생성합니다. 
  2. 이 소켓을 client socket이라고 하겠습니다.
  3. client socket 정보를 구하기 위해 변수를 선언합니다. 그리고 client 주소 크기를 대입합니다.
    int     client_addr_size;
    
    client_addr_size = sizeof( client_addr);
    
  4. accept()를 호출 후에 에러가 없으면 커널이 생성한 client socket 을 반환해 줍니다.
    client_socket = accept( server_socket, (struct sockaddr*)&client_addr,
                                                              &client_addr_size);
    if ( -1 == client_socket){
       printf( "클라이언트 연결 수락 실패\n");
       exit( 1);
    }

이제 client socket까지 만들어 졌으므로 read(), write() 함수를 이용하여 자료를 송수신 할 수 있습니다. read() 함수를 이용하여 클라이언트로부터 전송되어 오는 자료를 읽어 들입니다.

read ( client_socket, buff_rcv, BUFF_SIZE);
  1. read() 를 이용하여 클라이언트로부터 전송된 자료를 읽어 들입니다.
  2. 만일 클라이언트로부터 전송된 자료가 없다면 송신할 때 까지 대기하게 됩니다. 즉, 블록된 모습이 됩니다.

이번에는 wirte() 함수를 이용하여 클라이언트도 데이터를 전송합니다.

  1. 수신된 데이터의 길이를 구하여 전송 데이터를 준비합니다.
    sprintf( buff_snd, "%d : %s", strlen( buff_rcv), buff_rcv);
    
  2. write() 를 이용하여 클라이언트로 자료를 송신합니다.
    write( client_socket, buff_snd, strlen( buff_snd)+1); // +1: NULL까지 포함해서 전송
    

작업이 완료되면 close() 를 이용하여 client socket 을 소멸 시켜 데이터 통신을 종료합니다.

close( client_socket);

클라이언트 프로그램

클라이언트 프로그램은 서버에 비해 간단합니다. 바로 설명 들어갑니다.

socket() 을 이용하여 소켓을 먼저 생성합니다.

int     client_socket;

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

connect()를 이용하여 서버로 접속을 시도합니다.

  1. 주소 정보에 서버의 주소와 포트번호를 지정하고
  2. 서버와의 연결을 시도합니다.
  3. 예제에서는 시스템 자기를 가르키는 IP, 127.0.0.1 을 사용했습니다.
    struct sockaddr_in    server_addr;
    
    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);
    }
    
  4. 접속에 성공하면 데이터를 전송합니다.
    write( client_socket, argv[1], strlen( argv[1])+1); // +1: NULL까지 포함해서 전송
    
  5. 자료를 수신하고 화면에 출력합니다.
    read ( client_socket, buff, BUFF_SIZE);
    printf( "%s\n", buff);
    
  6. socket 을 소멸하여 통신 작업을 완료합니다.
    close( client_socket);
    

서버 프로그램 소스

#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];

   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);
   }
   if( -1 == listen(server_socket, 5)){
      printf( "listen() 실행 실패\n");
      exit( 1);
   }
   while( 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);
      }
      read ( client_socket, buff_rcv, BUFF_SIZE);
      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까지 포함해서 전송
      close( client_socket);
   }
}

클라이언트 프로그램 소스

#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];

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

실행 결과

]$ gcc server.c -o server    // 서버 프로그램을 server 이름으로 컴파일
]$ gcc client.c -o client    // 클라이언트 프로그램을 client 이름으로 컴파일
]$ ./server &                // 서브 프로그램을 백그라운드로 실행
[1] 25869
]$ ./client test_string      // 클라이언트를 문자열을 입력하여 실행
receive: test_string
11 : test_string
]$ ./client badayak.com
receive: badayak.com
11 : badayak.com
]$
이 댓글을 비밀 댓글로