크래프톤정글6주차 - 소켓 , 파일 디스크립터 , datagram/stream socket

김태성·2024년 2월 22일
0
post-thumbnail

소켓이란?

리눅스 커널의 관점에서 웹 소켓은 네트워크 상의 두 프로그램 간 양방향 통신을 위한 엔드포인트다.

UNIX 관점에서 본다면 소켓은 식별자를 가진 열린 파일이다.

소켓 인터페이스

소켓 인터페이스는 네트워크 애플리케이션을 설계하기 위해 UNIX 입출력 함수와 연결하는데 쓰이는 함수의 집합이다.

클라이언트에서 TCP/IP로 넘어가기 전에 시스템 콜을 하는 영역으로,
클라이언트에서 연결을 요청하면 클라이언트 소켓 주소에 들어가는 포트는 커널에서 자동으로 배정한다.

여기서 처음 TCP 서버에서 socket을 미리 만드는데,
이 소켓은 클라이언트로부터 정보를 받는 소켓이고,
클라이언트와 통신할때는 새로운 소켓을 만든다.
생각보다 중요한 개념이니 잘 알아두고 넘어가자

socket() 함수

소켓 생성 socket()

클라이언트/서버가 소켓 판별자(socket descriptor)를 만들기 위해 사용하는 함수이다.

clientfd = socket(AF_INET , SOCK_STREAM , 0);
  • AF_INET : 네트워크 주소체계를 알려준다. AF_INET는 32비트 주소를 나타낸다.(IPv4)

  • type : 소켓이 어떤 타입(TCP/UDP 등)인지 알려준다. SOCK_STREAM은 TCP 프로토콜을 사용한 통신 소켓이라는 뜻이다.

  • protocol : 프로토콜 정보. 앞의 두개 인자를 이용해 프로토콜을 특정할 수 없을때 따로 명시해준다. 그 외는 0으로 처리한다.

소켓 주소 할당

socket()함수로 socket descriptor를 할당받고 소켓 유형을 지정한 후 , IP주소와 포트 번호를 할당하기 위해 위의 소켓 구조체를 정의해야 한다.

generic 소켓 주소 구조체 sockaddr

/* Generic socket address structure (for connect, bind, and accept) */
struct sockaddr
{
	uint16_t  sa_family;    /* Protocol family */
    char      sa_data[14];  /* Address data  */
};

이 소켓의 종류가 무엇인지 담고있는 , 말 그대로 일반적인 형태의 구조체이다.
IPv4는 sockaddr_in 이라는 전용 구조체가 따로 있다.

IPv4 인터넷 소켓 주소 구조체 sockaddr_in

/* IP socket address structure */
  struct sockaddr_in  {
    uint16_t        sin_family;  /* Protocol family (always AF_INET) */
    uint16_t        sin_port;    /* Port number in network byte order */
    struct in_addr  sin_addr;    /* IP address in network byte order */
    unsigned char   sin_zero[8]; /* Pad to sizeof(struct sockaddr) */
};

  • sinfamilt : AF_INET 구조체는 IPv4 임으로 항상 동일하다.
    sin_port : 16비트 포트 번호
  • sin_addr : 32비트 IP 주소
  • IP주소와 포트 번호는 항상 네트워크 바이트 주소(빅 엔디안)으로 저장된다.
  • _in은 internet에 대한 축약이며 , input과는 연관이 없다.

아래는 예제의 일부분이다.

....
struct sockaddr_in serv_addr;
    char* hello = "Hello from client";
    char buffer[1024] = { 0 };
    if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("\n Socket creation error \n");
        return -1;
    }
 
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
 
    // Convert IPv4 and IPv6 addresses from text to binary


....

다음 선언으로 소켓을 생성하고

  • struct sockaddr_in serv_addr;

다음 선언으로 familt 와 포트 번호를 설정한다.

  • serv_addr.sin_family = AF_INET;
  • serv_addr.sin_port = htons(PORT);

서버의 시스템콜(bind , listen , accept)

bind()
소켓에 주소를 할당하는 함수이다.

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,
         socklen_t addrlen);
 // Returns 0 if OK, -1 on error

bind 함수는 커널에게 addr에 있는 서버의 소켓 주소를 소켓 식별자 socketfd와 연결 해달라고 한 후 , 성공시 0 , 에러시 -1을 리턴한다.

  • sockfd : socket()함수를 통해 배정받은 디스크립터 번호
  • *addr : IP주소와 PORT 번호를 지정한 sockaddr 구조체를 가리키는 포인터
  • addrlen : 주소 정보를 담은 변수의 길이

listen()
연결 요청을 대기하는 함수이다. 서버는 커널에게 해당 디스크립터가 클라이언트가 아닌 서버에 의해 사용 될 것이라고 명시해 주기 위해서 listen()을 호출한다.

#include <sys/socket.h>
int listen(int sockfd, int backlog);
// Returns: 0 if OK, −1 on error
  • sockfd : 소켓 디스크립터 번호
  • backlog : 연결 요청을 대기하는 큐의 크기

accept()
열결 요청을 수락하는 함수

#include <sys/socket.h>
int accept(int listenfd, struct sockaddr *addr, int *addrlen);
// Returns: nonnegative connected descriptor if OK, -1 on error
  • listenfd : 리스닝 소켓의 디스크립터
  • *addr : 대기 큐를 참조해 얻은 클라이언트의 주소 정보
  • addrlen : addr 변수의 크기

listendfd , connectedfd

듣기 식별자와 연결 식별자

  • listen.desc 와 connected desc는 소켓이 서버와 연결되었는지 아닌지 여부를 나타내며 , 소켓의 상태를 구분한다.

  • listen desc.는 클라이언트의 접속을 기다리는 상태의 네트워크 소켓이다.
    클라이언트 요청에 대한 끝점으로 한번 생성되며 서버가 살아있는 동안 계속 존재한다.

  • connected desc. 는 이미 서버와 연결되어 데이터를 주고받는 상태의 네트워크 소켓.
    클라이언트와 서버 사이에 연결된 연결의 끝점으로 , 서버가 연결 요청을 수락할 때 마다 생성되며,
    서버가 클라이언트에 서비스 하는 동안에만 존재한다.

  • 이 두 디스크립터를 구분함으로써 많은 클라이언트 연결을 동시에 처리 할 수 있는 동시성 서버를 만들 수 있다.

bind() , listen() , accept() 세 가지 함수를 순차적으로 호출함으로써 서버 측에서 데이터 송수신 준비를 마칠 수 있다.
최종적으로 클라이언트 측에서 connect() 함수를 통해 연결한 뒤 , write() 함수로 실제 데이터를 출력한다.
데이터 송수신이 완료 되면 , close()시스템 콜을 통해 소켓의 자원을 모두 제거해 준다.

클라이언트의 시스템콜(connect)

서버에서는 socket()함수로 소켓 디스크립터를 할당받은 뒤 bind() , listen() , accept() 함수를 순차적으로 호출해 클라이언트와 연결 할 수 있었다.
클라이언트에서는 대기중인 서버에 연결 요청(connect)을 통해 클라이언트 - 서버 간의 연결을 생성 할 수 있다.

connect()
서버에 연결 요청을 보낸다.

#include <sys/socket.h>
int connect(int clientfd, const struct sockaddr *addr,
            socklen_t addrlen);
// Returns: 0 if OK, −1 on error
  • clientfd : 클라이언트의 소켓 디스크립터 번호
  • *addr : 연결 요청할 서버의 주소 구조체
  • addrlen : 서버 주소 구조체 변수의 길이

++ TCP의 3way handshake
클라이언트에서 connect()를 호출하면 listen() - 대기중이던 서버에서 연결이 요청되고
서버는 accept()함수를 불러 서버와 클라이언트 간의 연결이 생성된다.

close()
통신을 끝낸다.

#include <sys/socket.h>
int close(int socket_fd);
// Returns : 0 if OK , -1 on error

shutdown()

#include <sys/socket.h>
int shutdown(int socket_fd , int how_to);
// Returns : 0 if OK , -1 on error

close와 같이 소켓을 종료하지만 두번째 매개변수 how_to의 값에 따라 readbuffer와 writebuffer를 차단할 지 선택할 수 있다.

++ 버퍼는 보내는 버퍼/받는 버퍼로 생각하면 편하다.
메세지를 보낼 경우 버퍼에 쌓아두고 순차적으로 데이터를 보낸다는 것이다.


SHUT_RD : recv buffer 만 차단한다.

  • shutdown(s, SHUT_RD);   

위처럼 호출 된 경우 더이상 해당 소켓으로 부터 통신을 수신할 수 없다.

SHUT_WR : send buffer 만 차단한다.

  • shutdown(s, SHUT_WR);

위처럼 호출 된 경우 더이상 해당 소켓(s)에게 송신을 할 수 없다.

SHUT_RDWR : 두 버퍼 모두 차단한다.

  • shutdown(s, SHUT_RDWR);

위처럼 호출 한 경우 더이상 해당 소켓과 송수신을 할 수 없다.

파일 디스크립터

파일 서술자 라고도 불리며 특정한 파일에 접근하기 위한 추상적인 키이다.
이 용어는 POSIX 운영체제에 쓰인다.(POSIX = 서로다른 UNIX os의 공통 API를 정리한 인터페이스 규격)

POSIX에서 파일 디스크립터는 정수, C언어 int형을 의미한다.

Datagram/Stream socket

Datagram socket

  1. 프로토콜 : UDP(user datagram protocol)
  2. 특징:
  • 비연결 지향적 : 데이터를 보내기 전에 수신자와의 연결을 설정할 필요가 없음
  • 신뢰성 없음 : 전송 데이터의 도착과 순서를 보장하지 않는다.
  • 빠른 속도 : 연결 설정도 없고 오버헤드도 없기 때문에 빠른 전송 가능
  • 용도 : 실시간 애플리케이션(streaming , online game)

Stream Socket
1. 프로토콜 : TCP(transmission control protocol)
2. 특징:

  • 연결지향적 : 데이터 전송 전에 통신할 상대방과 연결을 맺음. 데이터 전송이 끝날때까지 유지됨
  • 신뢰성 : 데이터가 순서대로 , 오류없이 전송됨. 손실 데이터는 재전송
  • 속도보다 신뢰도 중시 : 정확한 전송을 위해 오버헤드가 발생
  • 용도 : email , webpage 전송 등 정확성이 중요한 애플리케이션
profile
닭이 되고싶은 병아리

0개의 댓글

관련 채용 정보