관리 메뉴

IT 컴퓨터공학 자료실

소켓에 대해 본문

컴퓨터공학/네트워크 멀티스레드 프로그래밍

소켓에 대해

윤맨1 2015. 6. 24. 10:14

소켓 (BSD)


소켓 ( 영어 : Socket )은 BSD 계열 UNIX 를 기원으로하는 API 이며, C 언어 에 의한 프로그램 개발에서의 프로세스 간 통신 , 특히 컴퓨터 네트워크 관련 라이브러리 를 구성한다. BSD 소켓 , 버클리 소켓 이라고도한다.

1983 년 에 출시 된 UNIX 운영 체제 (OS) 4.2BSD에서 처음 API로 구현되었다. 네트워크 추상화 인터페이스로 사실상의 표준 이되고있다. 본래의 소켓 API는 C 언어를 대상으로하지만, 다른 프로그래밍 언어 에서도 유사한 인터페이스를 채용하고있는 것이 많다.

소켓의 대체가되는 API로 STREAMS 기반 Transport Layer Interface (TLI)가있다. 그러나 BSD 소켓은 비교가 안 될 정도로 인기가 있으며, 많은 구현이 존재한다.


목차

  [ 숨기기 ] 

BSD 소켓 인터페이스 편집 ]

BSD 소켓은 호스트 간의 통신 및 하나의 컴퓨터에서 프로세스 간 통신을 가능하게한다. 통신 매체로는 다양한 입출력 기기 나 장치 드라이버 를 이용 가능하지만, 그 부분은 운영 시스템 의 구현에 의존한다. 이 인터페이스 구현은 TCP / IP 를 이용하는 경우는 거의 반드시 필요하고 인터넷 을 지탱하는 기반 기술의 하나가되고있다. 당초 UC 버클리 에서 UNIX 용으로 개발되었다. 최근 모든 운영 체제에 확실히 BSD 소켓이 어떤 형태로 구현되어 있으며, 인터넷에 연결하는 표준 인터페이스이다.

프로그래머의 관점에서 보면 소켓 인터페이스는 3 개의 레벨로 액세스 가능하다. 가장 강력하고 기본적인 수준은 RAW (생) 소켓 수준이다. RAW 소켓을 가능하게하는 통신 제어의 자유도를 필요로하는 응용 프로그램은 드물며 인터넷 관련 기술 개발에만 사용되어야한다고되어있다.

헤더 파일 편집 ]

BSD 소켓에 어떤 헤더 파일 이있다.

<sys / socket.h>
BSD 소켓의 핵심 함수와 데이터 구조
<netinet / in.h>
AF_INET 및 AF_INET6 주소 패밀리. 인터넷에서 널리 사용되는 IP 주소와 TCP / UDP 포트 번호가 포함된다.
<sys / un.h>
AF_UNIX 주소 패밀리. 동일한 컴퓨터에서 실행되는 프로그램 간의 로컬 통신에 사용됩니다. 네트워크에서 사용되지 않는다.
<arpa / inet.h>
숫자로 IP 주소를 조작하는 기능의 정의
<netdb.h>
프로토콜 및 호스트 이름을 숫자 주소로 변환하는 기능을 정의합니다. 로컬 데이터와 DNS를 검색한다.

TCP 편집 ]

TCP 는 연결의 개념을 제공한다. TCP 소켓을 생성하려면 socket () 함수에서 AF_INET 또는 AF_INET6 와 SOCK_STREAM 을 인수로 지정한다.

서버 편집 ]

간단한 TCP 서버 설정은 다음과 같이 이루어진다.

  • TCP 소켓을 생성 ( socket () 호출)
  • 소켓을 listen (연결 확립 요구 대기 받아) 포트에 바인드 ( bind () 호출). bind () 를 호출하기 전에 sockaddr_in 구조체를 선언하고 그 내용을 지우고 ( bzero () 나 memset () ) 그 sin_family 필드를 AF_INET 또는 AF_INET6 로 설정하고 sin_port 필드에 listen 대상 포트 번호를 네트워크 바이트 순서 로 설정한다. short int 를 네트워크 바이트 순서로 변환하려면 htons () 함수를 사용한다.
  • 소켓을 연결 listen에 사용하기 위해 listen () 를 호출한다.
  • 들어온 연결을 accept () 호출에서 접수한다. 이것은 연결이 수신 될 때까지 차단하고 수신 된 연결 소켓 기술자를 반환한다. 첫 번째 기술자는 listen 용 기술자 그대로이며, accept () 는 소켓을 닫을 때까지 여러 번 호출.
  • 원격 호스트와 통신한다. send () 와 recv () 또는 write () 과 read () 를 사용한다.
  • 불필요하게되면, close () 를 사용하여 오픈 된 각 소켓을 닫습니다. 또한, fork () 가 이루어진 경우, 각 프로세스는 보이지 소켓을 모두 종료해야하며, 여러 프로세스가 동일한 소켓을 동시에 사용해서는 안된다.

C99 에서 서버 측 소스 코드.

#include <stdio.h> 
#include <string.h> 
#include <unistd.h> 
#include <sys / types.h> 
#include <sys / socket.h> 
#include <netinet / in.h> 
#include <arpa / inet.h>
 
int main ( void ) 
{ 
    // 서버 소켓 생성 
    int sock = socket ( AF_INET , SOCK_STREAM ,  0 ) ; 
    if  ( sock ==  - 1 ) 
    { 
        perror ( "socket" ) ; 
        return  1 ; 
    }
 
    // struct sockaddr_in 작성 
    struct sockaddr_in SA =  { 0 } ; 
    sa. sin_family  = AF_INET ; 
    sa. sin_port  = htons ( 1100 ) ; 
    sa. sin_addr . s_addr  = htonl ( INADDR_ANY ) ;
 
    // 바인딩 
    if  ( bind ( sock ,  ( struct sockaddr * )  & sa ,  sizeof ( struct sockaddr_in ) )  ==  - 1 ) 
    { 
        perror ( "bind" ) ; 
        Goto bail ; 
    }
 
    // 수신 
    if  ( listen ( sock ,  128 )  ==  - 1 ) 
    { 
        perror ( "listen" ) ; 
        Goto bail ; 
    }
 
    while  ( 1 ) 
    { 
        // 클라이언트의 연결을 기다리는 
        int FD = accept ( sock , NULL , NULL ) ; 
        if  ( fd ==  - 1 ) 
        { 
            perror ( "accept" ) ; 
            Goto bail ; 
        }
 
        // 수신 
        char buffer [ 4096 ] ; 
        int recv_size = read ( fd , buffer ,  sizeof ( buffer )  -  1 ) ; 
        if  ( recv_size ==  - 1 ) 
        { 
            perror ( "read" ) ; 
            close ( fd ) ; 
            Goto bail ; 
        }
 
        // 수신 내용을 표시 
        buffer [ recv_size ]  =  ' \ 0 ' ; 
        printf ( "message : % s \ n " , buffer ) ;
 
        // 소켓을 닫는 
        if  ( close ( fd )  ==  - 1 ) 
        { 
            perror ( "close" ) ; 
            Goto bail ; 
        } 
    }
 
bail : 
    // 에러가 발생했을 경우의 처리 
    close ( sock ) ; 
    return  1 ; 
}

클라이언트 편집 ]

TCP 클라이언트 설정은 다음과 같이 이루어진다.

  • TCP 소켓을 생성한다 ( socket () 호출)
  • connect () 서버에 연결한다. 이 때, sockaddr_in 구조체의 sin_family 필드는 AF_INET 또는 AF_INET6 하며 sin_port 에는 통신 상대가 listen하고있는 것 포트 번호를 네트워크 바이트 순서로 설정하고 sin_addr 에도 네트워크 바이트 순서로 상대의 (IPv4 또는 IPv6 의) 주소를 설정한다.
  • 서버와 통신한다. send () 와 recv () 또는 write () 과 read () 를 사용한다.
  • 연결의 종료와 삭제 처리를 close () 에서 실시한다. 또한, fork () 가 이루어진 경우, 각 프로세스는 보이지 소켓을 모두 종료해야하며, 여러 프로세스가 동일한 소켓을 동시에 사용해서는 안된다.

C99 에서 클라이언트 측 소스 코드.

#define _BSD_SOURCE
 
#include <stdio.h> 
#include <string.h> 
#include <unistd.h> 
#include <netdb.h> 
#include <sys / types.h> 
#include <sys / socket.h> 
#include <netinet /in.h> 
#include <arpa / inet.h>
 
#define MESSAGE "Hello World!"
 
int main ( void ) 
{ 
    // 소켓 생성 
    int sock = socket ( AF_INET , SOCK_STREAM ,  0 ) ; 
    if  ( sock ==  - 1 ) 
    { 
        perror ( "socket" ) ; 
        return  1 ; 
    }
 
    // struct sockaddr_in 작성 
    struct sockaddr_in SA =  { 0 } ; 
    sa. sin_family  = AF_INET ; 
    sa. sin_port  = htons ( 1100 ) ;
 
    // localhost의 IP 주소를 끄는 
    struct hostent * hostent = gethostbyname ( "localhost" ) ; 
    if  ( hostent == NULL ) 
    { 
        herror ( "gethostbyname" ) ; 
        Goto bail ; 
    } 
    memcpy ( & sa. sin_addr , hostent -> h_addr_list [ 0 ] ,  sizeof ( sa. sin_addr ) ) ;
 
    // 연결 
    if  ( connect ( sock ,  ( struct sockaddr * )  & sa ,  sizeof ( struct sockaddr_in ) )  ==  - 1 ) 
    { 
        perror ( "connect" ) ; 
        Goto bail ; 
    }
 
    // 전송 
    if  ( write ( sock , MESSAGE ,  strlen ( MESSAGE ) )  ==  - 1 ) 
    { 
    	perror ( "write" ) ; 
    	Goto bail ; 
    }
 
    // 청산 
    close ( sock ) ; 
    return  0 ;
 
bail : 
    // 에러가 발생했을 경우의 처리 
    close ( sock ) ; 
    return  1 ; 
}

위의 소스 코드는 gcc의 경우 -std = c99 를 붙이는 것으로 컴파일 할 수 있지만, -std = gnu99 을 사용하면 처음부터 정의되어 있기 때문에 #define _BSD_SOURCE 은 있어도 없어도 어디라도 좋다. #define _BSD_SOURCE 가 없다고 herror ()사용할 수 없다.

UDP 편집 ]

UDP 는 연결없는 프로토콜이며 패킷이 반드시 상대에 닿는 것은 보장되지 않는다. UDP 패킷은 순서대로 도착하는 것은 아니며 하나의 패킷이 복수 닿을 수도있다, 그리고 전혀 닿지 않는 경우도있다. 이처럼 거의 아무것도 보장되지 않으므로 UDP는 TCP에 비해 오버 헤드가 작다. 연결형 인은 두 호스트간에 연결과 스트림이라고 한 것이 아니라 데이터가 단순히 개별 패킷 (datagram)로 전달되는 것을 의미하고있다.

UDP의 주소 공간, 즉 UDP 포트 번호는 TCP 포트와는 별개이다.

서버 편집 ]

C99 에서 서버 측 소스 코드. 포트 번호 7654에서 UDP 서버를 연다.

#include <stdio.h> 
#include <string.h> 
#include <unistd.h> 
#include <sys / socket.h> 
#include <sys / types.h> 
#include <netinet / in.h>
 
int main ( void ) 
{ 
    // 소켓 생성 
    int sock = socket ( AF_INET , SOCK_DGRAM ,  0 ) ; 
    if  ( sock ==  - 1 ) 
    { 
        perror ( "socket" ) ; 
        return  1 ; 
    }
 
    // struct sockaddr_in 작성 
    struct sockaddr_in SA =  { 0 } ; 
    sa. sin_family  = AF_INET ; 
    sa. sin_port  = htons ( 7654 ) ; 
    sa. sin_addr . s_addr  = htonl ( INADDR_ANY ) ;
 
    // 바인딩 
    if  ( bind ( sock ,  ( struct sockaddr * )  & sa ,  sizeof ( struct sockaddr_in ) )  ==  - 1 ) 
    { 
        perror ( "bind" ) ; 
        Goto bail ; 
    }
 
    while  ( 1 ) 
    { 
        // 수신 
        char buffer [ 4096 ] ; 
        socklen_t addrlen =  sizeof ( struct sockaddr_in ) ; 
        ssize_t recv_size = recvfrom ( sock ,  ( void  * ) buffer ,  sizeof ( buffer )  -  1 ,  0 ,  ( struct sockaddr * )  & sa ,  & addrlen ) ; 
        if  ( recv_size ==  - 1 ) 
        { 
            perror ( "recvfrom" ) ; 
            Goto bail ; 
        }
 
        // 수신 내용 표시 
        buffer [ recv_size ]  =  ' \ 0 ' ; 
        printf ( "datagram : % s \ n " , buffer ) ; 
    }
 
bail : 
    // 에러가 발생했을 경우의 처리 
    close ( sock ) ; 
    return  1 ; 
}

bind () 소켓 및 주소 / 포트를 결부시킨다.

마지막 무한 루프 recvfrom () 를 사용하여 포트 번호 7654에서 UDP 패킷을 수신한다. 인수는 다음과 같다.

  • 소켓
  • 버퍼에 대한 포인터
  • 버퍼의 크기
  • 플래그 (read 같은 다른 소켓 수신 함수와 동일)
  • 주소 구조
  • 주소 구조체의 크기

클라이언트 편집 ]

C99 에서 클라이언트 측 소스 코드. 포트 7654 주소 127.0.0.1에 "Hello World!"라는 내용의 UDP 패킷을 보낸다.

#include <stdio.h> 
#include <string.h> 
#include <unistd.h> 
#include <sys / socket.h> 
#include <sys / types.h> 
#include <netinet / in.h> 
#include <arpa / inet.h>
 
#define MESSAGE "Hello World!"
 
int main ( void ) 
{ 
    // 소켓 생성 
    int sock = socket ( AF_INET , SOCK_DGRAM ,  0 ) ; 
    if  ( sock ==  - 1 ) 
    { 
        perror ( "socket" ) ; 
        return  1 ; 
    }
 
    // struct sockaddr_in 작성 
    struct sockaddr_in SA =  { 0 } ; 
    sa. sin_family  = AF_INET ; 
    sa. sin_port  = htons ( 7654 ) ; 
    sa. sin_addr . s_addr  = inet_addr ( "127.0.0.1" ) ;
 
    // 전송 
    int Bytes_sent = sendto ( sock , MESSAGE ,  strlen ( MESSAGE ) ,  0 ,  ( struct sockaddr * )  & sa ,  sizeof ( struct sockaddr_in ) ) ; 
    if  ( bytes_sent ==  - 1 ) 
    { 
        perror ( "sendto" ) ; 
        Goto bail ; 
    }
 
    // 청산 
    close ( sock ) ; 
    return  0 ;
 
bail : 
    // 에러가 발생했을 경우의 처리 
    close ( sock ) ; 
    return  1 ; 
}

UNIX 도메인 소켓 편집 ]

IP의 경우는 IP 주소 + 포트 번호로 연결을 지정하는데 대해, UNIX 도메인 소켓의 경우 파일 경로로 지정한다.

함수 편집 ]

socket () 편집 ]

socket () 는 통신의 한쪽 끝을 만들고, 그것을 나타내는 파일 기술자 를 돌려 준다. socket () 는 다음 세 가지 인수가있다.

  • domain - 생성하는 소켓의 프로토콜 패밀리를 지정한다.
  • type - 소켓 유형 지정.
    • SOCK_STREAM - 신뢰성있는 스트림 지향 서비스 (TCP 지원)
    • SOCK_DGRAM - 데이터 그램 서비스 (UDP 지원)
    • SOCK_SEQPACKET - SOCK_STREAM 과 거의 같지만, 수신 된 패킷을 읽을 때 패킷 전체를 읽어 않으면 나머지를 폐기한다.
    • SOCK_RAW - IP 레벨의 프로토콜 처리를 사용자 측에서 준비 할 때 사용
  • protocol - 보통 0을 지정하고 기본 물건이 선정된다. 전송 계층 프로토콜은 domain 과 type 으로 지정되지만 ( TCP 의 경우 AF_INET 또는 AF_INET6 와 SOCK_STREAM , UDP 의 경우 비슷한 AF_ 값과 SOCK_DGRAM ) 명시 적으로 지정하는 것도 가능하고, 그 값 는 <netinet / in.h>에 정의되어있다.

오류가 발생하면 -1을 반환한다. 그렇지 않은 경우 새로 확보 된 기술자를 나타내는 정수를 반환한다.

# include <sys / types.h> 
# include <sys / socket.h> 
int socket ( int domain ,  int type ,  int protocol ) ;

gethostbyname ()과 gethostbyaddr () 편집 ]

gethostbyname () 과 gethostbyaddr () 는 이름이나 주소로 지정된 인터넷 호스트를 나타내는 struct hostent 라는 데이터 구조에 대한 포인터를 반환한다. 그 중에는 네임 서버에서 얻은 정보 / etc / hosts 파일에서 얻은 정보 등이 포함되어있다. 로컬 네임 서버가 작동하지 않는 경우, / etc / hosts 파일을 참조한다. 다음 인수가있다.

  • name - 호스트 이름을 지정합니다. 예를 들어 "www.wikipedia.org"
  • addr - struct in_addr 의 포인터. 내용으로 호스트 주소를 지정합니다.
  • len - addr 의 길이를 바이트 단위로 지정합니다.
  • type - 주소의 도메인 형식을 지정합니다. 예를 들어, AF_INET

오류가 발생하면 NULL 포인터를 반환 h_errno 를 보면 전체 오류의 원인 (다시 시도할지 여부) 알 수있다. 그렇지 않은 경우 유효한 struct hostent * 이 반환된다.

struct hostent * gethostbyname ( const  char  * name ) ; 
struct hostent * gethostbyaddr ( const  void  * addr ,  int len ,  int type ) ;

connect () 편집 ]

connect () 는 커넥션의 확립에 성공하면 0을 반환하고 에러가 발생하면 -1을 반환한다.

연결형 소켓의 경우 ( User Datagram Protocol ), connect () 는 송수신 상대를 지정하는 데 사용되며, send () 와 recv () 를 비 연결형 소켓에서 사용할 수있게된다.

# include <sys / types.h> 
# include <sys / socket.h> 
int Connect ( int sockfd ,  const  struct sockaddr * serv_addr , socklen_t addrlen ) ;

bind () 편집 ]

bind () 소켓에 주소를 설정한다. socket () 으로 생성 된 시점에서는 소켓 주소 패밀리는 지정되어 있지만 주소는 설정되어 있지 않다. 소켓은 연결을 수락하기 전에 바인딩 (주소 설정) 될 필요가있다. 다음 인수가있다.

  • sockfd - 바인드해야하는 소켓의 기술자
  • serv_addr - 바인드해야하는 주소를 나타내는 sockaddr 구조체에 대한 포인터
  • addrlen - sockaddr 구조체의 크기

성공하면 0을 반환하고 에러가 발생하면 -1을 반환한다.

# include <sys / types.h> 
# include <sys / socket.h> 
int bind ( int sockfd ,  struct sockaddr * my_addr , socklen_t addrlen ) ;

listen () 편집 ]

listen () 은 바인딩 된 소켓에 연결 설정 요구를 기다린다. SOCK_STREAM 또는 SOCK_SEQPACKET 의 경우에만 유효합니다. 다음 인수가있다.

  • sockfd - 소켓 기술자
  • backlog - 어떤 시점에서 보류 할 수있다 (큐잉 수있다) 최대 연결 수. 일반적으로 OS가 상한을 마련하고있다.

연결이 accept () 되면 데큐된다. 성공하면 0을 반환한다. 오류가 발생하면 -1을 반환한다.

# include <sys / socket.h> 
int 인식 ( int sockfd ,  int backlog ) ;

accept () 편집 ]

accept () 는 원격 호스트에서 연결 설정 요구를 접수한다. 다음 인수가있다.

  • sockfd - listen하고 있던 소켓 기술자
  • cliaddr - 클라이언트의 주소 정보를 accept () 에서 저장하는 sockaddr 구조체에 대한 포인터
  • addrlen - cliaddr 가 가리키는 sockaddr 구조체의 크기를 포함하는 socklen_t 의 포인터. accept () 에서 돌아 왔을 때, 실제로 저장된 데이터 구조의 크기에 업데이트되고있다.

연결이 설정되면 그에 대응하는 소켓 기술자를 반환한다. 오류가 발생하면 -1을 반환한다.

# include <sys / types.h> 
# include <sys / socket.h> 
int accept ( int sockfd ,  struct sockaddr * cliaddr , socklen_t * addrlen ) ;

블로킹과 non-blocking 편집 ]

BSD 소켓 블로킹 및 비 블로킹 두 가지 모드를 가지고있다. 블로킹 소켓은 지정된 데이터를 모두 송수신 할 때까지 호출 라이브러리 함수에서 돌아 오지 않는다. 이것은 소켓에 listen를 계속하려는 경우에 곤란한 일이된다. 프로그램은 결코 닿지 않는 데이터를 기다리고 움직일 수 없게 될 가능성이있다.

차단 또는 비 차단 여부를 결정하려면 fcntl () 또는 ioctl () 함수를 사용한다.

주의점 편집 ]

socket () 에서 확보 된 자원은 close () 를 사용까지 해제되지 않는다. 이것은 connect () 가 성공할 때까지 재 시도를 반복하는 구현에서주의가 필요하다. socket () 를 호출하면 반드시 해당 close () 를 호출 할 필요가있다. close를 사용하려면 <unistd.h>를 포함 할 필요가있다.

참고 문헌 편집 ]

소켓 인터페이스의 정당한 표준 정의는 다음과 POSIX 표준에 포함되어있다.

  • IEEE Std. 1003.1-2001 Standard for Information Technology - Portable Operating System Interface (POSIX).
  • Open Group Technical Standard : Base Specifications, Issue 6, December 2001.
  • ISO / IEC 9945 : 2002

이 표준에 대한 정보와 현재 진행 작업 내용은 the Austin website 에있다.

소켓 API의 IPv6 확장은 RFC 3493 와 RFC 3542 에있다.

이 기술은 GNU Free Documentation License 에게로 공개되는 컴퓨터 용어 사전 " Free On-line Dictionary of Computing (FOLDOC)」에 근거하고 있습니다.

관련 항목 편집 ]

외부 링크 편집 ]