2020년 3월 16일 월요일

[C++] 암시적 링크와 명시적 링크

동적 라이브러리 (DLL)을 컴파일 하면 *.lib 파일과 *.dll 파일이 생성된다. 여기서 *.lib 파일(가져오기 라이브러리)은 라이브러리의 전체 코드를 포함하고 있는 정적 라이브러리의 *.lib와 달리 DLL에서 제공하고자 하는 외부 함수 참조 정보가 담겨져 있다. 이렇게 생성된 DLL을 다른 실행 파일에서 연결하려면 다음의 두가지 방식 중 하나를 선택해야 한다.

  • 암시적 링크 : 정적 로드 방식. 함수가 정적으로 연결되고 실행 파일 내에 포함된 것과 동일한 방식으로 DLL에서 내보낸 함수를 호출할 수 있다. *.lib 파일을 이용해 연결된다. *.lib에 담겨진 정보를 토대로 런타임에 DLL의 함수 코드를 참조할 수 있게 된다.

  • 명시적 링크 : 런타임에 필요시 DLL을 로드한다. DLL에의 각 함수에 접근할 함수 포인터를 설정해 함수를 호출해야 한다. 다 사용하고 나면 DLL을 언로드 해주어야 한다. 각각 LoadLibrary(), GetProcAddress(), FreeLibrary()의 세가지 함수를 통해 구현된다.

대부분의 응용 프로그램에선 간편하고 쓰기 쉽기 때문에 암시적 링크를 사용하지만 명시적 링크 방식을 쓸 때의 장점도 있다. 명시적 링크 방식을 쓰면 프로그램 실행 중에 동적으로 DLL을 교체할 수 있다. (플러그인) 그리고 프로그램 실행 전에 필요한 모든 DLL을 메모리에 로딩하는 암시적 링크 방식에 비해서 빠른 실행 시간을 갖는다.

https://docs.microsoft.com/ko-kr/cpp/build/linking-an-executable-to-a-dll?view=vs-2019

[Java] BufferedReader

BufferedReader는 입력 스트림(파일, 소켓, ...) 에서 텍스트를 쉽게 읽을 수 있는 클래스이다. 텍스트 데이터를 효율적으로 읽을 수 있도록 문자를 버퍼링한다. (입력된 데이터가 바로 전달되지 않고 버퍼링 된 후 전달된다.)

BufferedReader는 데코레이터 패턴을 적용한 좋은 예다. (* 데코레이터 패턴은 객체에 동적으로 새로운 책임을 추가해준다. 서브클래스를 만드는 것보다 더 합리적이다.)
여기서 데코레이터는 말 그대로 장식자로, 특정 객체를 감싼 뒤 중간에 위치해서 오고 가는 요청과 응답을 더 매끄럽게 처리해 준다. 당연히 데코레이터는 자신이 감싼 객체의 동작을 잘 알고 있어야 한다.

reader = new BufferedReader(new FileReader("sample.txt"));
reader = new BufferedReader(new InputStreamReader(System.in));
reader = new BufferedReader(new InputStreamReader(socket.getInputStream());

위 구문에서 보듯 Reader를 구현하는 인스턴스를 감싸, 유연하게 버퍼링 기능을 추가(확장) 해줄 수 있다. 저렇게 한번 감싸주면 readLine() 같은 메서드를 이용해 데이터를 쉽고 간편하게 읽어들일 수 있게 된다.

마지막으로 BufferedReader는 Scanner와 자주 비교된다. 비슷한 일을 해주는 클래스를 굳이 따로 구분한 까닭이 있을 것이다. 주요 차이점은 다음과 같다.

• BufferedReader는 thread-safe하지만 Scanner는 그렇지 않다.
• Scanner는 정규 표현식을 써서 구문 분석을 할 수 있다. (파싱 같은 작업을 하는데 편리하다.)
• Scanner는 IOException 을 숨기고, BufferedReader는 이를 드러낸다.
• BufferedReader의 기본 버퍼 크기가 더 크고, 데이터를 읽기만 하는 경우 Scanner 보다 빠르게 처리할 수 있다.

Scanner 사용법 : Text Parsing Made Easy
https://dzone.com/articles/java-scanner-text-parsing-made-easy

2020년 3월 10일 화요일

[Django] no such table: main.auth_user__old

해당 오류는 SQLite 3.26.0에서 ALTER TABLE RENAME 구문에 대한 사양 변경으로 발생한 오류라고 한다. 기존의 모델(models.py) 또는 어드민(admin.py)을 수정하는 과정에서 이 오류가 발생할 수 있다. 오류는 Django 2.1.5 이하 버전에서 발생하며 Django의 버전을 2.1.5 이상으로 올린뒤 다시 마이그레이션(makemigrations, migrate) 명령을 호출해주면 문제는 해결된다. 사용하는 버전에 따라 SQLite를 다시 설치해야 하는 경우도 있다고 하는데 내 경우엔 필요한 작업이 아니었기 때문에 확실한 정보는 아니다.

어쨌든 조치를 하기 전에 데이터 백업은 필수.

참고로 contrib.auth.views.login(), logout() 함수는 Django 2.1에서 삭제되었으므로 해당 함수를 사용하는 Django 버전을 쓴다면 LoginView.as_view(template_name='...') 방식으로 수정이 필요하다.

//

위와 같이 작업한 후 마이그레이션은 되었는데, 다시 사용자 데이터를 수정할 때 동일한 문제가 생긴다면 직접 db.sqlite3에 접속해 문제를 해결하는 방법이 있다. (기존 DB를 날리고 새 DB 파일을 생성하는 건 차마 못하겠다..)

커맨드 라인에서 Django 프로젝트 폴더로 이동해 sqlite3 db.sqlite3를 입력하면 Django가 바라보는 SQLite DB에 접근할 수 있다.

테이블 목록을 확인하는 명령어는 .tables 인데 입력해보면
사용자 데이터는 auth_user
그룹 데이터는 auth_group
사용자와 그룹에 대한 관계 데이터는 auth_user_groups
에 저장되어 있는 것을 확인할 수 있다.

수정하고 싶은 데이터의 ID를 알아내 스키마에 맞게 SQL을 작성하면 원하는 작업을 할 수 있는 것이다.

문제가 되는(?) auth_user_groups의 테이블 생성 구문을 살펴보면 아래와 같이 user_id 컬럼이 존재하지 않는 auth_user__old 테이블의 id를 참조하고 있는 것으로 확인된다.

CREATE TABLE "auth_user_groups" (
"id" integer NOT NULL,
"user_id" integer NOT NULL,
"group_id" integer NOT NULL,
PRIMARY KEY("id" AUTOINCREMENT),
FOREIGN KEY("group_id") REFERENCES "auth_group"("id"),
FOREIGN KEY("user_id") REFERENCES "auth_user__old"("id")
);

아마도. 다른 트러블 슈팅 문서대로 조치하였는데 고쳐지지 않는다면 이 부분을 해결하면 될 것이다. 방법은 아래와 같이 외래키가 올바르게 지정된 테이블을 생성한 뒤 교체해 주는 작업을 진행하면 된다.

PRAGMA foreign_keys = OFF;

CREATE TABLE auth_user_groups_temp (
    "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, 
    "user_id" integer NOT NULL REFERENCES "auth_user" ("id"), 
    "group_id" integer NOT NULL REFERENCES "auth_group" ("id")
);

INSERT INTO auth_user_groups_temp SELECT * FROM auth_user_groups;

DROP TABLE auth_user_groups;

ALTER TABLE auth_user_groups_temp RENAME TO auth_user_groups;

PRAGMA foreign_keys = ON;


2020년 3월 4일 수요일

[Java] 프로토콜 버퍼 (Protocol Buffers) 기초

아래의 내용은 Java 언어를 이용해 프로토콜 버퍼를 사용하는 것에 대한 내용이다.

자세한 내용은 아래 링크를 참고하시면 됨.
https://developers.google.com/protocol-buffers/docs/javatutorial

프로토콜 버퍼는 직렬화 라이브러리로 프로그래밍 언어를 통해 만들어진 데이터를 bytes로 변환해 준다. 프로토콜 버퍼는 XML, JSON 방식보다 더 작고(smaller data) 빠르게 동작한다. 데이터를 텍스트가 아니라 bytes로 변환하기 때문이다. 알려진 내용에선 프로토콜 버퍼가 텍스트 포맷 송수신 대비 10배 적은 용량을 사용하면서도 100배 빠르게 동작한다고 한다.

게다가 사용자가 하기 싫어하는 지저분한 작업들을 프로토콜 버퍼가 대신 해준다. 직렬화/역직렬화는 물론이고 enum과 문자열 조작에 대한 것들이 이러한 작업이다. 또한 서로 다른 언어간 데이터를 주고 받을 때 발생하는 문제점들도 프로토콜 버퍼가 해결해준다. 사실 이게 가장 강력한 기능일 것이다.

튜토리얼에 따르면 프로토콜 버퍼를 사용하기 위해선 아래의 3가지를 알아야 한다고 되어 있다.

• .proto 파일을 정의하는 방법.
• 프로토콜 버퍼 컴파일러를 이용해 .proto 파일을 .java 파일로 변환하는 방법
• 프로토콜 버퍼의 Java API로 메시지를 읽고 쓰는 방법.

프로토콜 버퍼를 사용하는 이유는 다음과 같다.

• 직접 직렬화 라이브러리를 작성해야 하는 경우 또는 빌트-인 직렬화 기능보다 더 나은 솔루션이 필요한 경우
• 직렬화된 객체를 각기 다른 언어로 작성된 프로그램에서 읽어서 사용해야 하는 경우

* 물론 "단순한 객체를 단일 언어에서" 간단하게 객체를 읽고 쓰기 위함이라면 (임시로) 자체적인 로직을 작성하거나 XML/JSON 등의 텍스트 형식으로 이를 처리할 수 있을 것이다.


□ .proto 파일을 정의하는 방법.


프로토콜 버퍼의 프로토콜 포맷은 .proto 확장자를 이용한다.

C의 struct 정의와 유사한 문법으로 메시지 형식을 정의하는데 message 키워드를 이용해 각각의 데이터 스트럭쳐를 정의할 수 있다. 이렇게 만들어진 .proto 파일은 프로토콜 버퍼 컴파일러를 통해 타겟 언어의 소스 코드로 변환된다. 그리고 각자 작성하는 프로그래밍 언어에서 이 파일을 참조해 목적에 맞는 작업을 하면 된다.

message A { message B { ... } } 식의 nested 방식을 지원하며 bool, int32, float, double, string등의 익숙한 자료형을 쓸 수 있다. enum을 지원하며 repeated 키워드를 이용해 리스트 형식을 표현할 수 있다.

* Java 언어인 경우 package, java_package 옵션을 이용해 이름 충돌이 발생하지 않도록 패키지 이름을 정의해주어야 한다.

필드엔 반드시 값이 지정되어야 한다는 의미의 required와 값이 지정되지 않아도 된다는 의미의 optional이 위치하게 된다. 튜토리얼에 의하면 required는 가급적 사용하지 않는 것이 좋다고 한다. 메시지 구조의 유연함을 떨어뜨리는 요인으로 나중에 가서 하위 호환성을 지키기 어려워지기 때문이다.

마지막으로 한가지, 모든 필드엔 " = 1", " = 2" 식의 숫자 태그를 붙여주어야 한다. 필드를 직렬화할 때 정보를 컴파일러에게 알려주는 일을 하는데 하위 호환성을 제공하려면 한번 부여한 숫자는 절대로 변경해선 안된다. 태그가 변경되면 바이너리 포맷이 틀어지기 때문이다.


□ 프로토콜 버퍼 컴파일러를 이용해 .proto 파일을 .java 파일로 변환하는 방법


protoc.exe가 제공되는데 이를 가지고 타겟 프로그래밍 언어로 프로토콜 포맷에 대한 소스코드를 생성할 수 있다.
protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/프로토콜포맷파일명


□ 프로토콜 버퍼의 Java API로 메시지를 읽고 쓰는 방법.


필드에 대한 getter(), setter()는 물론이고 빌더-패턴을 제공하기 때문에 객체에 값을 지정하고 읽는 것엔 큰 어려움이 없을 것이다.

Person john =
  Person.newBuilder()
    .setId(1234)
    .setName("John Doe")
    .setEmail("jdoe@example.com")
    .addPhones(
      Person.PhoneNumber.newBuilder()
        .setNumber("555-4321")
        .setType(Person.PhoneType.HOME))
    .build();

객체를 읽고 쓰는덴 아래의 4가지 API가 제공된다.

byte[] 버전과,
  • byte[] toByteArray();
  • static Person parseFrom(byte[] data);

스트림 버전이 있다.
  • void writeTo(OutputStream output);
  • static Person parseFrom(InputStream input);

추가로, 통신으로 객체를 주고 받을 수도 있는데 Netty와 궁합이 좋다.
디코딩 단계에선 ProtobufVarint32FrameDecoderProtobufDecoder를,
인코딩 단계에선 ProtobufVarint32LengthFieldPrependerProtobufEncoder를 사용하면 된다.
사용자의 입장에선 데이터 프레임의 길이만 앞에 붙여서 서로 주고 받으면 되는 것이다.


2020년 3월 3일 화요일

[Netty] Netty의 데이터 처리 순서

Netty에서의 데이터 처리 순서를 이해하려면 Channel과 Pipeline에 대한 이해가 먼저 되어야 한다. Netty에서 Channel은 읽기, 쓰기, 연결 등의 I/O 작업이 가능한 통로이고, Channel에 연결된 Pipeline엔 인바운드, 아웃바운드, 인아웃바운드 핸들러가 쭉 연결되어 있다. 각 핸들러는 서블릿 필터와 유사한 역할을 수행한다.

서버 입장에서 보면 Channel은 연결된 클라이언트마다 생성되고, Channel당 하나의 Pipeline을 갖게 된다. Pipeline에 올라가는 핸들러는 등록된 순서에 따라 연결되는데 인바운드, 아웃바운드, 그리고 인아웃바운드 핸들러를 구분하지 않고 하나의 double-linked list에 추가된다. 개인적으론 인바운드와 아웃바운드에 대한 체인이 따로 구분되었다면 이해하고 사용하기에 더 편했을 것이란 아쉬움이 있다.

인바운드 핸들러는 읽기 이벤트를 처리하는데 ByteBuf를 데이터 스트럭쳐로 변환하는 일을 하므로 디코더라고 하고,
아웃바운드 핸들러는 쓰기 이벤트를 처리하는데 데이터 스트럭쳐를 ByteBuf로 변환하는 일을 하므로 엔코더라고 한다.
* 인아웃바운드 핸들러는 읽기와 쓰기 이벤트 모두에 관여할 것이다.

엔코더와 디코더는 처리 순서를 고려하여 Pipeline의 시작 부분에 위치시키는 것이 일반적이다. 그리고 비즈니스 핸들러는 가장 나중에 위치시켜야 한다. 알아두어야 할 것은 모든 핸들러는 하나의 파이프라인에 연결된다는 점이다.


@ChannelInitializer

ChannelPipeline p = ch.pipeline();
p.addLast(new MyEncoder());
p.addLast(new MyDecoder());
p.addLast(new MyBusiness()); // 비즈니스 핸들러는 가장 마지막에 위치.

내가 작성한 프로그램으로 데이터가 입력된다면 처리 순서는 다음과 같을 것이다. 입력 데이터에 대해 엔코더는 관여하지 않는다.
MyDecoder (ByteBuf를 데이터 스트럭쳐로 변환) -> MyBusiness


반대로 내가 작성한 프로그램이 데이터를 다른 곳으로 출력한다면 처리 순서는 다음과 같아진다. 위와 마찬가지로 출력 데이터에 대해 디코더는 관여하지 않는다.
MyBusiness -> MyEncoder (데이터 스트럭쳐를 ByteBuf로 변환)


2020년 3월 2일 월요일

[Django] Django의 인증 시스템 (authentication system)

Django의 인증 시스템은 일반적인 프로젝트의 인증과 관련된 요구사항을 쉽게 해결해준다. 기능 확장에 대해서도 충분히 열려있기 때문에 기본적으로 제공되는 기능 바탕 위에 사용자가 원하는 기능을 추가할 수도 있다.
(* Django의 인증 기능은 인증(authentication)과 권한(authorization) 부여를 모두 제공하고 있으므로 일반적으론 인증 시스템(authentication system)이라고 한다.)


코어 클래스, User

Django에서 인증은 User 객체를 이용해 이루어진다. User는 주요 속성으로 username, password, email, first_name, last_name을 갖는다. 이 중 username, password, email 값은 반드시 지정되어야 한다. (required field) User 클래스는 django.contrib.auth.models 패키지에 정의되어 있다. 아래 링크로 들어가면 대략적인 느낌 정도는 파악할 수 있을 것이다. (코드는 좀 어렵다.)
https://docs.djangoproject.com/en/1.8/_modules/django/contrib/auth/models/

User에 대한 조작은 User.objects를 통해 가지고 올 수 있는 UserManager 객체를 통해 할 수 있다. 그렇지만 Django admin을 통해 GUI로 조작하는 것이 훨씬 간편하다.. 인증에서 username이 ID로 쓰인다고 보면 되고 password는 저장 시점에 암호화되어 데이터베이스에 기록된다. 암호화 과정에서 SHA256 해시 알고리즘과 함께 PBKDF2 알고리즘을 쓰는데 이는 충분히 안전하다. 이마저도 사용자가 알고리즘을 변경할 수 있게끔 구성되어 있다.


기본 인증

인증은 django.contrib.auth의 authenticate 메서드를 통해 수행되는데 입력된 username과 password가 유효하면 User 객체가 반환된다. (유효하지 않으면 None을 반환한다.)


권한

Django에는 권한 시스템이 내장되어 있다. 특정 사용자나 그룹에 권한을 부여할 수 있는 기능을 제공한다. 제공되는건 Model 객체에 대한 view, add, change, delete 권한이고, 이는 AbstractUser 클래스가 상속받는 PermissionsMixin 클래스를 통해 관리된다.


웹 요청의 인증

Django는 세션과 미들웨어를 사용해 인증 시스템을 request 객체에 연결해주고 있다.
이는 매우 편리한데 모든 request 객체로부터 로그인된 User 객체를 얻을 수 있기 때문이다. (request.user)
만약 사용자가 로그인된 상태가 아니면 AnonymousUser가 반환된다.

사용자의 인증 여부는 아래의 간단한 코드를 통해 확인할 수 있다.

if request.user.is_authenticated:
    # do something for authenticated users.

view 함수가 호출되기 전에 이를 확인할 수도 있는데 @login_required 데코레이터가 이러한 일을 해준다. (CBV 방식이면 LoginRequireMixin이 같은 일을 한다.) 비슷한 기능으로 특정 조건 체크를 위한 @user_passes_test, 권한 체크를 위한 @permission_required가 제공된다. 그냥 필요한게 다 만들어져 있다고 보면 된다. Django가 뚱땡이 프레임워크라는걸 잊지 말자.

@login_required
def my_view(request):
    # 로그인된 상태여야만 진입할 수 있음.


사용자 로그인, 로그아웃

사용자 로그인은 login() 함수를 이용해서 할 수 있다. login()은 request 객체와 user 객체를 인수로 받고, login()은 Django의 세션 프레임워크를 사용해 세션에 사용자의 ID를 저장한다.
로그아웃은 django.contrib.auth의 logout 함수를 이용하여 수행된다.

기타 기본 login.html, logout.html을 확장하거나 하는 내용들이 아래 링크에 자세히 설명되어 있다. 이 부분은 내용이 너무 길어서 생략하기로 한다.

마지막으로 언급하고 싶은 부분은 Django의 인증 시스템은 Django를 쓰지 않는 사용자에게도 좋은 참고 자료가 된다는 것이다.

https://docs.djangoproject.com/en/3.0/topics/auth/default/

[Django] DateField - auto_now, auto_now_add

Django Model Field(django.db.models.fields)에서 시간을 표현하는 클래스로 DateField, TimeField, DateTimeField가 있다.

 • class DateField(DateTimeCheckMixin, Field): # 날짜
 • class TimeField(DateTimeCheckMixin, Field): # 시간
 • class DateTimeField(DateField): # 날짜와 시간

시간과 관련해서 객체를 데이터베이스에 저장할 때 자주 쓰이는 항목이 있는데 그건 바로 생성 시간과 수정 시간에 관한 것이다. 속성 중 auto_now와 auto_now_add가 이러한 기능을 제공한다.

 • auto_now: 최종 수정 시간. True로 셋 되어 있으면 update 작업이 일어날 때마다 시간이 갱신된다.
 • auto_now_add: 최초 생성 시간. True로 셋 되어 있으면 최초 insert 작업이 일어날 때만 시간이 설정된다.

하나의 필드는 둘 중 하나만 True 값을 갖을 수 있고, 둘 중 하나에 True가 할당되면 이후 사용자가 직접 수정할 수 없게 되어 있다. 최소한의 이력 관리라도 되어야 한다면 아래와 같은 모델을 떠올릴 수 있을 것이다.

class MyModel(models.Model):
    created_on = models.DateTimeField(auto_now_add=True)
    updated_on = models.DateTimeField(auto_now=True)

https://docs.djangoproject.com/en/2.2/_modules/django/db/models/fields/#DateTimeField