본문 바로가기

CS/네트워크

13. 네트워크 프로그래밍

쉽게 배우는 데이터 통신과 컴퓨터 네트워크를 정리한 내용입니다

1. 소켓의 주소 체계

소켓은 네트워크 통신에서 양 종단의 교신점 역할을 한다.

종단의 교신점은 크게 두 종류로 나눌 수 있다.
- 연결형 서비스 : 전화서비스처럼 양쪽의 합의에 따른 연결과정이 필요한 서비스이다.
- 비연결형 서비스 : 우편서비스와 비슷하다. 송신,수신자가 직접 연결된게 아닌 대리로 작업을 해주는 서비스이다.

2개의 독립된 프로세스가 소켓을 이용해 통신하기위해서는 소켓 주소가 필요하다.

소켓의 경우 기능이 있는것이 아니고 운영체제에서 제공하는 통신 프로토콜을 편하게 사용할 수 있게 도와주는 역할을 하는데, 따라서 프로토콜의 종류에 따라 프로토콜이 지원해주는 주소체계를 따른다.

1) 소켓 주소

소켓은 프로토콜 종류에 따라 다양한 방식으로 주소를 부여한다.

일반 프로그래밍 환경에서는 AF_UNIX, AF_INET의 소켓 주소 체계를 많이 사용한다.

 

유닉스 주소 체계(AF_UNIX)

하나의 호스트 내부에서 실행되는 프로세스 사이의 통신을 지원하며, 사용하는 주소체계파일 시스템의 경로명을 기반으로 한다

유닉스 주소 체계를 표시하는 구조체는 두개의 필드를 사용하며 필드의 용도는 아래와 같다

struct sockaddr_un {
	short sun_family;
	char sun_path[108];
};
  • sun_family : AF_UNIX를 값을 가지는 필드
  • sun_path : 소켓을 구분하는 주소의 파일 경로명을 기록하는 필드

인터넷 주소 체계(AF_INET)

하나의 호스트 내부에서 실행되는 프로세스 통신을 지원하는 유닉스 주소 체계와 달리, 서로 다른 호스트에서 실행되는 프로세스 사이의 통신을 지원한다. 일반적으로 인터넷을 이용해 통신하는 모든 프로세스는 이 주소 체계를 사용한다.

 

AF_INET방식은 소켓이 생성되는 호스트의 IP주소와 포트 번호를 조합하여 소켓 주소를 표시한다. 

인터넷 주소 체계를 지원하는 구조체는 4개의 필드를 사용한다

struct sockaddr_in {
	short sin_family;
    u_short sin_port;
    struct in_addr sin_addr;
    char sin_zero[0];
};
  • sin_family : AF_INE값을 가지는 필드
  • sin_addr & sin_port : IP주소 & 포트 번호를 기록함 (이 조합의 값이 중복되는 프로세스는 유일하기 때문에 중복되는 프로세스는 동시에 존재할 수 없다)
  • sin_zero : 사용되지 않고 있는 필드이다

통합 주소 체계

소켓의 주소 체계는 사용하는 전송 계층 프로토콜에 따라 달라지기 때문에 운영체제에서 제공하는 통신 프로토콜의 수가 늘면 주소 체계 표현 방식도 증가한다.

소켓은 통일된 형식의 소켓 시스템 콜을 이용하는데 표현 형식이 다른 구조체가 한번에 들어와 이를 소켓이 수용하는게 문법적으로 불가능하다. 이런 경우 여러 소켓 구조체를 통합해 일반 구조체 하나로 정의할 필요가 있다. 

struct sockaddr {
	u_short sa_family;
    char sa_data[14];
}
인터넷을 이용한 소켓 프로그래밍시 소켓 변수는 sockaddr_in 구조체로 선언된다(addr변수에 ip와 포트번호 값을 지정)
이때 맨 아래에 있는 소켓 시스템 콜 bind()는 공통적으로 사용되는데 이때 bind의 매개변수가 고정이 되지 않는다. 
따라서 이런경우에는 공통 주소 체계인 sockaddr 구조체로 변경해 문법의 통일성을 유지해야 한다

2) 소켓 서비스

제공하는 서비스에 다른 소켓 유형도 다양하게 나뉜다.

  • SOCK_STREAM : 연결형 서비스를 의미한다. 인터넷에서 TCP 프로토콜에 대응한다
  • SOCK_DGRAM : 비연결형 서비스를 의미한다. UCP 프로토콜에 대응된다
  • SOCK_RAW : 일반 네트워크 응용 프로그램 개발에서는 자주 사용되지 않으며, IP프로토콜을 직접 사용한다

3) 소켓의 기본 함수

소켓 시스템 콜을 이용한 네트워크 프로그래밍에서는 7개의 기본 함수가 사용된다

  1. socket(int domain , int type, int protocol) : 매개변수로 지정한 유형에 따라 소켓을 생성한다. 시스템 콜이 성공적으로 실행되어 소켓이 만들어지면, 해당 소켓의 디스크립터를 반환한다
  2. bind(int s, const struct sockaddr *name, socklen_t *namelen) : socket()함수로 생선된 소켓에 주소를 부여한다. s는 socket()에서 반환된 소켓 디스크립터이고 name은 바인드될 소켓의 주소값이다
  3. listen(int s, int backlog) : 첫번째 매개변수로 표시한 소켓을 활성화 시키는데, 보통 서버 프로세스에서 실행된다. 
  4. accept(int s, struct sockaddr *addr, socklen_t *addrlen) : 보통 서버 프로세스에서 실행되며 첫번째 매개변수로 지정한 소켓에서 클라이언트의 연결요구가 들어올때까지 대기한다. 클라이언트로 부터 연결설정 요청이 들어오면 둘 사이에 연결이 설정되고, 서버에 새로운 소켓이 자동으로 생성된다.
  5. connect(int s, const struct sockaddr *name, socklen_t namelen) : 클라이언트 프로세스에서 사용되며, name이 가르키는 서버와 연결 설정을 시도한다. 존재하지 않으면 오류가 발생/서버가 연결대기중이면 연결됨
  6. send(int s, const void *msg, size_t len, int flags) : 연결형 서비스를 제공하는 환경에서 데이터를 전송하는 역할을 한다. s로 표시한 소켓을 통해 상대방 프로세스에 msg에 보관된 데이터를 전송한다
  7. recv(int s, void *buf, size_t len, int flags) : 연결형 서비스에서 데이터를 수신하는 역할을 한다. s로 표시한 소켓을 통해 데이터를 수신하고, 수신한 데이터를 buf에 보관한다.

2. 클라이언트, 서버 프로그래밍

- 서비스를 제공하는 프로그램 : 서버 프로세스

- 서비스를 제공받는 프로그램 : 클라이언트 프로세스

1) 연결형 서비스

2개의 독립된 프로세스가 네트워크를 통해 통신하려면 논리적인 연관관계를 맺어주는 소켓이 필요하다.

위의 이미지처럼 connect() 요청시점때 클라이언트와 연결이 되었다는걸 서버가 인지할 수 있다.

 

클라이언트와 서버의 동작

서버는 다수의 클라이언트에 공개되는 well-known 포트로 자신의 소켓 주소를 설정하고 클라이언트의 요청을 기다린다

이후 클라이언트 요구에 따라 연결이 설정되면 서버가 제공하는 서비스가 시작된다

서버의 동작과정
1. 서비스 교신점(ip,포트번호) 공개
2. 클라이언트로부터 발생하는 서비스 요구 대기
3. 클라이언트에 서비스 제공 -> 서비스 제공 완료
4. 2번으로 이동

서비스 시간이 짧다면 서버 프로세스를 생성하지 않고 하위 서버 프로세스를 생성하지 않고 직접 서비스를 제공한다. 

서비스 시간이 길어진다면 다른 클라이언트가 대기하는 시간이 길어지기때문에 하위 서버 프로세스를 생성해 클라이언트에 서비스를 제공하도록 중계역할을 하고 서버 프로세스는 2단계로 돌아가 다른 클라이언트의 요청을 받을 수 있다.

클라이언트의 동작과정
1. 원하는 서비스를 제공하는 서버 확인
2. 해당 서버와 연결 시도
3. 서버에 서비스 요청
4. 서비스 이용 완료

연결형 서비스는 연결 절차가 필요하므로 클라이어트뿐 아니라 서버도 연결이 설정되어있다는 사실을 인지 할 수 있다.

따라서 데이터 송수신 과정에서 누가먼저 send()함수를 이용해 데이터를 송신할지는 응용프로그램 마다 필요에 따라 선택할 수 있다.

2) 비연결형 서비스

비연결형 서비스에서는 connect()와 accept()함수로 연결을 설정하는 과정이 생략되어 있다.

연결형은 connect() 요청시점때 클라이언트와 연결이 되었다는걸 서버가 인지할 수 있으나 비연결형 서비스에서는 연결절차가 생략되어 있으므로 sendto()요청시에만 서버가 클라이언트를 인지할 수 있다.