2026년 2월 23일 월요일

(번역기) 보안 사고 업데이트: ioTube 브리지 취약점 및 복구 로드맵

 https://x.com/iotex_io/status/2025824807120412842


요약 보고서

2026년 2월 21일, IoTeX 팀은 IoTeX 멀티체인 브리지인 ioTube에서 발생한 보안 침해 사고를 인지하고 비상 대응 체제로 전환했습니다. 이번 공격은 이더리움 체인 내에서만 진행되었습니다. 본 보고서는 발생한 사건의 전말, 자금 현황, 그리고 향후 복구 및 보상 계획을 자세히 설명하기 위해 작성되었습니다. 포렌식 분석 결과, 이번 공격은 정교하고 전문적인 공격으로, 과거 발생했던 주요 DeFi 공격 사건들과 연관이 있을 가능성이 있는 것으로 나타났습니다. 공격 주체에 대한 자세한 정보는 사후 분석 보고서에 포함될 예정입니다.


영향

* IoTeX L1 체인은 안전합니다: IoTeX 레이어 1 체인, 합의 메커니즘 및 모든 네이티브 스마트 계약은 손상되지 않았습니다.

* IoTeX 및 거래소의 사용자 자산은 안전합니다: IoTeX 체인과 중앙 집중식 거래소에 있는 IOTX 토큰은 영향을 받지 않습니다.

* 이번 침해는 ioTube의 이더리움 측 브리지 계약에만 영향이 있었고, BSC 및 Base와 같은 다른 체인의 브리지 계약은 영향을 받지 않았습니다.


사건 개요: 근본 원인 및 기술적 분석

이번 공격은 정교한 4단계 공격 과정을 통해 ioTube 브리지의 이더리움 사이드를 표적으로 삼았습니다.

* 검증자 키 탈취: 이더리움 상의 검증자 계약 소유자 계정이 탈취되어 공격자가 관리자 권한을 획득했습니다.

* 악성 업그레이드: 공격자는 이 접근 권한을 이용하여 검증자 계약을 모든 서명 및 유효성 검사를 우회하는 악성 버전으로 업그레이드했습니다.

* 계약 탈취: 검증자 계층을 장악한 공격자는 MintPool(토큰 발행)과 TokenSafe(예비 자산)를 제어하게 되었습니다.

* 자산 유출: 공격자는 4억 1천만 개의 CIOTX 토큰을 발행하고 브리지 예비 자산에서 약 440만 달러 상당의 다양한 토큰을 유출했습니다.


현재 자산 현황 및 복구 노력

사고 발생 후 몇 시간 만에 이를 감지하고 피해를 최소화하기 위한 즉각적인 조치를 취했습니다. 온체인 추적 결과, 도난당한 자산의 대부분은 이미 안전하게 보호되고 있습니다.

파트 A: 4억 1천만 CIOTX (MintPool)

공격자는 4억 1천만 CIOTX를 발행했습니다. 당사의 신속 대응 프로토콜을 통해 발행된 토큰의 86% 이상이 이미 잠금 또는 동결되었으며, 이는 당사의 체인 수준 제어를 통해 직접 보호할 수 있는 자산입니다. 추가로 12.8%(5,240만 IOTX)는 바이낸스에서 추적되었으며, 바이낸스 및 거래 파트너와 협력하여 동결 조치를 진행하고 있습니다. 0.4%(170만)는 탈중앙화 거래소(DEX)에서 스왑되었으며, 현재 위험에 노출되어 있습니다.

* 이더리움 및 베이스(3억 1,500만 CIOTX): 유동성이나 브릿지 경로 없이 완전히 잠겨 있습니다.

* IoTeX 체인(9,500만 IOTX): 적극적으로 복구 중입니다. 온체인 추적이 완료되었습니다. 9,500만 CIOTX 중:

- 4,050만 CIOTX는 공격자 지갑에 남아 있습니다. IoTeX 체인에서 공격자가 제어하는 ​​주소 29개를 확인했으며, 체인 수준 패치를 통해 모두 블랙리스트에 추가했습니다. 현재 체인 위임자들에게 패치를 배포하고 있습니다.

- 5,240만 CIOTX가 바이낸스에 입금되었습니다. 이 중 4,160만 CIOTX는 거래 파트너(이지비트, 체인지나우 등)를 통해 이동되었습니다. 바이낸스와 해당 거래 파트너들과 협력하여 입금된 모든 자금을 동결하고 있습니다.

- 170만 CIOTX가 탈중앙화 거래소(DEX)에서 다른 토큰으로 교환되었습니다. 이는 전체 발행 CIOTX의 0.4%에 해당하는, 위험에 처한 유일한 부분입니다.


파트 B: 브리지 준비금(TokenSafe)

공격자는 탈취한 준비금 토큰(USDC, USDT, WBTC, WETH 및 기타 자산 포함)을 약 2,183 ETH로 전환했습니다. 이 중 약 1,572 ETH는 THORChain을 통해 비트코인으로 브리지되었으며, 나머지는 현재 모니터링 중인 중간 이더리움 주소에 분산되어 있습니다.

현황: 총 66.78 BTC를 보유하고 있는 4개의 비트코인 ​​주소를 확인했습니다.

12V7jhcPnqnGbRFMasSW2CZVBd8qpvUgAK

16xusPKLMyqK68SkhfXDtic6AJPDi51tqh

1PN2BoHU4buDQWcrNHk9T9NBA2qX8oyYEc

135oSa2fobTxtHtm5dwTREDyRY2o1DG1Aw

모니터링: 이 보고서 작성 시점 기준으로 모든 BTC는 미사용 상태입니다. 해당 주소들은 플래그가 지정되어 있으며, 저희 팀과 분석 파트너가 24시간 내내 지속적으로 모니터링하고 있습니다.


즉각적인 조치 및 서비스 재개 일정

저희 팀은 모든 서비스를 완전히 복구하기 위해 24시간 내내 노력하고 있습니다.

* IoTeX L1 체인: 현재 체인 위임자들에게 패치를 배포하기 시작했습니다. 충분한 수의 위임자들이 패치를 완료하면 합의 및 정상 운영이 자동으로 재개됩니다.

* 거래소 활동: 출금은 24~48시간 이내에 재개될 예정이며, 입금 기능은 그 직후에 재개될 것입니다.

* 커뮤니티 AMA: 24~48시간 이내에 자세한 내용을 안내하는 커뮤니티 AMA를 진행할 예정입니다.

* 보상 계획: 영향을 받은 브리지 사용자들을 위한 자세한 보상 계획은 48시간 이내에 발표될 예정입니다.

* ioTube 브리지: 독립적인 보안 감사가 완료될 때까지 모든 체인에서 브리지 운영이 중단됩니다. 또한 IIP-55 구현 및 배포를 신속하게 진행할 것입니다.

* 법률 및 포렌식: 사법 당국 및 최고 수준의 온체인 분석 회사와 협력하고 있습니다. 공격자의 주소는 Etherscan에서 0x6487B5006904f3Db3C4a3654409AE92b87eD442f(Etherscan에서는 Fake_Phishing2054654로 표시됨)로 플래그가 지정되었습니다.

* 화이트햇 현상금: 공격자에게 자금을 자발적으로 반환하는 경우 화이트햇 현상금을 제공하는 온체인 메시지가 전송될 예정입니다.


커뮤니티에 대한 우리의 약속

커뮤니티 자산의 보안은 우리의 최우선 과제입니다. 우리는 피해를 입은 사용자에게 완전한 보상을 제공하기 위해 최선을 다할 것입니다.

* 보상: 피해를 입은 브리지 사용자를 위한 자세한 보상 계획은 48시간 이내에 발표될 예정입니다.

* 임시 지원: 이 기간 동안 브리지 복구 요청은 개별적으로 수동으로 처리됩니다.

* 투명성: 창립팀과 함께하는 커뮤니티 AMA(Ask Me Anything)를 24~48시간 이내에 개최할 예정입니다.


앞으로 나아갈 길: 보안 강화

이번 사건은 IoTeX 생태계를 위한 가장 강력하고 다층적인 보안 프레임워크를 구현하게 될 터닝 포인트가 될 것입니다.

* 철저한 재감사: ioTube는 독립적인 보안 감사가 완료되고 모든 취약점이 해결될 때까지 일시 중단됩니다.

* 분산형 거버넌스: 단일 장애 지점을 제거하기 위해 다중 서명 및 24시간 타임락 제어 기능을 도입합니다. 또한, 다자간 검증자 세트를 통해 브리지 검증을 분산화하는 거버넌스 제안인 IIP-55의 구현 및 배포를 가속화할 것입니다.

* 지능형 보호 장치: 위험을 사전에 차단하기 위해 새로운 거래당 및 일일 거래량 제한을 통합하고 있습니다.

* 보안 버그 바운티: 글로벌 화이트햇 커뮤니티와 협력하여 생태계를 보호하기 위해 확장된 버그 바운티 프로그램을 시작합니다.


이번 사태를 함께 극복해 나가는 동안 IoTeX 커뮤니티 여러분의 인내와 지속적인 지원에 감사드립니다.

IoTeX 팀

2024년 10월 2일 수요일

Java 8 Spring Boot 세팅

오랜만에 확인해보니 Spring Initializer(https://start.spring.io/)에서 Java 8 지원이 끊겼다. 17부터만 제공하여 17 프로젝트로부터 Java 8로 변경하는 내용을 간단히 정리.


1. spring-boot-starter-parent의 버전을 2.6.2로 변경한다.

<parent>

  • <groupId>org.springframework.boot</groupId>
  • <artifactId>spring-boot-starter-parent</artifactId>
  • <version>2.6.2</version>


2. properties 태그에서 java.version을 1.8로 변경해주고, maven-jar-plugin.version도 3.1.1로 낮춰준다.

<properties>

  • <java.version>1.8</java.version>
  • <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>

React Query

React Query에 대해 간단히 정리.

React Query는 서버와의 통신 작업을 진행할 때 반복적으로 발생하는 번거로운 작업을 안정적으로 대신해주는 역할을 한다고 이해하고 있다. 데이터의 상태와 생명 주기를 관리해주는 도구로 볼 수 있고, 사용했을 때 코드가 간결해지고 유지 보수가 쉬워지는 장점이 보인다.

React Query는 고맙게도 데이터를 가져온 뒤 그 결과를 상태로 관리해준다. useState와 useEffect를 이용해 직접 데이터를 다루지 않아도 되고, 로딩 중이나 에러가 발생하는 상황의 처리도 더 간결한 방식으로 사용할 수 있다. 

const { data, isLoading, isError } = useQuery(['api이름'], 초기로드함수);

- data: 데이터를 성공적으로 가져온 경우 여기에 데이터가 담긴다.

- isLoading: 데이터가 로딩 중일 때

- isError: 데이터 로드 실패 시


React Query는 데이터 요청을 캐싱한다. 무슨 얘기냐면 데이터 변경 없이 컴포넌트가 다시 렌더링 되어야 하는 상황에서 동일한 데이터에 대한 불필요한 추가 요청을 막아준다. 이전 데이터를 캐시에서 가져오는데, 사용자가 원하면 invalidateQueries를 통해 캐시를 무효화하고 최신 데이터를 가져오도록 할 수도 있다.


구현 패턴은 비슷해지는데

1. useQuery로 데이터를 가져오고

2. useMutation을 사용해 데이터를 수정한다. 그리고 queryClient.invalidateQueries로 데이터를 갱신한다.


// api 호출부.
import axios from 'axios';

export const fetchItems = async () => {
  const response = await axios.get('/items');
  return response.data;
};

export const updateItem = async ({ id, field1, field2 }) => {
  const response = await axios.put(`/items/${id}`, { field1, field2, });
  return response.data;
};


// 컴포넌트에서 사용
import React from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { fetchItems, updateItem } from './api';

const 컴포넌트 = () => {
  const queryClient = useQueryClient();
  const { data: items, isLoading, isError, refetch } = useQuery(['items'], fetchItems);

  // 업데이트 시 mutation.mutate() 식으로 사용.
  const mutation = useMutation(updateItem, {
    onSuccess: () => {
      queryClient.invalidateQueries(['items']); // 서버 데이터와 다시 동기화
    },
  });

  if (isLoading) return <div>Loading...</div>;
  if (isError) return <div>Error loading data</div>;

  // 렌더링 코드 반환 구문
};

2024년 2월 2일 금요일

REACT - JSX

회사에서 리액트 스터디를 하게되서..

책을 한권 구매해서 순번을 정해 한 챕터씩 맡아 내용을 정리하기로 했다.

난 챕터 3의 JSX를 정리하게 되서 짧막하게 정리해본다.


배경은?

최근의 웹은 더욱 상호 작용적으로 변하면서 점점 JavaScript 코드가 컨텐츠를 결정하게 되었다.

React가 렌더링 로직과 마크업을 같은 장소, 즉 컴포넌트에 위치시키게 된 것은 흐름을 반영한 자연스러운 결과다. 로직과 마크업을 합치기로 했다면, 이왕에 HTML 구문 그대로를 JavaScript 파일 안에 넣을 수 있으면 더 직관적일 것이다.


JSX : Javascript Syntax eXtension

한줄 정리 : JSX는 JavaScript 파일 안에 HTML과 유사한 마크업을 작성할 수 있게 해주는 JavaScript 확장 문법이다. (HTML과 살짝 다르다.)


그럼 왜 사용하는가?

JSX를 사용하면 코드가 간결해져 보기에 편하다. createElement 함수를 이용해 엘리먼트를 만드는 방법과 비교하면 차이가 극명한데, JSX 방식을 사용하면 JSX 내용을 보고 결과물을 예상하기에도 좋다.


JSX는 HTML보다 조금 더 엄격하게 동작하는데 아래의 3가지 룰이 추가된다.

1. 단일 루트 요소를 반환해야 한다.

 : 여러 개의 요소를 반환하고 싶은 경우 <div> 태그와 같은 것으로 감싸줄 필요가 있다. HTML 트리에 흔적을 남기고 싶지 않은 경우라면 <>와 </>의 빈 태그를 활용해도 된다. (이런 태그를 Fragment라 부른다.) 


2. 태그는 반드시 받아야 한다.

 : <img>, <li>와 같은 태그도 예외가 없다. 반드시 <img />, <li> ~~~ </li>와 같이 닫아주어야 한다.


3. 낙타 표기법으로 작성한다.

 : JSX도 결국 JavaScript 코드로 변환하는 과정을 거치게 된다. JSX로 작성된 속성은 JavaScript 객체의 키가 되는데 JavaScript에는 변수 이름에 제한이 있다. 예를 들면 대시를 포함하거나 class와 같은 예약어를 사용할 수 없는데, 이에 대한 방안으로 React에서는 낙타 표기법으로 속성을 표기하기로 하였다.

stroke-width는 strokeWidth, class는 className 식으로 작성해야 한다.


다행스러운 것은 HTML을 JSX로 변환해주는 온라인 컨버터가 존재한다는 것인데 주소는 다음과 같다.

https://transform.tools/html-to-jsx


누군가는 마크업에 간단한 로직을 포함하거나 동적으로 변하는 프로퍼티를 넣고 싶을 수 있다. JSX에선 중괄호를 이용해 이러한 작업을 할 수 있게 지원한다. (값의 대입 뿐만 아니라 함수의 호출도 지원한다.)

+ CSS나 오브젝트를 전달하고 싶은 경우 '{{ ... }}'의 이중 중괄호로 표현해야 동작한다.



2023년 3월 11일 토요일

Protobuf-net 서버-클라이언트 데이터 송수신 예제

성능 신경쓰지 않고 단순히 데이터 주고 받는데 집중하면 동기 방식의 TCP/IP에 protobuf-net 조합이 가장 간편한 것 같다. 예제는 ChatGPT에서 찾은 것으로 단순하게 메시지 하나를 전달한다. 이 정도 예제만 바로 찾을 수 있어도 접근하기가 얼마나 쉬워지는지. 아쉬운 부분은 회사에서는 ChatGPT가 막혀있다.


아래는 서버 코드. 클라이언트 네트워크 스트림을 대상으로 Serialize를 호출하면 데이터가 클라이언트 쪽으로 전송된다. Protobuf-net을 사용하는 경우 *.proto 파일을 별도로 작성하고, 관리하지 않아도 되기 때문에 사용하기 간편하다.


using System;
using System.Net;
using System.Net.Sockets;
using ProtoBuf;

public class Program
{
    private const int Port = 12345;
    private const int BufferSize = 1024;

    [ProtoContract]
    public class Message
    {
        [ProtoMember(1)]
        public int Id { get; set; }
        [ProtoMember(2)]
        public string Text { get; set; }
    }

    static void Main(string[] args)
    {
        var listener = new TcpListener(IPAddress.Any, Port);
        listener.Start();
        Console.WriteLine($"Listening on port {Port}");

        while (true)
        {
            using (var client = listener.AcceptTcpClient())
            {
                var stream = client.GetStream();
                var buffer = new byte[BufferSize];
                var bytesRead = stream.Read(buffer, 0, BufferSize);
                var message = Serializer.Deserialize<Message>(new ArraySegment<byte>(buffer, 0, bytesRead));

                Console.WriteLine($"Received message from client: {message.Text}");

                var response = new Message { Id = 1, Text = "Hello from server!" };
                Serializer.Serialize(stream, response);
                Console.WriteLine($"Sent response to client: {response.Text}");
            }
        }
    }
}

다음은 클라이언트. ProtoContract 클래스는 서로 일치해야 한다.

using System;
using System.Net.Sockets;
using ProtoBuf;

public class Program
{
    private const string Host = "localhost";
    private const int Port = 12345;
    private const int BufferSize = 1024;

    [ProtoContract]
    public class Message
    {
        [ProtoMember(1)]
        public int Id { get; set; }
        [ProtoMember(2)]
        public string Text { get; set; }
    }

    static void Main(string[] args)
    {
        using (var client = new TcpClient(Host, Port))
        {
            var stream = client.GetStream();

            var message = new Message { Id = 1, Text = "Hello from client!" };
            Serializer.Serialize(stream, message);
            Console.WriteLine($"Sent message to server: {message.Text}");

            var buffer = new byte[BufferSize];
            var bytesRead = stream.Read(buffer, 0, BufferSize);
            var response = Serializer.Deserialize<Message>(new ArraySegment<byte>(buffer, 0, bytesRead));

            Console.WriteLine($"Received response from server: {response.Text}");
        }
    }
}


2021년 9월 26일 일요일

Visual Studio 2019+ D(서브) 드라이브 설치

Visual Studio 2019 버전을 메인 드라이브인 C 드라이브가 아닌 다른 드라이브에 설치하는 방법에 대해 정리해 본다.


SSD 128 GB 정도의 작은 저장 장치를 메인 드라이브로 사용하는 경우 용량 부족으로 Visual Studio 2019 설치가 어려운 경우가 있는데 윈도우 시스템의 Junction(리눅스 시스템의 Symbolic Link) 기능을 이용해 일부 설치 용량을 서브 드라이브에 할당시킬 수 있다.


어느 정도의 공간 절약이 되냐면 Professional 버전 기준 .NET 데스크탑 개발 환경과 C++ 데스크탑 개발 환경을 설치할 때 12GB 가량 공간이 필요한데 Junction을 이용하면 4GB 정도 공간 절약이 가능하다.


원문 링크 : https://eventhorizon.tistory.com/110


방법>

1. 관리자 권한으로 cmd.exe를 실행한다.

2. 실제 설치가 이루어질 디렉토리를 D 드라이브에 생성한다.

3. 링크를 배치할 디렉토리를 C 드라이브에 생성한다.

4. mklink 명령어를 이용해 링크를 생성한다.

5. Visual Studio 설치를 진행한다.


ex) Professional 기본 설치 경로를 이용하는 경우 예시. 2단계 메인 드라이브에 디렉터리를 생성할 때 'Professional' 디렉토리는 제외해서 생성해야 한다.

1. mkdir -p "D:\Program Files (x86)\Microsoft Visual Studio\2019\Professional"

2. mkdir -p "C:\Program Files (x86)\Microsoft Visual Studio\2019"

3. mklink /j Professional "D:\Program Files (x86)\Microsoft Visual Studio\2019\Professional"




2021년 1월 26일 화요일

[Protocol Buffers] optimize_for 옵션

프로토콜 버퍼 컴파일러(protoc) 옵션으로 사용 목적에 따라 SPEED, CODE_SIZE, 또는 LITE_RUNTIME 값을 지정할 수 있다.


기본 값은 SPEED이다.


• SPEED (기본) : 기본 값인 만큼 프로토콜 버퍼의 모든 기능(Descriptor, Reflection)이 포함되며 최적화된 코드를 생성해 준다.


• CODE_SIZE : SPEED 옵션 대비 작은 코드 사이즈를 갖는 결과 파일을 만들어 주지만 SPEED 보다 동작 속도가 느려진다. 매우 많은 수의 .proto 파일을 포함하고(메시지 개수에 코드 사이즈가 비례하므로) 있거나 속도가 중요하지 않은 앱에서 유용한 옵션이다.


• LITE_RUNTIME : 'lite' 런타임 라이브러리에만 의존하는 클래스를 생성한다. (libprotobuf 대신 libprotobuf-lite) 전체 라이브러리보다 10배 작은 라이브러리로 동작이 가능하기 때문에 휴대폰과 같이 제한된 플랫폼에서 실행되는 경우 유용하다. 생성된 클래스는 Message 대신 MessageLite 인터페이스를 구현하며 이 옵션으로 컴파일 하는 경우 Descriptor, Reflection 등의 기능은 제외된다.


* 동일한 proto 파일을 여러 번 로드할 때 Descriptor로 부터 에러가 발생하는 경우가 있는데 이러한 경우엔 Descriptor 기능이 제외된 LITE_RUNTIME 옵션을 사용하는 것을 고려해볼 수 있다.

https://github.com/protocolbuffers/protobuf/issues/4126


일반적인 타겟 환경을 가진 프로그램이라면 기본 값을 그대로 쓰면 될 것 같고, 바이너리 사이즈가 중요한 프로그램이라면 CODE_SIZE, LITE_RUNTIME 등의 옵션을 고려해 볼만 한 것 같다. 사실 protoc에 의해 생성된 클래스를 그대로 사용하는 것이 일반적이라 Descriptor 같은 기능은 고급 사용자가 아니면 잘 쓰이지 않는다.


2021년 1월 11일 월요일

[Protocol Buffers] Base 128 Varints

프로토콜 버퍼의 인코딩 방식을 이해하려면 우선 Varint를 이해해야 한다. Varint는 하나 이상의 바이트를 사용해 정수를 직렬화 하는 방법으로 숫자가 작을수록 적은 바이트 수를 차지하게 된다. (아주 큰 숫자가 아니면 데이터 크기가 절약된다.)


Varint에서 마지막 바이트를 제외한 각 바이트의 최상위 비트(msb)는 1로 하기로 약속되어 있다. 이는 앞으로 올 바이트가 더 있음을 나타내기 위함이다. 

반대로 msb가 0이라면 Varint의 끝을 의미하게 된다. 따라서 1바이트를 차지하는 Varint로 표현할 수 있는 가장 큰 숫자는 127(0111 1111)이 된다. 또 하나의 룰로 Varint는 최하위 그룹을 먼저 위치시키는 방식을 택한다. (least significant group first).


표현 예시.

웹 페이지 설명에 나온 것처럼 숫자 1은 1바이트로 표현이 가능하므로 msb는 0이 되고 표현은 '0000 0001'이 된다.


그렇다면 숫자 300은 어떻게 표현될까? 앞서 언급된 것처럼 숫자 300은 하나의 바이트로 표현이 불가능 하므로 첫 바이트의 msb는 1로 셋 되어야 한다.

결과적으로 숫자 300은 아래와 같이 표현된다.


 1010 1100 0000 0010


여기서 msb를 떼면 010 1100 000 0010이 되는데, 앞서 적혀있는 대로 최하위 그룹이 먼저 위치하게 되므로 우리가 이해하는 숫자로 인식하기 위해선 7비트 단위 그룹의 위치를 서로 바꾸어 주어야 한다.


따라서 010 1100 000 0010은, 000 0010 010 1100 되어야 하고, 의미 없는 비트를 버린 뒤 두 그룹을 연결하면

→ 100101100 = 300 (256 + 32 + 8 + 4)이 된다.


https://developers.google.com/protocol-buffers/docs/encoding#varints


2020년 8월 12일 수요일

matplotlib 한글 깨지지 않게..

rc를 임포트한 다음에 한글이 지원되는 폰트의 이름을 지정해 주면 된다.
한글 윈도우 사용자라면 malgun.ttf는 있을거란 가정이 깔린다.

import matplotlib.pyplot as plt
from matplotlib import font_manager, rc

font_name = font_manager.FontProperties(fname="C:/Windows/Fonts/malgun.ttf").get_name()
rc('font', family=font_name)

x = list(range(1,11))
y = [a * 2 for a in range(0, 10)]

l = plt.plot(x, y, 'ro', label='빨강 닷')
plt.setp(l, markersize=4)
#plt.setp(l, markerfacecolor='C0')
plt.xlabel('X-축')
plt.ylabel('Y-축')
plt.title('차트 제목')
plt.legend(loc='best')
# plt.savefig('fig.pdf') # to pdf
plt.show()


https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.rc.html


log4jdbc-remix 사용법

https://code.google.com/archive/p/log4jdbc-remix/


프로젝트는 2013년 11월 중단된 상태고, 앞으론 log4jdbc-log4j2가 log4jdbc-remix(이건 2012년 4월에 시작된 프로젝트)를 대체한다고 한다. 


log4jdbc-remix의 주 기능은 쿼리문과 쿼리 결과를 보기 좋게 로그로 기록해 주는 것이다. 데이터베이스에 요청한 쿼리문이 의도와 맞게 작성되었는지 확인하는 용도로 활용할 때 좋다. (콘솔에 찍어보는 용도)


이건 Java 6 이상 버전에서 사용 가능하고, 제공되는 Proxy에서 기존에 정의해둔 DataSource를 참조하는 설정 방식을 따르기 때문에 사용하기 편리하다.


안정화 릴리즈 버전은 0.2.7이고, maven 기준 다음과 같이 끌어다 쓸 수 있다.

<dependency>

    <groupId>org.lazyluke</groupId>

    <artifactId>log4jdbc-remix</artifactId>

    <version>0.2.7</version>

</dependency>


log4j.xml엔 jdbc.sqlonly, jdbc.resultset 이름으로 로거 설정을 등록해주면 된다.

jdbc.sqlonly는 쿼리문을 기록하는 용도이고, jdbc.resultset은 쿼리 결과를 기록하는 용도이다.


프록시 빈 등록은 인터넷에 많이 돌아다니는 걸 그대로 쓰면 된다.

<bean id="dataSource" class="net.sf.log4jdbc.Log4jdbcProxyDataSource">

        <constructor-arg ref="dataSourceSpied" />

        <property name="logFormatter">

            <bean class="net.sf.log4jdbc.tools.Log4JdbcCustomFormatter">

                <property name="loggingType" value="MULTI_LINE" />

                <property name="sqlPrefix" value="SQL         :  "/>

            </bean>

        </property>

 </bean>




2020년 8월 10일 월요일

[펌] 비트겐슈타인이 남긴 명언

 

1889. 4. 26 빈~ 1951. 4. 29 잉글랜드 케임브리지셔 케임브리지. 오스트리아 태생 영국의 철학자. 1925~50년 영국 철학계에서 가장 영향력 있는 철학자 중 한 사람이었으며, 논리학 이론과 언어철학에 관한 독창적이며 중요한 철학적 사유체계를 제시했다.


1. 쓸모없는 문제에 관여하지 마라.

2. 말할 수 없는 것에 관해서는 침묵해야 한다.

3. 언어는 만물의 척도다.

4. 생각도 일종의 언어이다.

5. 말에는 음악이 깃들어 있다.

6. 내 언어의 한계는 내 세계의 한계를 의미한다.

7. 우리는 남에게 자신의 속마음을 감추고 싶어한다. 인간의 마음이란 아름답게만 간직되는 것은 아니기 때문이다.

8. 두려움이 아니라, 두려움의 극복이 칭찬받을만한 것이고, 인생을 보람차게 만든다.

9. 마음속 용기야말로 처음에는 겨자씨처럼 작아도 점점 성장해서 거목이 되는 것이다.

10. 어떤 돌이 전혀 움직이지 않고, 도저히 손을 쓸 방도가 없다면 먼저 주변의 돌부터 움직여라.

11. 문제를 해결하는 힘은 새로운 정보를 얻는데서 오는 것이 아니라, 이미 오래전부터 알고 있던 것을 체계적으로 정리하는데서 온다.

12. 철학자란 건강한 인식을 얻기 위해서 자기안에 박혀있는 다양한 사고의 오류를 고쳐야 하는 사람이다.

13. 반대되는 결론도 항상 함께 생각하라.

14. 오늘날 우리의 교육은 고뇌하고 인내하는 능력을 누르는 방향으로 흐르고 있다.

15. 생활이 자꾸만 변화하는 것은 인생에 있어서 가장 기본적인 일이다. 물론, 그것은 습관에 있어서도 마찬가지다.

16. 나는 왜 우리가 여기에 있는지 그 이유를 알지 못한다. 그러나, 나는 우리가 단지 즐기기 위해서 여기에 있는 것은 아니라는 그 사실만큼은 확신한다.

17. 자신과 아무런 상관이 없는 문제에 자신을 끌어들이지 않는 것은 철학자의 주요한 기술중 하나이다.

18. 자아성찰은 내 삶의 새로운 한 부분이어야 한다.

19. 의심은 믿음 이후에 온다.

20. 확실하다는 말로써 우리는 완전한 확신, 의심의 부재를 나타내며, 또한 그것으로 다른 사람들을 설득시키고자 한다. 하지만, 우리의 그 믿음은 주관적 확실성임을 알아야한다.

21. 인간의 몸은 인간의 정신을 표현하는 가장 훌륭한 그림이다.


22. 너무 많이 아는 사람이 거짓말을 하지 않기란 어렵다.

23. 인생이 견딜 수 없게 되었을때, 우리는 상황이 변화할 것을 기대한다. 그러나, 가장 중요하고 가장 효과적인 변화, 즉 자기자신의 태도를 바꿔야한다는 인식에는 거의 생각이 미치지 못한다.

24. 사물의 가장 중요한 측면은 그것이 너무나도 단순하고 친숙한 것이기 때문에, 우리의 눈길을 끌지 못한다. 따라서, 가장 기본적으로 탐구해야 하는 것은 그냥 스쳐가는 것 중에 있다.

25. 나는 구두점을 많이 써서 읽는 속도를 늦춰보려고 하는 편이다. 내가 쓴 글이 천천히 읽혀지기를 희망하기 때문이다. 나 자신이 읽는 것처럼 ...

26. 철학적 탐구는 인간생활에 보탬이 되는 쪽으로 국한되어야 한다.


2020년 7월 29일 수요일

포카 요케 (poka-yoke)

품질 관리의 측면에서 실수를 방지하도록 행동을 제한하거나 정확한 동작을 수행하게끔 하도록 강제하는 여러 가지 제한점을 만들어 실패를 방지하는 방법을 말하는 용어. 토요타의 시게오 신고에 의해 처음으로 고안됐으며, 실수를 피하다라는 뜻의 일본어에서 왔다.
... 인지 심리학 및 인간의 컴퓨터 상호작용 분야에서도 이를 이용하면 여러 가지 실수를 방지하는 디자인 원리의 하나로 받아들여져서 이용하고 있다.
[위키피디어]

생활에서의 포카 요케 예
- 자동차 기어가 "P"에 있지 않으면 시동이 걸리지 않도록 함.
- USB 장치를 꼽을 때 뒤집어서 꼽으면 작동하지 않음.
- 문이 닫히기 전에 전자 레인지가 작동하지 않음.

이 개념을 제안한 사람은 토요타 생산 시스템의 개척자 중 한 명인 시게오 신고로 '제로 결함'이라는 아이디어를 제안한 품질 전문가였다고 한다.

실수가 발생하지 않도록 끊임없이 노력한다는 철학이 포함된다. 최종 제품의 품질이 올라가려면 연결된 모든 프로세스에서 일정 수준 이상의 품질이 제공되어야 한다. 따라서 결함이 후속 프로세스로 이어지는 것을 방지해야 한다. (완제품 상태에서 결함이 발견되는 것이 가장 나쁜 케이스)

실수 방지 목표를 달성하는데 도움이 되는 특징은 다음과 같다.
- 단순함 : 솔루션이 복잡하지 않고 오류 발생 문제를 명확히 해결해줄 수 있어야 한다.
- 자동화 : 사람의 추가 개입이 없으면 좋다.
- 즉각적인 피드백 : 조건이 사양을 벗어난 경우 즉시 피드백을 해주면 좋다.

-
아예 실수를 할 수 없게끔 하면 사용자가 제품이나 프로세스에 대해 생각할 필요가 없을 것이다.
또는 프로세스를 벗어나는 경우 피드백을 줘서 프로세스 안에서만 행동이 있게끔 유도할 수 있다. 
(정해진 순서 또는 경우의 수 내에서만 행동을 제한하는 것.)

2020년 7월 21일 화요일

입출력 다중화 (파일 디스크립터, select)

입출력 다중화는 단일 프로세스에서 여러 개의 파일을 제어할 수 있도록 해준다. 입출력 다중화는 여러 개의 파일을 다루기 위해서, 파일 기술자(FD, File Descriptor)를 배열(그룹)로 관리한다. 데이터 변경을 감시할 파일 기술자를 배열에 포함시키고, 배열에 포함된 파일 기술자에 변경이 발생하면 파일 기술자에 대응하는 배열에 표시하는 방식이다. 사용자는 파일 기술자 배열의 값을 검사(FD_ISSET)하는 방식으로 여러 개의 파일을 처리할 수 있게 된다. (일반적으로 파일 기술자 테이블의 크기는 1024라 한다. 이는 프로세스가 열 수 있는 파일의 최대 개수, 파일 기술자 테이블의 크기로부터 영향을 받는다.)

• fd_set 구조체
typedef struct
{
    int fds_bits[32];
} fd_set;

변경의 확인은 이벤트 기반이 아니므로, 배열에 있는 모든 값을 전부 검사해야 하는데 1000개의 클라이언트가 연결되어 있다고 가정할 경우 1000개의 필드를 모두 검사해야 한다. 이 방식은 병렬 처리가 아니기 때문에 주의할 필요가 있는데 서버가 하나의 클라이언트를 처리하는 동안 다른 클라이언트는 대기해야 한다. 따라서 상대적으로 데이터 처리 과정이 짧은 서비스에 적합한 방식이라 볼 수 있다.

변경에 대한 확인은 select() 함수를 통하는데 select 함수는 데이터가 변경된 파일의 개수를 반환한다. 데이터가 변경된 파일의 목록을 반환하는 것이 아니기 때문에 사전에 연결된 파일의 번호를 가지고 있지 않으면 배열의 개수만큼 루프를 돌아야 할 수도 있다. (참고로 파일 디스크립터는 0부터 채번되고, 프로세스에서 사용하지 않는 가장 작은 값을 할당한다. 일반적으로 0번은 stdin, 1번은 stdout, 2번은 stderr.)

• FD_ZERO : fd_set 구조체를 초기화 한다. (모든 비트 값을 0으로 세팅)
fd_set rfds;
FD_ZERO(&rfds); // memset(&rfds, 0, sizeof(fd_set));

• FD_SET : 특정 파일 디스크립터를 fd_set 변수에 추가할 때 사용한다.
int fd_socket = socket(AF_INET, SOCK_STREAM, 0);
fd_set rfds;

FD_ZERO(&rfds);
FD_SET(fd_socket, &rfds);

• FD_ISSET : fd_set 변수의 특정 파일 디스크립터를 가리키는 비트가 1인지 확인한다.

• select() : fd_set 변수에서 1로 세팅된 파일 디스크립터에서 이벤트가 발생하면 이를 감지한다.
fd_set rfds, _fds;

FD_ZERO(&rfds);

FD_SET(1, &rfds);
FD_SET(3, &rfds);

max_fd = 3;

while (1) {
    _fds = rfds; // select() 후 fd_set 변수의 내용이 변경되므로 매번 복사본을 전달.
  
    select(max_fd + 1, &_fds, NULL, NULL, NULL); // 1, 3 값을 갖는(FD_SET으로 설정된) 파일 디스크립터의 이벤트를 감시한다.

    // ...
}

* 위 내용에서 '파일'은 '소켓'을 포함한다. (과거 시스템에서 소켓, 장치를 모두 파일의 개념으로 관리했었던 것에서 유래.)

참고 사이트: https://www.joinc.co.kr/w/Site/system_programing/File/select

2020년 6월 17일 수요일

ZeroMQ 기본 개념 - 2

• ZeroMQ의 Socket API

ZeroMQ는 새로운 개념을 욱여넣지 않고, 친숙한 소켓 기반 API 이름을 따서 기능을 제공하고 있다. 사용자는 친숙한 이름의 API를 따르되 '분산 소프트웨어를 디자인하고 구현하고 있다.'라는 생각을 갖고 ZeroMQ를 대할 필요가 있다.

ZeroMQ에선 accept() API가 없으며 네트워크 연결은 백그라운드에서 진행되고, 일시적으로 연결이 끊어지더라도 ZeroMQ가 자동으로 다시 연결해준다. 하나 더 기억할 것은 ZeroMQ의 zme_send()의 실제 동작이다. 이 메서드는 메시지를 연결된 소켓으로 바로 전달하는 일을 하지 않고 실제론 메시지를 큐에 넣는 일을 한다. (사용자는 인지하지 않아도 되는) I/O 담당 쓰레드가 비동기적으로 큐에 쌓인 메시지를 실제 연결된 소켓으로 전달하는 일을 할 것이다.

대부분의 유스케이스는 TCP 방식으로 커버가 된다. ZeroMQ에서는 TCP를 'disconnected TCP' 전송 방식이라고도 표현을 하는데 이는 엔드 포인트가 연결이 되지 않아도 작동하기 때문에 이름지어진 표현이다. 클라이언트는 엔드포인트에 바인드된 서버가 없더라도 별다른 오류 없이 connect 작업을 할 수 있는 것은 ZeroMQ가 갖는 특징이다.

inproc이라는 방식의 연결도 제공하는데 이는 tcp, ipc 전송 방식보다 빠르게 작동한다. 다만 클라이언트 연결전에 반드시 서버가 실행되어 bind()를 하고 있어야 하는 제약 조건이 걸린다. (이후 버전에서 해결할 예정이라고..)

ZeroMQ에서 context를 만들면 하나의 I/O 쓰레드가 백그라운드에서 작업을 시작하게 된다. 일반적인 요구조건에선 하나의 I/O 쓰레드만 가지고 모든 작업을 처리할 수 있다고 한다. I/O 쓰레드의 개수를 늘리고 싶은 경우엔 zmq_ctx_set() 메서드의 ZMQ_IO_THREADS 옵션을 조정하면 된다.


• ZeroMQ가 내부적으로 해주는 일

ZeroMQ가 하는 일을 요약하면, 

1) 노드에 빠르고 효율적으로 메시지를 전송해주고,
2) 자동으로 다시 연결을 해주며
3) 메시지 큐잉을 해준다.
4) 메시지 큐는 프로세스가 메모리 부족 현상을 겪지 않게끔 적절한 크기에서 제한되며
5) 소켓 오류를 처리해준다.
6) 마지막으로 노드간 통신에서 데드락 같은 현상을 겪지 않게 보장해준다.


• ZeroMQ가 제공하는 소켓 페어

목록 중에선 거의 상위 3개 목록 중 하나를 쓰게될 것 같다. 아래의 패턴 외 다른 조합을 쓰는 경우엔  ZeroMQ가 올바른 동작을 보장해주지 않는다.

- PUB - SUB : 디커플링된 형태의 데이터 분산 패턴.
- REQ - REP
- PUSH - PULL : fan-out / fan-in의 병렬 작업 분산 및 수집 패턴에 적합.
- REQ - ROUTER
- DEALER - REP
- DEALER - ROUTER
- DEALER - DEALER
- ROUTER - ROUTER
- PAIR - PAIR

PUB - SUB과 REP - REQ 같은걸 조합해서 쓸 수도 있는데 예를 들어 모든 subscriber가 연결된 이후에 publish할 수 있도록 애플리케이션을 디자인 해야 하는 경우에 유용하다. 간단한 시나리오는
1) Publisher가 PUB 소켓을 열고 "Hello" 메시지를 발행한다.
2) Subscriber는 SUB 소켓을 열고 "Hello" 메시지에 응답한다. (REQ/REP 페어를 통해)
3) Publisher는 Subscriber의 응답을 확인한 뒤 실제 데이터 전송을 시작한다.


2020년 6월 16일 화요일

ZeroMQ 기본 개념

• ZeroMQ 등장의 배경

요즘 많은 애플리케이션은 LAN 또는 인터넷과 같은 일종의 네트워크를 통해 확장되는 구조를 갖는다. 이는 응용 프로그램 개발자로 하여금 일종의 메시징을 수행하게끔 한다.  일부 개발자는 메세지-큐 제품을 사용하지만 아직도 대부분은 TCP를 사용해 메시징을 수행한다. 이 프로토콜은 사용하기 어렵진 않지만 여러 케이스에 대응해 안정적인 방식으로 동작시키긴 어렵다. (메시지의 전달을 보장한다던가, 서로 다른 아키텍처 간의 메시지 전달이라던가 하는.) 게다가 재사용 가능한 메시징 시스템을 구축하는 것은 매우 어려운 일이다. 만들어진 제품을 사용하더라도 상대적으로 복잡하고 비싸며 제대로 쓰기까지 몇 주에서 몇 달이 걸릴 수도 있다.

최근의 대부분의 메시징 프로젝트는 중심에 broker를 두는 방식으로 설계되어 있다. Broker는 어드레싱, 라우팅, 큐잉등의 역할을 수행하게 되는데 이러한 구조를 따르면 네트워크의 복잡성을 꽤나 줄여주기 때문에 혜택이 크다. 하지만 broker 자체가 병목이 되거나 broker가 제대로 동작하지 않을 때 전체 네트워크에 위협이 되는 등의 문제가 생긴다. 이러한 구조를 따르는 프로젝트에선 두번째 세번째 네번째 broker를 추가로 띄우는 방식으로 이를 보완하지만 이는 다시 구성을 복잡하게 만들어 버린다.

ZeroMQ는 위 요구사항과 문제점을 해결하기 위해 만들어졌다. ZeroMQ는 쉽고 가벼운, 모든 프로그래밍 언어와 모든 OS에서 동작하는 메시징 기술이다. ZeroMQ는 다양한 기술적 이슈와 장애를 대응하면서 마치 작은 서버처럼 동작한다.


• ZeroMQ가 제공하는 패턴

ZeroMQ에선 어느 쪽이 연결되고 어느 쪽이 바인딩되는지는 중요하지 않다. 그리고 ZeroMQ는 전달하고 받는 데이터의 길이만을 알고 있다. 보내고 받는 데이터가 종단에서 어떻게 해석되는가에 대해선 관여하지 않는다. 그러한 부분의 책임은 사용자에게 있다. 복잡한 데이터 형식을 주고 받는다면 프로토콜 버퍼 같은 라이브러리를 사용할 수도 있을 것이고, 단순하게 문자열 데이터를 주고받을 수 있을 것이다. (C로 작업하는 경우 수신한 문자열에 null 바이트 처리를 해주어야 한다.)

ZeroMQ는 요청과 응답이 있는 REP-REQ 패턴, 발행-구독의 PUB-SUB 패턴 등을 제공한다. PUB-SUB 패턴의 경우 비동기로 동작하는데 SUB 소켓은 PUB 소켓보다 먼저 실행되더라도 첫 n개의 메시지를 놓칠 가능성이 있다. SUB 소켓과 PUB 소켓이 서로 연결되는데 걸리는 시간이 짧지 않기 때문이다. 이런 경우에 대해 SUB 소켓의 연결을 확인한 뒤 메시지를 발행할 수 있는 기법이 마련되어 있다. (단순히 sleep 한 뒤 동작하게 하는 방법도 있지만 권장되진 않는다.)

마지막으로 PUSH-PULL 방식을 제공한다. 단방향으로 흐르는 데이터 스트림을 다루는데 효과적이다. 단지 초기 데이터 또는 task를 내보내는 연결점에 데이터를 끌어오는 worker를 붙이거나 떼는 방식으로 네트워크를 확장하거나 축소할 수 있다. 하나의 연결점에 연결된 다수의 소켓은 고르게 처리된다. 이걸 fair-queuing이라고 부른다. fair-queuing 이지만 문제점이 없진 않은데 여러 개의 노드를 같이 동작시켜도 초기 연결 과정에서 가장 먼저 연결된 worker에 일시적으로 작업이 쏠리는 경우가 발생할 수 있다.

ZeroMQ 애플리케이션은 context를 만드는 것으로 작업이 시작된다. 하나의 프로세스는 반드시 하나의 context만을 만들어 사용해야 한다. context는 프로세스에서 사용하는 모든 소켓을 담는 컨테이너로 동작한다. 여러 개의 context를 만들어 사용할 수도 있지만 ZeroMQ는 이를 서로 다른 인스턴스(프로세스)로 인식하게 되므로 일반적인 사용 방식은 아니다. (리눅스의 fork()를 쓴다면 context를 생성하는 zmq_ctx_new()는 fork() 이후에 호출하는 것이 바람직하다.)

ZeroMQ에서 소켓은 여러 연결을 자동으로 관리하는 작은 백그라운드 통신 엔진의 출입구로 작동한다. 사용자는 내부의 연결을 확인하거나 제어할 수 없게끔 되어 있는데 이는 의도적인 설계이며 오히려 이러함 감춤이 ZeroMQ 확장성의 키 요소가 된다.

2020년 5월 28일 목요일

[Java] 러시아 시간 출력 오류

특정 국가, 환경에서 Java에서 가져온 시간과 윈도우에 적용된 시간 값이 다른 경우가 발생할 수 있다.

이는 대부분 오래된 JDK나 JRE를 사용해서 생긴 문제라고 보면 된다.

우리에겐 낯설은 써머타임(Daylight saving time, DST)에 대한 것인데,
써머타임 적용, 폐지에 대한 신규 정보가 JVM 시스템에 반영되어있지 않아서 발생하는 문제라고 한다.

해결은 JDK나 JRE를 최신 버전으로 올려주면 되는데 문제는 쉽게 업데이트를 결정할 수 없는 경우다. (안정성 또는 호환성 고려, 폐쇄망 환경 등으로 인한 업데이트의 어려움)

이 경우엔 아래의 두가지 대안이 있다.


1. 실행 옵션에 -XX:+UseGetTimeOfDay를 적용.

시간 관련 동작 수행 시 매번 gettimeofday() 시스템 콜을 이용해 시스템 시간을 가져온다. 성능 저하가 있다.



2. 타임존 정보를 갱신해주는 프로그램을 이용해 패치 (Time Zone Updater)

오라클에서 제공하고 있다. 

tzupdater.jar를 내려받은 후 아래 명령어를 실행해주면 시간이 정상적으로 보정된다.
java -jar tzupdater.jar -v -f -l http://data.iana.org/time-zones/releases/tzdata2017c.tar.gz


2020년 5월 24일 일요일

[Java] 빈도수를 간단하게, map의 merge()

Map을 이용해 빈도수를 계산할 때 어떤 언어에서는 간단히 myMap[key]++과 같이 해주면 되는데 Java에서는 그렇게 동작하지 않는다.

그래서 대개 AtomicInteger와 같이 레퍼런스 타입으로 처리하거나 ContainsKey 또는 get()의 null값 체크 후 빈도수를 기록하는 지저분한 코드가 작성되곤 한다.

Java 8에선 이러한 문제(?)가 다소 해결되었는데 아래와 같이 Map의 merge 함수를 이용하면 한 줄의 코드로 단순 카운팅 기능을 구현할 수 있다. 한 줄의 코드이지만 Atomic operation은 아님에 주의할 필요는 있다.

myMap.merge(key, 1, Integer::sum);

함수 이름이 좀 헷갈리게 지어진 것은 아쉽지만 한번 기억해두면 쓰기 편하다.

public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)

• key: 키

• value: 값. 초기 값으로 쓰이거나 이전 갑과 병합됨.

• remappingFunction: 새로운 값을 지정하는데 쓰이는 함수, 이전 값이 있는 경우 호출됨. BiFunction은 함수 인수가 2개이면서 결과 값이 있는 함수를 뜻한다.

2020년 5월 19일 화요일

[NSIS] StrCpy 사용법

IndexOf와 RIndexOf랑 StrCpy를 쓰면 결과물이 지저분해질 순 있어도 NSIS 내부에서 원하는 문자열 조작을 어떤식으로든 할 수 있다.

문법
StrCpy 저장변수 문자열 [최대길이] [시작_오프셋]
: 최대 길이와 시작 오프셋이 생략되거나 음수 값이 올 수 있는게 특징이다.

예시
StrCpy $0 "a string"
→ "a string" - 기본 복사

StrCpy $0 "a string" 3
→ "a s" - 최대 길이를 3으로 지정했으므로 처음의 3글자인 "a s"만 선택됨.

StrCpy $0 "a string" -1
→ "a strin" - 최대 길이를 음수로 하면 문자열의 뒤에서 부터 잘라내는 동작을 함.

StrCpy $0 "a string" "" 2
→ "string" - 복사 시작 위치를 2로 지정했으므로 "a "가 스킵됨.

StrCpy $0 "a string" "" -3
→ "ing" - 복사 시작 위치를 음수로 하면 뒤에서 지정된 길이만큼 문자열을 가져옴.

StrCpy $0 "a string" 3 -4
→ "rin" - 뒤에서 부터 4글자인 "ring"을 가져온 다음 앞의 3글자인 "rin"만 선택됨.

https://nsis.sourceforge.io/Reference/StrCpy

2020년 5월 18일 월요일

[C#] Task, Task

Task와 Task의 Generic인 Task<TResult>는 쓰레드풀에서 쓰레드를 가져와 비동기 작업을 실행해 준다. Generic은 값을 반환하는 작업인 경우에 쓰인다. Task를 실행한 다음엔 IsCanceled, IsCompleted, IsFaulted 등 속성을 이용해 Task의 상태를 확인할 수 있다.

Task를 실행하는 방법엔 여러가지가 있다. Task 인스턴스를 만든 다음 Start() 메서드를 호출하거나 TaskFactory.StartNew(Action<Object>, Object) 메서드를 호출해 한번에 처리하는 방식이 있다. 더 간단하게는 Task.Run()을 이용할 수도 있다. 다만 이 메서드는 .Net 4.5 이상에서 제공된다.

Task는 비동기적으로 실행되기 때문에 Task의 모든 작업이 끝나기 전에 애플리케이션이 종료되는 경우가 생길 수 있다. 이러한 경우엔 Wait() 메서드를 이용해 Task의 작업이 끝날때까지 호출 쓰레드가 기다리게 할 수 있다. 무한정 기다리기 싫은 경우엔 Wait() 메서드에 timeout을 지정해주면 된다.

Task는 대기하지 않고 백그라운드에서 돌리고 싶은 작업이 있을 때 유용하다. 실행 문맥과 독립적인 작업을 Task로 처리하면 사용자가 대기하지 않아도 되기 때문에 사용성 측면에서도 이점이 있다. 여기에 코드까지 깔끔해지는 것은 덤이다.


// Task의 생성과 실행을 따로.
var t1 = new Task(() => {
    // work
});

t1.Start();
// other work
t1.Wait();

// Task의 실행과 생성을 한 번에.
var t2 = Task.Factory.StartNew(() => {
    // work
});
// other work
t2.Wait();


2020년 5월 14일 목요일

GTA 5 (Grand Theft Auto V) PC 무료



















무려 GTA 5를 21일까지 에픽게임즈에서 무료로 다운로드 받을 수 있다.

출시된지 오래된 게임이라 높은 사양을 요구하지 않아서 대부분의 PC에서 부담없이 즐길 수 있으리라 생각된다.

최소 사양
권장 사양
OS
Windows 10 64비트, Windows 8.1 64비트, Windows 8 64비트, Windows 7 64비트 서비스 팩 1, Windows Vista 64비트 서비스 팩 2* (*Vista OS에서는 NVIDIA 비디오 카드를 권장합니다)
OS
Windows 10 64비트, Windows 8.1 64비트, Windows 8 64비트, Windows 7 64비트 서비스 팩 1
프로세서
인텔 코어 2 쿼드 CPU Q6600 @ 2.40GHz(4 CPU) / AMD Phenom 9850 쿼드코어 프로세서(4 CPU) @ 2.5GHz
프로세서
인텔 코어 i5 3470 @ 3.2GHZ(4 CPU) / AMD X8 FX-8350 @ 4GHZ(8 CPU)
메모리
4GB RAM
메모리
8GB RAM
비디오 카드
NVIDIA 9800 GT 1GB / AMD HD 4870 1GB (DX 10, 10.1, 11)
비디오 카드
NVIDIA GTX 660 2GB / AMD HD7870 2GB
사운드 카드
DirectX 10 100% 호환
사운드 카드
DirectX 10 100% 호환
HDD 공간
90 GB available space
HDD 공간
90 GB available space

누구에겐 부담을 가질만한 잔인한 샌드박스 게임이지만 공짜라는데 뭐 받지 않을 이유가 있나.
내려받기는 에픽 게임즈 런처 설치한 뒤 런처 통해서 내려받는 방법이 가장 수월하다.