제 책의 재고를 가지고 계신 분께서 기존의 사이트를 닫으시고, 인터파크 중고서점에서 판매하시고 계십니다.  실제로 책은 중고가 아닙니다.


http://book.interpark.com/product/UBizDisplay.do?_method=detail&sc.prdNo=207941317&bookblockname=b_sch&booklinkname=bprd_img


참고하시기 바랍니다.


Posted by 류종택

Indy 컴포넌트들이 사용하기 대단히 편리하게 잘 만들어져 있지만, 패킷 단위로 처리하고 싶은 요구사항 때문에 소켓으로 직접 작업하고는 했었습니다.  그러다가 오늘 문득 "TStream 클래스를 상속받아서 처리하면 간단하겠구나"하는 뒤 늦은 깨달음이 ㅠ.ㅠ


"없으면 만들어서 한다"가 기본 자세이다 보니, 쓸 때 없는 곳에 힘을 빼고 말았네요 ㅡ.ㅡ;


코드가 간단해서 설명은 생략합니다 ^^;  (메모장에는 다운로드가 끝난 다음에 한 거번에 표시됩니다)


unit _fmMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IdBaseComponent, IdComponent,
  IdTCPConnection, IdTCPClient, IdHTTP, Vcl.StdCtrls;

type
  TfmMain = class(TForm)
    IdHTTP1: TIdHTTP;
    moMsg: TMemo;
    procedure IdHTTP1Work(ASender: TObject; AWorkMode: TWorkMode;
      AWorkCount: Int64);
    procedure FormCreate(Sender: TObject);
    procedure IdHTTP1WorkBegin(ASender: TObject; AWorkMode: TWorkMode;
      AWorkCountMax: Int64);
    procedure IdHTTP1WorkEnd(ASender: TObject; AWorkMode: TWorkMode);
  private
  public
  end;

var
  fmMain: TfmMain;

implementation

type
  THTTPSream = class (TStream)
  private
    FSize : int64;
    FPosition : int64;
  protected
    function GetSize: Int64; override;
  public
    constructor Create;

    function Read(var Buffer; Count: Longint): Longint; override;
    function Write(const Buffer; Count: Longint): Longint; override;
    function Seek(Offset: Longint; Origin: Word): Longint; overload; override;
    function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; overload; override;
  end;

{$R *.dfm}

procedure TfmMain.FormCreate(Sender: TObject);
var
  HTTPSream : THTTPSream;
begin
  HTTPSream := THTTPSream.Create;
  try
    IdHTTP1.Get('http://다운받은 데이터가 있는 주소', HTTPSream);
  finally
    HTTPSream.Free;
  end;
end;

procedure TfmMain.IdHTTP1Work(ASender: TObject; AWorkMode: TWorkMode;
  AWorkCount: Int64);
begin
  //
end;

procedure TfmMain.IdHTTP1WorkBegin(ASender: TObject; AWorkMode: TWorkMode;
  AWorkCountMax: Int64);
begin
  fmMain.moMsg.Lines.Add(Format('AWorkCountMax: %d', [AWorkCountMax]));
end;

procedure TfmMain.IdHTTP1WorkEnd(ASender: TObject; AWorkMode: TWorkMode);
begin
  //
end;

{ THTTPSream }

constructor THTTPSream.Create;
begin
  inherited;

  FSize := 0;
  FPosition := 0;
end;

function THTTPSream.GetSize: Int64;
begin
  Result := FSize;
end;

function THTTPSream.Read(var Buffer; Count: Integer): Longint;
begin
  Result := 0;
end;

function THTTPSream.Seek(Offset: Integer; Origin: Word): Longint;
begin
  Result := FPosition;
end;

function THTTPSream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
begin
  Result := FPosition;
end;

function THTTPSream.Write(const Buffer; Count: Integer): Longint;
begin
  FSize := FSize + Count;
  fmMain.moMsg.Lines.Add(Format('Position: %d, Size: %d, Count: %d', [Position, Size, Count]));
end;

end.


Posted by 류종택

자동 업데이트 프로그램을 새로 만들다가 필요해서 작성한 함수입니다.  Vista 이상에서는 권한 상승이 필요합니다.  XP 서비스팩 어느 버전 이전에서는 레지스트리에 추가해줘야 할 것 입니다.  구글링하다가 그렇게 본 거 같습니다 ^^;


사용법은 AddExceptionToFirewall('등록 할 파일 이름 (패스 포함)') 입니다.


소스: http://code.google.com/p/ryulib4delphi/source/browse/trunk/XE2/FireWall.pas



Posted by 류종택

자동 업데이트 프로그램을 다시 만들 일이 생겨서 작업하던 중에 만든 함수 입니다.  구글 형님이 추천해주신 김영대님의 소스를 조금 수정했습니다.


김영대님 원본: http://www.howto.pe.kr/zboard/zboard.php?id=delphi_tiptrick&page=3&sn1=&divpage=1&sn=off&ss=on&sc=on&select_arrange=hit&desc=desc&no=913


수정된 소스: http://code.google.com/p/ryulib4delphi/source/browse/trunk/XE2/Disk.pas

  • function SetEveryoneAllowedToUseFile(AFileName:string):boolean;

SetEveryoneAllowedToUseFile('원하는 파일 이름 (패스 포함)') 처럼 하시면 됩니다.



Posted by 류종택

Vista 이후로 볼륨 컨트롤 방식이 변경되어서 윈도우 버전 마다 통일 안되는 인터페이스를 하나로 통합했습니다.


소스: http://code.google.com/p/ryulib4delphi/source/browse/trunk/XE2/VolumeControl.pas


사용법: 저장소/trunk/XE2/Samples/VolumeControl

procedure TfmMain.FormCreate(Sender: TObject);
begin
  sbMic.Position := Round(GetMicVolume * 100);
  sbSpeaker.Position := Round(GetSpeakerVolume * 100);
end;

procedure TfmMain.sbMicChange(Sender: TObject);
begin
  SetMicVolume(sbMic.Position / 100);
end;

procedure TfmMain.sbSpeakerChange(Sender: TObject);
begin
  SetSpeakerVolume(sbSpeaker.Position / 100);
end;



Posted by 류종택

예전 프로젝트에서 인디의 UDP 소켓을 이용해서 로컬에서 메시지를 전달하는데, 여러 개의 패킷을 빠르게 전송하면 손실률이 높아지는 문제를 발견한 적이 있습니다.  근래 P2P를 이용해서 프로젝트를 해야 할 일이 있어서 그 동안 그런가 보다 했던 넘을 오늘 다시 살펴보았습니다.


[소스 1] 손실률 50% 이상 발생

procedure TfmMain.btFastClick(Sender: TObject);
var
  Loop: Integer;
begin
  Tag := 0;
  for Loop := 1 to 1000 do begin
    IdUDPServer1.Binding.SendTo('127.0.0.1', 2222, FBuffer);
// 아래는 손실률을 높이게 된다.  아이피만으로 사용 할 경우에는 위의 방식을 사용
//    IdUDPServer1.SendBuffer('127.0.0.1', 2222, FBuffer);
  end;
end;


[소스 2] 손실률 0%, 로컬에서 테스트 함

procedure TfmMain.btSlowClick(Sender: TObject);
var
  Loop: Integer;
begin
  Tag := 0;
  for Loop := 1 to 1000 do begin
    IdUDPServer1.Binding.SendTo('127.0.0.1', 2222, FBuffer);
    Sleep(1);
  end;
end;


[소스 3] 손실률 0.1%, 윈속 유닛 사용

procedure TfmMain.btNativeClick(Sender: TObject);
var
  Loop: Integer;
  SockAddr : TSockAddr;
begin
  FillChar(SockAddr, SizeOf(SockAddr), 0);
  SockAddr.sin_family := AF_INET;
  SockAddr.sin_port := htons(2222);
  SockAddr.sin_addr.S_addr := inet_addr('127.0.0.1');

  Tag := 0;
  for Loop := 1 to 1000 do begin
    SendTo(FSocket, FBuffer[0], Length(FBuffer), 0, SockAddr, SizeOf(TSockAddr));
  end;
end;


귀찮아서 인디를 서버 소켓 두 개를 내려 놓고 테스트를 진행하였습니다.  보내는 쪽을 클라이언 용 컴포넌트로 교체해도 결과는 마찬가지 입니다.


[소스 2]는 이미 "빠르게 전송"이라는 의미가 퇴색했지만, 속도의 문제인지를 파악하기 위해서 검증한 것 입니다.


패킷의 크기는 손실률에 크게 영향을 주지 않으며, 특이하게도 인디 소켓의 경우, IdUDPServer1.BufferSize 크기가 작을 수록 수신률이 향상되었습니다.  (전송하는 패킷의 크기보다 작지 않은 범위에서 설정해야 합니다)


결과적으로 UDP로 다수의 패킷을 빠르게 보내려면 인디는 곤란하다.  해결 방법이 있는 지 소스를 뒤져봤지만, 찾기가 어렵더라 ㅡ.ㅡ;


방법을 찾으시면 저도 좀 ㅡ.ㅡa


인디의 문제일 가능성이 높습니다.  인디를 걷어내고, 윈속으로 서버와 클라이언트 모두 작업 한 후, 충분한 버퍼를 주고 나니 잘 작동합니다.  어느 정도 다듬어 지면 RyuSocket에 포함해서 배포하도록 하겠습니다.



Posted by 류종택

오늘 갑자기 두 넘의 성능이 궁금해져서 간단하게 테스트 프로그램을 만들어서 실행해 봤습니다.  테스트 코드는 귀찮아서 생략합니다 ^^;


일단, 싱글 스레드에서 락을 걸고 락을 푸는 일을 반복 해 본 결과는 거의 동일한 성능을 보여주었습니다.  이런 경우에 사용 될 일은 없겠지만, 기본적인 비용 문제를 살펴보았습니다.


이어서 스레드 두 개를 만들어서 TCriticalSection은 Enter와 Leave를 반복하고, TMultiReadExclusiveWriteSynchronizer의 경우에는 BeginRead와 EndRead를 반복 해 보았습니다.  허걱!  결과는 의외로 TCriticalSection의 승!!


이상해서 소스 코드를 살펴보니 TMultiReadExclusiveWriteSynchronizer 내부에는 디버깅용 코드가 많은 것을 확인, Release 모드로 컴파일 해서 다시 테스트 진행한 결과, 역시 TMultiReadExclusiveWriteSynchronizer의 경우 BeginRead의 부하가 거의 없는 것으로 나타났습니다. (즉, 테스트 환경에서는 두 배의 성능 효과)


주의 할 점은 락이 걸리는 시간이 매우 짧은 경우에는 TCriticalSection이 훨씬 유리하다는 점 입니다.  교착이 일어나지 않았을 경우의 코드의 실행 비용은 비슷하지만, 교착이 일어났을 경우의 실행 비용은 TMultiReadExclusiveWriteSynchronizer가 압도적으로 많습니다.  정확한 수치를 연구 할 정도로 흥미로운 주제는 아니라서 포기 합니다. ^^;  제 PC의 테스트 상황에서 3배가 조금 안되는 비용이 발생합니다.


또 하나의 주의 사항을 다시 강조하자면, 디버깅 모드에서는 역효과가 있습니다. !!


결론, 락이 오래 지속되는 상황에서라면 TMultiReadExclusiveWriteSynchronizer이 유리하다.  하지만, 간단한 메모리 공유의 문제라면 오히려 역효과가 날 수 있다.


http://msdn.microsoft.com/ko-kr/library/br244843(v=vs.110).aspx  이런 넘도 있었군요 ^^;  테스트 결과 어떠한 경우에도 TCriticalSection 보다 우수한 성능을 보여줍니다.  대신 재귀적인 락을 사용 할 수 없다는 단점이 있습니다.  TMultiReadExclusiveWriteSynchronizer의 경우처럼 Read(Shared)의 경우 스레드 개수만큼의 이득을 보이고 있습니다.  즉, Read는 락을 공유하기 때문에 얼마나 Read 락을 자주 하게 되느냐가 그대로 이득으로 나타난다고 생각하시면 됩니다.


델파이용 유닛은 다음 링크를 참고하시기 바랍니다.

http://code.google.com/p/ryulib4delphi/source/browse/trunk/XE2/SRWLock.pas


Posted by 류종택

지난 번 포스트에서 "인터페이스 릴레이"라는 주제를 다룬 적이 있습니다.  특정 객체가 다른 객체의 메소드를 위임 받아 실행하기 때문에 메소드가 다른 메소드를 호출하고 리턴을 돌려주는 일만 담당하는 경우가 되겠습니다.


이러한 경우 최대한 메소드 호출 시간을 절약하기 위해서 다음과 같이 뻘짓을 해봤습니다 ^^;  


1억번 반복에 약 500 ms를 절약 할 수 있었습니다.  테스트 환경은 인텔 듀얼 코어 2.6GHz 노트북입니다.


[소스]

unit _fmMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TFirstTest = class (TComponent)
  private
    FNO : integer;
  public
    procedure DoSomething;
  published
    property No : integer read FNo;
  end;

  TSecondTest = class
  private
    FTest : TFirstTest;
  public
    constructor Create;
    destructor Destroy; override;

    procedure DoSomething;
  end;

  TThirdTest = class
  private
    FTest : TFirstTest;
  public
    constructor Create;
    destructor Destroy; override;

    var DoSomething : procedure of object;
  end;

  TfmMain = class(TForm)
    btTest: TButton;
    procedure btTestClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    FFirstTest : TFirstTest;
    FSecondTest : TSecondTest;
    FThirdTest : TThirdTest;
  public
  end;

var
  fmMain: TfmMain;

implementation

uses
  MMTimer;

{ TFirstTest }

procedure TFirstTest.DoSomething;
begin
  FNo := FNo + 1;
end;

{ TSecondTest }

constructor TSecondTest.Create;
begin
  inherited;

  FTest := TFirstTest.Create(nil);
end;

destructor TSecondTest.Destroy;
begin
  FreeAndNil(FTest);

  inherited;
end;

procedure TSecondTest.DoSomething;
begin
  FTest.DoSomething;
end;

{ TThirdTest }

constructor TThirdTest.Create;
begin
  inherited;

  FTest := TFirstTest.Create(nil);
  DoSomething := FTest.DoSomething;
end;

destructor TThirdTest.Destroy;
begin
  FreeAndNil(FTest);

  inherited;
end;

{$R *.dfm}

procedure TfmMain.btTestClick(Sender: TObject);
var
  Loop1: Integer;
  Loop2: Integer;
  Tick : DWord;
begin
  Tick := MMGetTickCount;

  for Loop1 := 1 to 10000 do
//  for Loop2 := 1 to 10000 do FTest.DoSomething;  // 559 ms
//  for Loop2 := 1 to 10000 do FSecondTest.DoSomething;  // 829 ms
  for Loop2 := 1 to 10000 do FThirdTest.DoSomething;  // 580 ms

  Caption := IntToStr(MMGetTickCount - Tick);
end;

procedure TfmMain.FormCreate(Sender: TObject);
begin
  MMInitialize;

  FFirstTest := TFirstTest.Create(nil);
  FSecondTest := TSecondTest.Create;
  FThirdTest := TThirdTest.Create;
end;

end.



Posted by 류종택
var
  NewSocket : TSocket;
  pAddr : PSockAddr;
  pAddrLen : PINT;
begin
  New(pAddr);
  New(pAddrLen);
  NewSocket := WSAAccept(Socket, pAddr, pAddrLen, nil, 0);

위와 같은 코드가 64비트에서는 작동하지 않습니다.  잘못된 포인터 어쩌고 하는 에러가 닙니다.  32비트에서는 잘 돌아갑니다 ㅠ.ㅠ

PINT64 및 다른 방법으로 메모리 공간을 충분히 확보해 줘도 마찬가지 입니다.  packed record 도 한 번 시도 해보고 여러 가지 다른 방법도 찾아봤지만 모두 실패 ㅠ.ㅠ

  NewSocket := WSAAccept(Socket, nil, nil, nil, 0);

위의 코드는 32비트와 64비트에서 모두 잘 작동합니다.  이유는 아직 모르겠습니다 ^^;


일단 인디에서는 문제가 없으니, 거기서부터 힌트를 찾아야 겠습니다.  당장은 바쁜 일이 있어서 32비트로 작업을 진행하다가 해결책을 찾아봐야 겠습니다.


Posted by 류종택

오늘 한 동안 이것 때문에 고생하다가, 검색해보니, Same origin policy 때문에 원래 안되는 걸 엉뚱한 자바스크립트만 이리 저리 바꾸고 있었습니다 ㅠ.ㅠ


여하튼 해결 방법은 Response Header에 "Access-Control-Allow-Origin: *"를 추가하는 것 입니다.  아래 소스는 델파이로 만든 웹 서버에서 이를 처리하는 예제 입니다.

type
  TfmMain = class(TForm)
    IdHTTPServer: TIdHTTPServer;
    moMsg: TMemo;
    procedure IdHTTPServerCommandGet(AContext: TIdContext;
      ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
    procedure IdHTTPServerException(AContext: TIdContext;
      AException: Exception);
  private
  public
  end;

var
  fmMain: TfmMain;

implementation

{$R *.dfm}

procedure TfmMain.IdHTTPServerCommandGet(AContext: TIdContext;
  ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
  AResponseInfo.CustomHeaders.Values['Access-Control-Allow-Origin'] := '*';
  AResponseInfo.ContentText := 'Hi';
end;

아래 소스는 jQuery를 이용해서 델파이로 만든 웹 서버에서 데이터를 쿼리하는 과정입니다.

<script type="text/javascript">
	$.get('http://127.0.0.1:1234/Hello?', 
		function(data) {
			alert('Result: ' + data);
		}
	)
	.error(function() { alert("error"); });
</script>


[그림 1] 실행 결과


Posted by 류종택


티스토리 툴바