소켓(Socket) 통신 개념과 c언어 예제

반응형
  1. Socket 통신
  2. Socket 특성
  3. Socket 생성
  4. Linux Socket 통신 과정

 

오늘은 소켓 통신에 대해 정리했던 글을 포스팅하려고 한다. 일상생활에서 전구 중 나사 형태로 돌려서 넣거나 빼는 형태를 소켓이라 하고, 그래픽 카드와 같은 부품을 메인보드에 장착하는 부품도 소켓이라고 한다. 즉, 소켓(Socket)은 뭔가를 끼워 넣어 접속시키는 기구를 말한다.

 

 

1. Socket 통신

 

소켓 통신은 서버 클라이언트 구조에서 특정한 포트를 통해 서로 양방향 통신하는 것을 말한다. 소켓을 이용한 TCP/IP 프로그래밍을 소켓 프로그래밍이라 한다. 일반적으로 소켓 프로그래밍이라 하면 유닉스/리눅스 환경을 말하고, 윈도우 환경에서는 WinSock(Windows Socket, 윈속)이라는 명칭을 사용한다.

 

서버 측 클라이언트 측
1. 서버 응용프로그램에서 소켓을 생성한다. (이 소켓은 다른 프로세스와 공유되지 못한다.)
2. 서버 프로세스는 bind라는 시스템 호출을 이용하여 이름을 부여한다.
3. 이름을 부여한 후, 해당 소켓에 클라이언트가 연결되기를 기다린다. 이를 위한 시스템 호출은 listen이다. (이 함수는 들어오는 연결을 위한 대기열을 생성한다.)
4. 연결 요청이 들어오면 서버는 시스템 호출 accept를 이용해서 그 요청을 받아들인다. 서버가 accept를 호출하면, 명명된 소켓과는 개별적인 새로운 소켓이 생성되며, 이 소켓은 해당 클라이언트 하나와의 통신에만 쓰인다.
5. 클라이언트의 명령을 받아서 수행한다.
  1. socket()을 통해 소켓을 호출해서 이름 없는 소켓을 생성한다.
  2. 서버의 명명된 소켓을 주소로 사용해 connect()를 호출함으로써 서버와의 연결을 만든다.
  3. 서버에 각종 명령을 전달한다.

 

 

2. Socket 특성

 

소켓을 규정하는 가장 중요한 특성 세 가지는 도메인, 종류, 프로토콜이다.

1) 도메인

도메인은 소켓 통신이 사용할 네트워크 매체를 결정한다. 가장 흔히 쓰이는 소켓 도메인은 AF_INET으로, 이는 인터넷 자체는 물론 리눅스 근거리 통신망에도 쓰이는 인터넷 네트워킹에 해당한다. 이 도메인의 프로토콜 패밀리는 인터넷 프로토콜(IP)이다.

2) 소켓 종류

인터넷 프로토콜은 서로 다른 서비스 수준들을 가진 두 가지 통신 메커니즘을 제공한다.

(1) 스트림 소켓

  • 스트림 소켓은 AF_INET 도메인에서 TCP/IP 연결을 통해 구현된다.
  • 표준 입, 출력 스트림과 비슷한 면을 가진 스트림 소켓은 순차적이고 신뢰성 있는 양방향 바이트 스트림 방식의 연결을 제공한다.
  • 소켓 생성 시 SOCK_STREAM을 지정하면 스트림 소켓이 만들어진다.

(2) 데이터그램 소켓

  • 데이터그램 소켓은 AF_INET 도메인에서 UDP/IP 연결을 통해 구현된다.
  • 순차성과 신뢰성을 보장하진 않지만, 네트워크 연결을 유지할 필요가 없기 때문에 속도가 빠르며, 자원 효율성이 좋다.
  • 소켓 생성 시 SOCK_DGRAM을 지정하면 데이터그램 소켓이 만들어진다.

3) 소켓 프로토콜

주어진 소켓 종류의 전송 메커니즘이 여러 개의 프로토콜을 제공하는 경우 프로그램은 그중 하나를 선택할 수 있다.

 

(현재는 UNIX 네트워크와 파일 시스템 기준으로 실습 예정)

 

 

3. Socket 생성

 

시스템 호출 socket은 소켓을 하나 생성하고 그 소켓에 접근하는 데 사용할 *file descriptor를 돌려준다. domain 매개변수에 사용할 수 있는 값들은 아래 표와 같다. 위에서 언급했듯이 리눅스와 유닉스에서 가장 흔히 쓰이는 소켓 도메인은 AF_UNIX와 AF_INET이다. 소켓의 종류를 결정하는 type 매개변수에 사용할 수 있는 값으로는 SOCK_STREAM과 SOCK_DGRAM이 있다.

 

*file descriptor: 프로세스가 파일, 소켓 등의 자원에 접근할 수 있도록 해주는 참조 번호

 

도메인 설명
AF_UNIX UNIX 내부(파일 시스템 소켓)
AF_INET ARPA 인터넷 프로토콜(유닉스 네트워크 소켓)
AF_ISO ISO 표준 프로토콜들
AF_NS Xerox Network Systems 프로토콜들
AF_IPX Novell IPX 프로토콜들
AF_APPLETALK Appletalk DDS

 

 

4. Linux Socket 통신 과정

1) Socket 주소

소켓 주소의 형식은 소켓의 도메인에 따라 다르며, 아래는 주소 형식을 위한 여러 구조체들 중 하나이다. AF_UNIX 소켓의 경우 sockaddr_un이라는 구조체를 통해서 주소를 지정하는데, 이 구조체는 sys/un.h 헤더 파일에 정의되어 있다.

struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[]; /* 경로 이동 */
};

 

AF_INET 도메인에서는 sockaddr_in이라는 구조체로 주소를 지정하는데, 이 구조체는 netinet/in.h 헤더 파일에 정의되어 있다.

struct sockaddr_in {
short int sin_family; /* AF_INET */
unsigned short int sin_port; /* 포트 번호 */
struct in_addr sin_addr; /* IP 주소 */
}

 

 

위 구조체에서 IP 주소를 의미하는 구조체 in_addr의 정의는 아래와 같다.

struct in_addr {
unsigned long int s_addr;
};

 

 

2) 소켓에 이름 붙이기(bind)

socket으로 생성한 소켓을 다른 프로세스들이 사용할 수 있게 하려면 서버 프로그램이 그 소켓에 하나의 이름을 부여해야 한다. 이러한 이름을 부여하는 함수는 bind이다.

//AF_UNIX 소켓 이름 부여

#include <sys/socket.h>
int bind(int socket, const struct sockaddr *address, size_t address_len);

 

bind 시스템 호출은 socket 매개변수로 지정된 이름 없는 소켓에 address 매개변수로 지정된 주소 구조체에 담긴 주소를 부여한다. address_len 매개변수는 그 주소 구조체의 길이이다. bind는 성공 시 0을, 실패 시 -1을 돌려준다. 실패 시 errno 변수에는 다음 값들 중 하나가 설정된다.

 

errno 값 설명
EBADF 소켓 서술자가 유효하지 않음
ENOTSOCK 소켓 서술자가 실제 소켓을 가리키지 않음
EINVAL 소켓 서술자가 이미 명명된 소켓을 가리킴
EADDRNOTAVALL 주소를 인식할 수 없음
EADDRINUSE 이미 다른 소켓에 부여된 주소임

 

AF_UNIX 소켓의 경우에는 다음 값들도 설정될 수 있다.

errno 값 설명
EACCESS 접근 권한 때문에 파일 시스템상의 경로 이름을 생성할 수 없음
ENOTDIR, ENAMETOOLONG 경로 이름이 부적합함

 

3) 소켓 연결 대기열 만들기(listen)

소켓으로 연결 요청을 받아들이려면 서버는 반드시 요청을 담아 둘 대기열을 만들어야 한다. 연결 요청 대기열은 서버가 한 클라이언트의 요청을 처리하느라 바쁜 상황에서 다른 클라이언트들의 요청들을 대기 시킬 수 있는 여유(?)를 제공한다. 대기열을 생성하는 시스템 호출은 listen이다.

#include <sys/socket.h>

int listen(int socket, inst backlog);

 

backlog 매개변수는 listen이 생성할 대기열의 크기, 즉 대기열에 담을 수 있는 연결 요청들의 최대 개수이다. 리눅스 시스템에 따라 최대 개수의 제한이 있을 수 있다. 대기열이 꽉 찬 이후에 들어오는 연결 요청들은 자동으로 거부되며, 클라이언트의 연결 시도가 실패한다. (주로 backlog 값으로 5를 사용함) listen 함수는 성공 시 0을, 실패 시 -1을 돌려준다.

 

4) 연결 수락(accept)

서버가 소켓을 생성, 명명하고 연결 대기열을 생성한 후에는 accept 시스템 호출을 이용해서 소켓으로의 연결 요청을 받아들인다.

#include <sys/socket.h>

int accept(int socket, struct sockaddr *address, size_t *address_len);

 

accept 함수는 클라이언트와의 통신을 위한 새로운 소켓을 생성하고, 이 소켓을 통해 클라이언트와 데이터를 송수신할 수 있다. accept 함수는 클라이언트의 주소 정보를 반환하며, 서버는 이 정보를 사용하여 클라이언트를 식별할 수 있다. accept는 클라이언트 요청을 성공적으로 수락했다면 새 소켓 file descriptor를, 실패했다면 -1을 돌려준다.

 

5) 연결 요청(Connect)

클라이언트는 자신의 이름 없는 소켓을 서버의 청취 소켓과 연결함으로써 서버와의 통신 경로를 연다. 서버의 청취 소켓과 연결하는 함수는 connect이다.

#include <sys/socket.h>

int connect(int socket, const struct sockaddr *address, sizet address_len);

 

socket 매개변수는 클라이언트 쪽 소켓의 file descriptor이고 address는 서버 쪽 소켓의 주소를 담은 구조체를 가리킨다. address_len은 그 구조체의 길이이다. socket 매개변수에는 반드시 호출로 얻은 유효한 file descriptor를 지정해야 한다. connect는 성공 시 0을, 실패 시 -1을 돌려준다.

 

6) 소켓 닫기(close)

서버와 클라이언트 모두, 소켓 file descriptor에 대해 close를 호출해서 소켓 연결을 끝낼 수 있다. 소켓은 항상 양쪽 끝 모두를 닫아야 한다.

 


 

일단 오늘은 이론만 정리하고, 추후 c언어로 실습했던 내용을 업데이트하려고 한다. 다음 포스팅 땐 다중 클라이언트의 요청을 받아들이고 처리하는 방법과 종류에 대해 알아보고 업로드할 예정이다. 오늘도 읽어주셔서 감사합니다!

반응형