C언어 클라이언트 접속 요청을 받을 수 있도록 설정 함수 listen()

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

C함수 클라이언트 접속 요청을 받을 수 있도록 설정 listen()

inet_addr() 함수는 숫자와 점으로 이루어진 IP 문자열을 long형의 숫자 IP 주소로 바꾸어 줍니다. struct sockaddr_in 에서 .sin_addr.s_add 값을 long형의 숫자 IP값을 넣어 주어야 하는데 이 때 사용됩니다.

  • 헤더: sys/types.h, sys/socket.h
  • 형태: int socket(int domain, int type, int protocol)
  • 인수: int domain 인터넷을 통해 통신할 지, 같은 시스템 내에서 프로세스 끼리 통신할 지의 여부를 설정합니다.
    int type 인터넷을 통해 통신할 지, 같은 시스템 내에서 프로세스 끼리 통신할 지의 여부를 설정합니다.
    int protocol 인터넷을 통해 통신할 지, 같은 시스템 내에서 프로세스 끼리 통신할 지의 여부를 설정합니다.
  • 반환: 0 == 성공, -1 == 실패
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
]$
이 댓글을 비밀 댓글로