미새문지

크래프톤 정글 week06, day46 - 소켓, 파일 디스크립터, 잔디심기 본문

크래프톤 정글/TIL

크래프톤 정글 week06, day46 - 소켓, 파일 디스크립터, 잔디심기

문미새 2024. 2. 23. 00:34
728x90

소켓

  • 네트워크 프로그래밍이란 네트워크로 연결된 두 컴퓨터가 데이터를 주고 받을 수 있도록 하는 것이다.
  • 데이터를 제공하기 위해 요청을 대기하며 수락하는 쪽을 서버(Server)라고 하며, 요청을 진행하는 쪽을 클라이언트(Client)라고 한다.
  • 서버와 클라이언트 사이에 지켜야 할 통신 규약을 프로토콜(Protocol)이라고 한다.
  • 네트워크를 이해하기 위해서는 소켓(socket)에 대해 알아야 하는데, 소켓은 물리적으로 연결된 네트워크상에서의 데이터 송수신에 사용할 수 있는 소프트웨어 적인 장치를 의미한다.
    • 두 컴퓨터를 연결하기 위해서는 소켓이 필요하며, 클라이언트인지 서버인지에 따라 소켓을 다루는 방법이 다르다.

https://github.com/jwvg0425/ProjectArthas/wiki/Network-(TCP-IP-기본)

  • 서버는 socket → bind → listen → accept → read/write → close로 진행
  • 클라이언트는 socket → connect → read/write → close로 진행된다.

 

< 함수 >

  • socket()
    • 데이터 송수신에 사용할 수 있는 소프트웨어적인 장치이다.
    • 소켓은 네트워크에 필요한 주소 정보를 담을 수 있고, 주고 받는 데이터를 쓸 수 있는 함수이다
      • 그래서 I/O 함수와 같은 read, write, close 시스템을 사용한다.
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

// sockfd : 주소를 지정하고자 하는 소켓 디스크립터
// sockaddr : 통신 방식과 주소 정보를 담은 구조체
// addr_len : sock_addr 구조체 크기

 

  • listen()
    • 서버에서 주소와 포트를 지정하면 클라이언트에서 요청이 오기를 기다려야 하는데, 클라이언트에서 연결 요청이 왔을 때 요청을 받는 함수이다.
    • listen 함수를 호출하면 OS 내부적으로 연결 요청을 차례대로 저장할 대기 큐를 만드는데, listen 함수는 요청을 받아 대기 큐에 넣기만 할 뿐이라 데이터 통신에는 관여하지 않는다.
#include <sys/socket.h>

int listen(int socket, int backlog);

// socket : 읽는 동안 대기할 소켓 디스크립터
// backlog : 대기 메세지 큐의 수

 

  • accept()
    • 클라이언트의 요청을 받아들여 클라이언트 소켓과 서버 소켓을 연결해 데이터를 주고 받을 수 있는 함수이다.
    • accept 함수를 호출하면 소켓에 읽고 쓰기를 하며 TCP/IP 알고리즘에 의해 데이터를 주고 받을 수 있게 된다.
#include <sys/socket.h>

int accept(int socket, struct sockaddr* sock_addr, socklen_t* addr_len);

// socket : 서버 소켓 디스크립터
// sock_addr : 클라이언트 주소 정보를 가지고 있는 구조체의 포인터
// addr_len : sock_addr이 가리키는 구조체의 크기

 

  • connect()
    • 클라이언트 측에서 서버 측으로 연결 요청을 할 때 사용되는 함수이다.
    • connect 함수가 호출되면 서버로 연결 요청 데이터가 전송되고, 서버에 의해 accept 함수가 호출되면 서버 소켓과 클라이언트 소켓이 연결되어 데이터 통신이 가능해진다.
#include <sys/socket.h>

int connect(int sockfd, struct sockaddr* serv_addr, socklen_t addr_len);

// sockfd : 소켓(클라이언트) 디스크립터
// serv_addr : 서버 주소 정보 구조체에 대한 포인터
// addr_len : serv_addr 구조체의 크기

 

  • close()
    • 네트워크 통신을 마무리하고 소켓을 닫아 연결을 끊기 위한 함수이다.
    • close 함수가 호출되면 호출한 쪽의 소켓을 닫아버리고, 현재 전송 중인 데이터가 있다면 소멸시킨다.
    • close 함수는 인자로 전달된 socket_fd의 참조 카운터를 하나 감소시키는데 0이 된다면 종료시킨다.
# 서버 코드
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('', 8080))
server_socket.listen()

client_socket, client_address = server_socket.accept()

# 데이터 송수신

client_socket.close()
server_socket.close()

 

  •  shutdown()
    • 소켓을 닫는 함수는 close 함수 외에도 shutdown()이라는 함수가 있는데, close 함수와 달리, 함수를 호출할 경우 소켓을 닫지만 read/write 버퍼 중 하나를 선택해 닫을 수 있다.
    • 인자
      • SHUT_RD : 소켓으로의 read 버퍼를 닫아서 수신을 할 수 없게 한다.
      • SHUT_WD : 소켓으로의 write 버퍼를 닫아서, 송신을 할 수 없게 한다.
      • SHUT_RDWD : 소켓으로의 read/write 버퍼를 닫아서 수신/송신을 할 수 없게 한다.
# 클라이언트 코드
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('localhost', 8080))

# 데이터 송신

client_socket.shutdown(socket.SHUT_WR)

# 데이터 수신

client_socket.close()

 

 

close()와 shutdown()의 차이

  • close()는 양방향 통신을 모두 종료하지만 shutdown()은 매개변수에 따라 송신이나 수신 한 가지만 종료할 수도 있다.
  • close()는 소켓 리소스를 해제하고 시스템 자원을 반환하지만 shutdown()은 송신을 종료해도 수신이 가능하다.
  • close()를 호출하면 호출한 쪽이나 반대쪽이나 둘 다 데이터를 송수신할 수 없으며 ECONNRESET 오류를 받게 된다.

사용 처

  • close()
    • 더 이상 네트워크 연결을 사용하지 않을 때 사용
    • 연결을 즉시 종료하고 리소스를 해제해야 하는 경우 사용
    • 즉각적인 연결 종료와 리소스 해제를 위해 사용
  • shutdown()
    • 데이터 송수신을 단계적으로 종료해야 하는 경우 사용
    • 한 쪽에서 데이터 송수신을 종료하면서 다른 쪽은 계속 데이터를 송수신하도록 허용해야 하는 경우 사용
    • 단계적인 연결 종료와 송수신 제어를 위해 사용

ECONNRESET 오류

  • 네트워크 연결이 강제로 종료되었을 때 발생하는 오류
  • 원인
    • close() 또는 shutdown() 함수를 호출하여 연결을 닫았을 때 발생
    • 네트워크 장비 오류, 타임아웃 등으로 연결이 끊어졌을 때 발생
    • 연결된 프로세스가 종료되었을 때 발생
  • close() 또는 shutdown() 함수를 올바르게 사용했는지 확인
  • 해결 방법
    • 연결된 프로세스가 실행 중인지 확인
    • 네트워크 케이블 연결, 라우터 및 액세스 포인터 상태를 확인

출처 : https://ko.wikipedia.org/wiki/파일_서술자

파일 디스크립터

  • 리눅스 혹은 유닉스 계열의 시스템에서 프로세스가 파일을 다룰 때 사용하는 것으로 운영체제가 특정 파일에 할당해주는 정수값이다.
정수값 이름
0 표준 입력(stdin)
1 표준 출력(stdout)
2 표준 오류(stderr)
  • 유닉스 시스템에서는 일반적인 파일부터 디렉토리, 소켓, 파이프, 블록 등 모든 객체들을 파일로 관리하는데, 유닉스 시스템에서 프로세스가 이 파일들을 접근할 때 파일 디스크립터를 이용한다.
  • 작동 방식
    1. 프로세스가 파일을 열면 커널은 해당 프로세스의 파일 디스크립터 숫자 중 사용하지 않는 가장 작은 값을 할당한다.
    2. 그 다음 프로세스가 열려있는 파일에 시스템 콜을 이용해 접근할 때, 파일 디스크립터 값을 사용하여 파일을 지칭한다.
  • 장점
    • 파일 이름에 대한 의존성을 줄여준다.
      • 파일 이름은 변경될 수 있지만, 파일 디스크립터는 변경되지 않는다.
    • 파일 관리를 간편하게 한다.
      • 파일 디스크립터를 사용하면 파일을 쉽게 열고, 닫고, 읽고, 쓰고, 조작할 수 있다.
    • 시스템 성능을 향상시킬 수 있다.
      • 파일 이름을 매번 검색하는 대신, 파일 디스크립터를 사용하면 파일 접근 속도를 높일 수 있다.
  • 단점
    • 파일 디스크립터는 유한한 자원이다.
      • 시스템마다 사용할 수 있는 파일 디스크립터의 최대 개수가 정해져 있다.
    • 파일 디스크립터는 닫아야 한다.
      • 더 이상 파일을 사용하지 않으면 파일 디스크립터를 닫아야 시스템 자원을 확보할 수 있다.
    • 파일 디스크립터는 리소스 누수를 발생시킬 수 있다.
      • 파일 디스크립터를 닫지 않으면 리소스 누수가 발생하여 시스템 성능 저하를 초래할 수 있다.

 

오늘의 잔디심기

백준
1246
JavaScript
실버4
온라인 판매
let OnlineSell = () => {
    const fs = require("fs");
    const filePath = process.platform === "linux" ? "/dev/stdin" : "./input.txt";
    let input = fs.readFileSync(filePath).toString().split("\n");

    const [n, m] = input[0].split(" ").map(Number);
    const prices = input.slice(1, m+1).map(Number).sort((a, b) => b - a);
    
    let margin = 0;
    let priceegg = 0;

    for (let i = 0; i < prices.length; i++) {
        let lowprice = prices[i] * Math.min(n, i+1);
        if (lowprice > margin) {
            margin = lowprice;
            priceegg = prices[i];
        }
    }

    console.log(priceegg + " " + margin);
}

OnlineSell();

 

5주차가 끝나고 6주차가 되면서 새로운 팀원과 이번 주차의 키워드인 웹서버에 대해 학습했다. 내일 유명한 교수님의 강의가 있기 때문에 공부 키워드를 미리 학습하고 가면 도움이 될까 하여 오늘 코어타임에 최대한 땡겼다.

 

학습 시간 : 10 ~ 25시

728x90