Yebali

[Socket] Socket이란? 본문

Backend Common

[Socket] Socket이란?

예발이 2023. 1. 17. 20:12

Socket이란?

네트워크상에서 동작하는 프로그램 간 통신의 종착점(End-Point)으로 프로그램이 네트워크에서 데이터를 통신할 수 있도록 연결해주는 연결부이다.

프로세스는 데이터를 송/수신하기 위해 반드시 Socket을 열어서 데이터를 보내거나 받아야 한다.

즉, Application Layer에 존재하는 프로그램들은 Socket을 통해 Transport Layer를 거쳐 데이터를 주고 받는다.
(HTTP, FTP, TELNET 등이 Application layer에 속한다.)

소켓의 종류에는 TCP 소켓UDP 소켓이 있다.

TCP Socket

TCP를 사용하는 연결 지향 소켓이다.

데이터 송/수신의 신뢰성을 보장하며 데이터가 순서대로 송/수신된다.

TCP Socket은 Socket 간의 관계가 1:1이기 때문에 10개의 클라이언트에게 데이터를 보내기 위해서는 10개의 소켓이 필요하다.

UDP Socket

UDP를 사용하는 비 연결 소켓이다.

데이터 송/수신의 신뢰를 보장히지 못하고, 데이터가 순서대로 송/수신되는 것을 보장하지 못한다.

UCP Socket은 Socket 간의 관계가 1:N~N:N 이기 때문에 하나의 소켓으로 다수의 클라이언트에게 데이터를 보낼 수 있다.

Socket의 구성 요소

소켓은 Protocol, IP address, 포트번호로 구성된다.

소켓 통신에서는 "SRC IP:PORT // DST IP:PORT" 형태의 정보로 접속을 구분한다.  

  • Protocol : 컴퓨터들 간의 원활한 통신을 위해 지키기로 약속한 일종의 통신 규약
  • IP address : 컴퓨터 네트워크에서 장치들이 서로 인식하고 통신하기 위한 특수한 번호(주소)
  • 포트번호 : 호스트 내에서 실행되고 있는 프로세스를 구분하기 위한 논리적인 번호이다.
    0~65536까지 사용되고 일부 포드 번호는 Well-known Port라고 하여 미리 용도가 정해져 있다.

Socket 갯수 제한

Socket의 연결 정보는 "SRC IP:PORT // DST IP:PORT" 정보로 구분된다.

일반적으로 포트번호는 Well-known Port를 제외하고 약 50K 개를 사용할 수 있으며,
클라이언트(DST)의 IP:PORT가 다르다면 하나의 서버(SRC) 포트에 N개의 소켓을 만드는 것이 가능하다.

즉, 이론상 약 50K * N 개의 소켓을 만드는 것이 가능하다.

리눅스에서는 Socket을 하나의 파일로 간주하고 Socket이 열릴 때마다 FD(File Descriptor)하나가 생성되기 때문에 실제로 서버가 열 수 있는 Socket의 수는 FD수로 제한되기도 한다.

*File Discriptor : 리눅스에서 프로세스에서 특정 파일에 접근할 때 사용하는 추상적인 값.

 

TCP Socket 통신의 흐름

Server

Client Socket의 연결 요청을 대기하고, 연결 요청이 오면 Client Socket을 생성하여 통신을 가능하게 한다.

  1. socket() 함수를 이용하여 소켓을 생성
  2. bind() 함수를 이용하여 IP address와 포트번호를 설정
  3. listen() 함수를 이용하여 수신 대기열을 만듦
  4. accept() 함수를 이용하여 Client와의 연결을 기다림

Server 예시 코드

더보기
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>

#define BUF_SIZE 5

void error_handling(char *message);

int main(int argc, char *argv[]){
    int serv_sock, clnt_sock;
    char message[BUF_SIZE + 1];
    int str_len, i;

    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;

    if(argc != 2){
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if(serv_sock == -1)
        error_handling("socket() error");
    
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_adr.sin_port=htons(atoi(argv[1])); // port
    
    if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
        error_handling("bind() error");

    if(listen(serv_sock, 5) == -1){
        error_handling("listen() error");
        exit(1);
    }
    
    clnt_adr_sz=sizeof(clnt_adr);
    clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
    if(clnt_sock == -1){
        error_handling("accept() error");
        exit(1);
    }
    
    while((str_len = recv(clnt_sock, message, BUF_SIZE, 0)) != 0){
        message[str_len] = 0;
        printf("read data : %s\n", message);
    }

    close(clnt_sock);
    close(serv_sock);
    return 0;
}

void error_handling(char *message){
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

Client

  1. socket() 함수를 이용하여 소켓을 생성
  2. connect() 함수를 이용하여 서버에 연결 시도
  3. send()/recv() 함수를 이용하여 통신

Clinet 예시코드

더보기
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>

#define BUF_SIZE 100

void error_handling(char *message);

int main(int argc, char *argv[]){
    int sock;
    char message[BUF_SIZE];
    int str_len;
    struct sockaddr_in serv_adr;

    if(argc != 3){
        printf("Usage : %s <IP> <port> \n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_STREAM, 0);
    if(sock == -1)
        error_handling("socket() error~");

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = inet_addr(argv[1]); //ip address
    serv_adr.sin_port = htons(atoi(argv[2])); //port

    if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1){
        error_handling("connect() error");
        exit(1);
    }

    //처음 3번 전송
    char *first = "0123456789";
    char *second = "ABCDEFGHIJ";
    char *third =  "KLMNOPQRST";
    send(sock, first, strlen(first) , 0);
    send(sock, second, strlen(second), 0);
    send(sock, third, strlen(third), 0);

    while(1) {
        fputs("Input message(Q to quit) : ", stdout);
        fgets(message, BUF_SIZE, stdin);

        if(!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
            break;
        send(sock, message, strlen(message), 0);
    }
    close(sock);
    return 0;
}

void error_handling(char *message){
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

UDP Socket의 경우 'listen()', 'connect()', 'accept()' 과정이 생략된다.

 

 

 

참조

https://helloworld-88.tistory.com/215

https://velog.io/@rhdmstj17/%EC%86%8C%EC%BC%93%EA%B3%BC-%EC%9B%B9%EC%86%8C%EC%BC%93-%ED%95%9C-%EB%B2%88%EC%97%90-%EC%A0%95%EB%A6%AC-1

https://on1ystar.github.io/socket%20programming/2021/03/16/socket-1/

 

'Backend Common' 카테고리의 다른 글

docker-compose  (0) 2023.07.09
분산락과 Redisson  (0) 2023.05.08
Time zone과 표준시  (0) 2022.11.27
[WebSocket] WebSocket이란?  (0) 2022.09.27
[JAVA] Garbage Collection의 개념과 동작  (0) 2022.09.25