Project Darkstar server tutorial 번역


























Project Darkstar Server Application Tutorial

목차

(Java bindings for OpenGL : https://jogl.dev.java.net/)
  • 소개 (Introduction)
  • Project Darksart Server Application 코딩하기 (Coding Project Darkstar Server Applications)
    • 목표와 철학 (Goals and Philosophy)
    • 실행하기 (Approach to Execution)
      • 작업과 관리 (Tasks and Managers)
      • 작업 순번 (Task Ordering)
      • 작업 실행시간 (Task Lifetime)
    • Managed Object와 Managed Reference (Managed Objects and Managed References)
      • Managed Reference를 통하여 Managed Object 접근하기 (Accessing Managed Objects through Managed References)
      • 나만의 Managed Object 디자인하기 (Designing Your Managed Objects)
      • The Player Managed Object (The Player Managed Object)
      • The AppListener (The AppListener)
    • 서버 API 클래스 위치하기 (Locating the Server API Classes)
    • System Classes and Interfaces (System Classes and Interfaces)
    • Task Manager Classes and Interfaces (Task Manager Classes and Interfaces)
    • Data Manager Classes and Interfaces (Data Manager Classes and Interfaces)
    • Channel Manager Classes and Interfaces (Channel Manager Classes and Interfaces)
  • Lesson One: Hello World!
    • HelloWorld 코딩하기 (Coding HelloWorld)
      • HelloWorld
    • HelloWorld 실행하기 (Running HelloWorld)
      • HelloWorld 실행하기 (Running HelloWorld)
  • Lesson Two: Hello Logger!
    • HelloLogger 코딩하기 (Coding HelloLogger)
      • HelloLogger
    • The Logging Properties File
  • Lesson 3: Tasks, Managers, and Hello Timer
    • Tasks (Tasks)
    • Managers (Managers)
    • HelloTimer 코딩하기 (Coding HelloTimer)
      • HelloTimer
  • Lesson 4: Hello Persistence!
    • HelloPersistence 코딩하기 (Coding HelloPersistence)
      • HelloPersistence
    • HelloPersistence2 코딩하기 (Coding HelloPersistence2)
      • HelloPersistence2
      • TrivalTimedTask
    • HelloPersistence3 코딩하기 (Coding HelloPersistence3)
      • HelloPersistence3 26
  • Lesson 5: Hello User!
    • 사용자가 로그인할 때 일어나는 일 (Knowing When a User Logs In)
      • HelloUser
    • 직접 통신 (Direct Communication)
      • HelloUser2
      • HelloUserSessionListener
      • HelloEchoSessionListener
    • 예제 실행하기 (Running the Examples)
  • Lesson 6: Hello Channels!
    • HelloChannels 코딩하기 (Coding HelloChannels)
      • HelloChannels
      • HelloChannelsSessionListener
      • HelloChannelsChannelListener
    • HelloChannels 실행하기 (Running HelloChannels)
  • 결론 (Conclusion)
    • Best Practices
    • 튜토리얼에 없는 내용 (Things Not Covered in This Tutorial)
  • Appendix A: SwordWorld Example Code
    • Sword World
    • SwordWorldObject
    • SwordWorldRoom
    • SwordWorldPlayer


소개 (Introduction)
Project Darkstar Server(PDS)의 튜토리얼로의 여행을 환영합니다. 이 문서는 PDS상의 게입서버를 만들기 위하여 필요한 모든 것을 알려주기 위해 작성되었습니다. 우리는(역자주:아마 PDS를 진행하는 개발자들이겠죠?) 이러한 프로그램을 PDS 어플리케이션이라고 부르고, 여러분은 이 용어를 이 튜토리얼과 다른 PDS 문서들에서 보게 될 것입니다. 이 튜토리얼은 PDS 어플리케이션을 어떻게 만들 수 있는지에 대한 개략으로 시작해서 한 단계씩 간단한 어플리케이션을 개발하여보겠습니다.
Welcome to the Project Darkstar Server (PDS) application tutorial. This document is designed to teach you everything you need to know to start writing game servers that run on top of the Project Darkstar Server. We call such programs PDS applications, and you will see that term used in this and other PDS documents. This tutorial begins with an overview of how to code a PDS application, then steps through the development of a very simple application.


PDS 어플리케이션 코딩하기 (Coding Project Darkstar Server Applications)
이 챕터는 PDS 환경에서 어덯게 게임 서버 어플리케이션을 코딩하는지에 대한 기초 컨셉을 알려줍니다. 해당 컨셉들을 이해한다는 것은 규모가 크고 신뢰성이 있으며 오류에 강하고 견고한 네트웍 게임을 만드는 첫번째 단계입니다.
This chapter presents the fundamental concepts of how to code game server applications in the Project Darkstar Server (PDS) environment. Understanding these concepts is the first step on the path to building massively scalable, reliable, fault-tolerant, and persistent network games.


목표와 철학 (Goals and Philosophy)
PDS 코딩 모델을 이해하기 위해 PDS 시스템의 설계 목표를 이해하는 것이 좋습니다. 기본적인 목표는 다음과 같습니다.
In order to understand the Project Darkstar Server (PDS) coding model, it is useful to understand the system's goals. The fundamental goals are as follows:
  • 신뢰성이 있으며 견고하고 대규모에 적용가능하며 오류에 강한 게임 개발자에게 투명한 서버측 코딩을 할 수 있어야 한다.
  • 개발자에게 단일 스레드 이벤트 기반 프로그래밍 모델을 제공하여야 한다. 개발자의 다른 이벤트등에 대한 오류가 전파되지 않도록 하여야 한다.
  • Make server-side game code reliable, scalable, persistent, and fault-tolerant in a manner that is transparent to the game developer.
  • Present a simple single-threaded event-driven programming model to the developer. The developer should never have his or her code fail due to interactions between code handling different events. Applications coded to run in the PDS environment are called PDS applications.


실행 접근성 (Approach to Execution)

태스크와 매니저 (Tasks and Managers)
PDS 어플리케이션 프로그래머 측면에서 보면 PDS 어플리케이션은 확실히 단일 스레드와 이벤트 기반 모델로 실행됩니다. 어떤 데이터가 수정되거나 하는 하나의 이벤트를 다루는 코더에 의해서 이벤트 핸들링 코딩됩니다. 그러므로 실행이란 결과에 대한 증명과 데드락에 대한 증명으로 생각 될 수 있습니다. 사실, 동기화가 전혀 필요 없는 대부분의 상태라는 조건하에 Managed Object에서 synchronized 키워드를 시도하는 것은 해결하기 어려운 버그를 초래할 수 있습니다.
실제로 이 시스템(PDS)는 각각의 이벤트를 처리하기 위해 상당수의 스레드를 사용합니다. 이 스레드들을 컨트롤 하는 것을 태스크(tasks)라고 합니다. PDS는 각각의 무슨 태스크가 억세스하는 데이터들을 추적합니다. 충돌을 피하기 위하여, 이전에 생성된 태스크가 제대로 종료되도록 최근 생성된 태스크는 중지되며 스케쥴링됩니다.
태스크는 PDS 매니저(PDS Manager)에 의해 생성됩니다. PDS 환경 안팍으로 PDS 어플리케이션은 메니저를 사용하여 효과적인 액션을 취할 수 있습니다. PDS에는 세가지 기본 매니저가 있습니다. 또한 PDS 환경에 임의의 매니저를 추가할 수 있습니다. 세가지 기본 매니저는 다음과 같습니다.
From the point of view of the PDS application programmer, PDS applications execute in an apparently monothreaded, event-driven model. The code handling the event appears to the coder to have sole ownership of any data it modifies. Thus, execution is both race-proof and deadlock-proof. Under most conditions there is no need to synchronize application code and, in fact, attempting to use the synchronized keyword in Managed Objects1 can cause subtle bugs.
In actuality, the system has many threads of control all simultaneously processing their own events. These threads of control are called tasks. The system keeps track of what data each task accesses. Should a conflict arise, the younger task is aborted and scheduled for retry at a later date so that the older task can complete and get out of the way. (“Younger” and “older” refer to the time at which the task was queued for execution.)
Tasks are created by PDS managers. a PDS application uses these managers to effect actions in the PDS environment and the outside world. There are three standard managers in the system. In addition, arbitrary managers may be coded and added to the PDS environment. The standard managers are:
  • Task Manager
태스크 매니저를 사용하여 PDS 어플리케이션에서 큐를 사용할 수 있습니다. 한 태스크는 자식 태스크를 생성할 수 있습니다. 즉시 또는 지연 후 실생되거나 주기적으로 실생되기 위해 큐에 추가될 수 있습니다. 형제 태스크라고 말할 수 있는 다중 자식 태스크는 같은 한 부보 태스크에 의해 큐에 추가됩니다.
A PDS application can use the Task Manager to queue tasks of its own. Tasks can be queued for immediate execution, delayed execution, or periodic execution. Tasks created from within other tasks are called child tasks. The task that queued the child task is called the parent task. Multiple child tasks queued by the same parent task are called sibling tasks.
  • Data Manager
PDS 어플리캐이션은 데이터 매니저를 사용하여 Managed Object라고 하는 퍼시스턴스 클래스 또는 분산 자바 오브젝트를 생성할 수 있습니다.
A PDS application can use the Data Manager to create and access persistent, distributed Java objects called Managed Objects. The PDS application is itself composed of Managed Objects
  • Channel Manager
채널 매니저는 데이터 채널을 컨트롤 하기 위하여 사용됩니다. 이 데이터 채널은 보통 서로 다른 클라이언트나 서버-클라이언트의 통신을 위해 사용됩니다.
A PDS application can use the Channel Manager to create and control publish/subscribe data channels. These data channels are used to communicate between different clients and between clients and the server.
PDS 어플리케이션은 AppContext class를 사용하여 다른 매니저를 접근하는 PDS 핵심 기능에 접근합니다.
A PDS application gets access to core PDS functionality through the AppContext class, which provides methods to obtain references to the various managers.


태스크 정리하기 (Task Ordering)
클라이언트로부터 생성된 이벤트를 핸들링하는 태스크는 특정 규칙에 의해 실행이 보증됩니다. 한 클라이언트의 이전 이벤트가 모두 끝나기 전에는 그 이후 발생될 클라이언트 이벤트를 핸들링하는 태스크가 실행하지 않을 것입니다. 자식 태스크는 그 부모 태스크를 주목하고 있으나 형제 태스크에 관하여는 그렇지 않습니다. 이것은 자식 태스크의 실행이 부모 태스크의 실행이 끝나기 전 까지 시작되지 않는 것을 의미합니다. 그러나 형제 태스크에 관한 순서에 대한 보증은 존재하지 않습니다.
중요 : PDS에서 이외의 다른 태스크 순서에 대한 보증은 더이상 있지 않습니다. 실제적으로, 서로 다른 유저의 이벤트를 핸들링에 대한 실행은 한 서버 안에서도 각각의 다른 태스크의 실행 시작 순서를 보증하지 않습니다.
Tasks that handle events created by a client are guaranteed to execute in order. A task to handle a client event that occurred later in time will not start executing until the tasks to handle all earlier events generated by the same client have finished. A child task is ordered with regard to its parent task, but not with regard to its siblings. This means that execution of a child task will not begin until execution of the parent has completed. There are, however, no guarantees among sibling tasks as to order of execution.
Important: There are no other order guarantees in the system. In particular, execution of tasks to handle events of different users are not guaranteed to start executing relative to each other in the order they arrived at the server.

태스크 생명주기 Task Lifetime
다른 태스크에 요청될 수 있는 자원들의 과도한 요청으로 블럭되지 않기 위해 태스크는 짧은 주기로 제공됩니다. PDS 자체는 최대한의 실행시간으로 설정되어 있습니다(디폴트 1분). 각각의 설정대로 아직 종료되지 않은 다른 태스크는 시스템에 의해 강제로 종료될 것입니다. 만약 어떤 태스크의 실행 기간이 너무 길다면 실행시간을 줄일 수 있는 두가지 방법이 제공됩니다.
Tasks are intended to be short-lived so that they do not block access for an inordinate amount of time to resources that might be needed by other tasks. The PDS is configured by its operator with a maximum task execution time (the default is one minute). Any task that does not finish within that time will be forcibly terminated by the system.
If you have a task that runs too long, there are two approaches to reducing its execution time:
  • 자식 태스크의 연결로 각각 나누거나, 문제되는 한 부분마다 핸들러를 추가하거나 다음 태스크로 설정하여 순차적으로 큐에 넣는 것입니다. 보통 continuation-passing style 이라고 알려진 방법입니다.(연결-통과 스타일? -_-)
Split it up into a chain of child tasks, each of which handles one discrete portion of the problem, and then queues the next task in sequence. This is known as continuation-passing style.
  • 소비되는 시간의 계산이 종료되었을 때의 결과를 갖는 태스크를 큐에 넣어 처리하는 방법입니다.
  • Move the time-consuming calculations into a custom manager that queues a result-task when the calculations are complete.
이 접근법들은 각각 장단점이 있습니다. 첫번째 방법은 태스크들을 잘게 쪼갤 수 있을 때 문제를 해결하는데 더 쉽습니다. 주의할 것은, 각 단계가 실행될 때 어떠한 정확한 보증도 없기 때문에, 데이터를 포함한 각각의 태스크가 민감하고 불안한 상태로 있게 된다는 것입니다. 이 접근 방법의 특별한 경우는 여러 문제의 부분들이 각각 다른 위치에 개별적이고 핸들링 가능한 경우입니다. 이러한 경우, 종료까지 필요한 시간은 각각 병렬로 실행되는 태스크의 연결상태에 의해 줄어들 수도 있습니다. 그러나 이 별개의 연결상태는 각각 서로 정확하지 않은 순서로 있을 수 있으므로 그 상태의 태스크들의 수행은 반드시 서로 독립적이어야 합니다. 두번째 접근법은 태스크를 작은 상태로 분해할 수 없을 때 적용하기 쉽습니다. 그러나 이 방법은 여러분이 커스텀 PDS 매니저를 만들고 설치하여야 합니다. (커스텀 PDS 매니저를 만드는 것은 'PDS 환경을 확장하는 방법'에 관한 문서를 보시기 바랍니다.)
특별히 중요한 경우는 태스크의 실행주기가 좀 더 긴 것을 블럭하기 위해 시스템 콜을 호출하는 경우입니다. 이 경우 반드시 정교한 커스텀 매니저를 개발하는 것이 필요합니다.
Each approach has its advantages and disadvantages. The first is easier for simple problems that lend themselves to serial decomposition. Care must be taken that each task ends with the data in a sensible and usable state, because there is no guarantee as to exactly when the next step will be executed. A special case of this approach is where parts of the problem are separable and handleable in parallel. In this case, the time to complete may be reduced by launching parallel chains of tasks. These parallel chains, however,
have no guaranteed ordering in relation to each other, so the work they perform must really be independent of each other, The second approach is easier for problems that don't decompose well into small, discrete components; however, it requires the writing and installation of a custom PDS manager. (Writing custom PDS managers will be covered by a separate document explaining how to extend the PDS environment.)
A particularly important case is code that has to go into system calls that can block for more then the task execution lifetime. These must be implemented through a custom manager in order to produce a robust PDS application.


Managed Objects and Managed References (역자주 : 원어 그대로 사용하는 용어이고 다른 용어가 생각나질 않아서 그대로 둡니다.)
데이터 매니저는 오브젝트 스토어라고 하는 오프젝트의 풀에 저장된 매니지드 오브젝트의 퍼시스턴스 셋을 보존하고 있습니다. 보통 자바 오브젝트와 같이, 각 매니지드 오브젝트는 데이터와 데이터를 처리하기 위한 메소드를 포함합니다. 매니지드 오브젝트를 작성하려면 ManagedObject와 Serializable 인터페이스를 상속하여야 합니다. 매니지드 오브젝트는 오브젝트 풀에 해당 오브젝트가 있는 동안 오브젝트 소토어의 풀의 한 부분이 되지 않습니다. 이는 매니지드 레퍼런스를 상속받은 오브젝트 또는 오브젝트를 상속받은 오브젝트는 데이터 매니저에 의해 결정된다는 것입니다.
매니지드 레퍼런스는 전형적인 J2SE(tm)의 레퍼런스 오브젝트와 같습니다(예를 들면, SoftReference, WeakReference). 매니지드 오브젝트는 반드시 다른 매니지드 레퍼런스를 통하여 다른 매니지드 오브젝트를 참조하여야 합니다. 이는 매니지드 오브젝트의 한 상태의 부분인 매니지드 오브젝트의 컴포넌트 오브젝트를 레퍼런스 하는 것과, 자신의 상태를 포함한 서로 다른 매니지드 오브젝트를 레퍼런스 하는 것에 대한 차이점을 데이터 매니저가 어떻게 표할 수 있는지에 대한 것입니다.
매니지드 오브젝트가 데이터 매니저에서 getBinding 을 호출하는 또다른 태스크에 의해 오브젝트 스토어로부터 갱신될 수도 있을 만큼, 이 바인딩한 이름은 매니지드 오브젝트와 함께 스트링에 첨가시킵니다.
The Data Manager maintains a persistent set of Managed Objects stored in a pool of objects called the Object Store. Like a normal Java object, each Managed Object contains both data and the methods to act upon that data. In order to be a Managed Object, the object must implement both the ManagedObject and Serializable interfaces. A Managed Object does not become part of the Object Store's pool until the pool is made aware of the object. This is done by using the Data Manager either to request a Managed Reference to the object or to bind a name to the object.
A Managed Reference is a reference object that looks much like the J2SE(tm) reference objects (for example, SoftReference, WeakReference). Managed Objects must refer to other Managed Objects through Managed References. This is how the Data Manager can tell the difference between a reference to a component object of the Managed Object (for instance, a list) that is part of that Managed Object's state, and a reference to a separate Managed Object with a state of its own.
A name binding associates a string with the Managed Object such that the object may be retrieved from the Object Store by other tasks using the getBinding call on the Data Manager.

매니지드 레퍼런스를 통하여서 매니지드 오브젝트 억세스하기 (Accessing Managed Objects through Managed References)
매니지드 레퍼런스는 get과 getForUpdate 두 개의 메소드를 억세스할 수 있습니다. 두 메소드 모두 오브젝트의 복사본을 리턴합니다. 두 메소드의 차이점은 다음과 같습니다.
The Managed Reference has two access methods: get and getForUpdate. Both methods return a task-local copy of the object. The difference between the two methods is:
  • getForUpdate는 매니지드 오브젝트의 상태가 변경될 것이라고 시스템에 통보합니다.
getForUpdate informs the system that you intend to modify the state of the Managed Object.
  • get은 단지 상태를 read할 때 사용됩니다.
get says you intend only to read the state but not write it.
다른 매니지드 오브젝트의 모든 변경점이 변경되지 않는다 하여도(get을 통한 억세스를 포함하여), 여러분이 특정 시간에 매니지드 오브젝트의 상태가 변경될 것을 예상하고 있다면 getForUpdate를 사용하는 것이 좀 더 효율적입니다. 이는 시스템이 태스크와 훨씬 유효하고 빨리 생성된 핸들러와 태스크 사이의 충돌을 탐지하는 것을 가능하게 합니다.
반대로 말하면, 매니지드 오브젝트의 상태가 변경되지 않을 것이라면 get을 사용하는 것이 더 낫습니다. get을 호출하는 것은 다중 태스크가 동작하는 상태에서 매니지드 오브젝트에 대하여 좀 더 병렬적으로 접근하는 것을 가능하게 합니다. 여러분이 오브젝트의 상태를 수정하는 것을 알고 있는 시점이라면, 데이터 매니저의 markForUpdate를 호출하여 get에서 getForUpdate까지 접근을 올릴 수 있습니다. (업데이트를 하기 위해 똑같은 매니지드 오브젝트를 확인하는 다중 호출은 위험하지 않습니다.)
같은 태스트 내의 동등한 매니지드 레퍼런스상의 get 또는 getForUpdate 호출로의 Subsequent 호출은 같은 태스크의 복사본을 리턴할 것입니다.
getBinding을 호출하여 바인딩된 이름의 오브젝트를 갱신할 수 있습니다. 이는 매니지드 오브젝트의 get을 호출하는 것과 같아서, 해당 오브젝트를 변경할 예정이라면 여러분은 그 오브젝트를 갱신한 후 markForUpdate를 호출하여야 할 것입니다.
오브젝트 스토어 내의 매니지드 오브젝트들은 가비지 컬렉션에 영향을 받지 않습니다. 일단 매내지드 오브젝트가 스토어에 있다는 것만 알려주면, 데이터 매니저의 removeObject를 호출하여 오브젝트 스토어에서 명시적으로 제거되기 전까지 해당 오브젝트의 상태가 유지됩니다. 이는 어플리케이션이 더이상 필요 없을 때 오브젝트 스토어에서 오브젝트를 제거하고 생명주기를 관리하여야 한다는 것을 말합니다. 이를 잘못한다면 성능과 메모리 낭비라는 결과를 초래할 수 있습니다. 마찬가지로, 네임 바인딩은 removeBinding을 호출하여 제거하기 전까지 메모리상에 있을 것입니다. 네임 바인딩은 제거된 오브젝트를 참조하고 있을 때에도 사라지지 않습니다.
Although all changes to any Managed Object are persistent (even those accessed via get), it is more efficient to use getForUpdate if you know at that time that you are going to want to modify the Managed Object's state. This allows the system to detect conflicts between tasks and handle them earlier and with greater efficiency.
Conversely, it is better to use get if the state of the Managed Object may not be modified. The get call can allow for more parallel access to the Managed Object from multiple tasks. If you reach a point later in the execution where you know you are going to modify the object's state, you can upgrade your access from get to getForUpdate by calling the markForUpdate method on the Data Manager. (Multiple calls to mark the same Managed Object for update are harmless.)
Subsequent calls to get or getForUpdate on equivalent Managed References in the same task will return the same task-local copy.
You can also retrieve an object with a bound name by calling getBinding. This is equivalent to a get call on a Managed Reference to the object, so, if you intend to modify the object's state, you should call markForUpdate after retrieving the object.
Managed Objects in the Object Store are not garbage-collected. Once the store is made aware of a Managed Object, it keeps the state of that object until it is explicitly removed from the object store with a call to the removeObject call on the Data Manager. It is up to the application to manage the life cycle of Managed Objects and to remove them from the Object Store when they are no longer needed. Failure to do so may result in garbage building up in your Object Store and impacting its performance. Likewise, name bindings are stored until explicitly destroyed with removeBinding. A name binding is not removed when the object it refers to is removed.



자신만의 매니지드 오브젝트 설계하기 (Designing Your Managed Objects)
일반적으로 매니지드 오브젝트는 세가지 타입입니다.
Managed Objects typically fall into three general types of entity:
  • 칼, 몬스터, 활동공간(예를들면 방) 등과 같은 게임상의 가상환경에 동작하는 오브젝트.
Actual objects in your game's simulated environment, such as a sword, a monster, or a play-space (such as a room).
  • 근접 계산을 위한 quau-tree나 이동 경로 결정 work-mesh와 같은 로직 또는 데이터 구조.
Purely logical or data constructs such as a quad-tree for determining player-proximity or a walk-mesh to determine movement paths.
  • 게임 플레이어를 위한 매니지드 오브젝트 프록시
Proxies for human players in the world of Managed Objects.
Figure 1) 한 방안의 두명의 게임 플레이어와 칼 하나를 포함한 방의 간단한 월드 구조 개요
Figure 1 below illustrates a very basic world consisting of a single room that contains two players and a sword.
Figure 1: Example of a simple ManagedObject world

다중 매니지드 오브젝트상의 데이터 전송을 결정할 때 다음 세가지를 고려하여야 합니다.
When deciding where to break data up into multiple Managed Objects, consider these questions:
  • 얼마나 큰 데이터입니까? 자신의 상태에 한 개의 매니지드 오브젝트를 포함하는 큰 데이터는 읽고 쓰는데 더 많은 시간을 요합니다.
How big is the data? The more data a single Managed Object encompasses in its state, the more time it takes to load and save.
  • 그 데이터의 결합도가 얼마나 가깝습니까? 일반적으로 공용으로 사용되는 데이터는 똑같은 매니지드 오브젝트에서 좀 더 효과적으로 저장됩니다. 독립적으로 사용되는 데이터는 다른 매니지드 오브젝트상으로 구분되기 위한 대상입니다. 핵심적으로 수정되어야 하는 데이터는 최우선적으로 같은 매니지드 오브젝트에 저장되어야 합니다.


How closely coupled is the data? Data that are generally accessed together are more efficiently stored in the same Managed Object. Data that are accessed independently are candidates for separation onto different Managed Objects. Data that have to be modified atomically are best stored in the same Managed Object.
  • 특정 데이터를 동시에 접근하여야 하는 태스크들이 얼마나 많습니까? 위에 언급한 것에 따르면, PDS는 가능한한 병렬로 실행되는 많은 태스크와 같이 실행되기 위한 최선책을 수행합니다. 다중 병령 수행되는 태스크가 같은 매니지드 오브젝트의 상태를 변경할 때 충돌을 피하는 것은 많은 비용이 소요됩니다.


How many simultaneous tasks are going to need access to this data? As explained above, the PDS does its best to execute as many tasks in parallel as it can. Resolving the conflicts that arise when multiple parallel tasks want to change the state of the same ManagedObject can be expensive.
get과 같이 공유될 수 있는 데이터를 업데이트하기 위하여 데이터에 lock을 거는 것이 데이터를 나누기 위한 최선책입니다. 단지 읽히는 데이터가 다중 태스크에 읽혀지는 것에의해 공유되는 것에 반하여, 업데이트되어야할 데이터는 업데이트하는 태스크에 의하여 수행되어야 합니다. 다중 태스크가 적어도 하나 이상의 태스크에 의해 업데이트되는 매니지드 오브젝트상의 항목들에 접근하여야 할 때, 매니지드 오브젝트는 병복에 걸릴 수 있습니다. 최고의 성능을 위하여 가능한한 적은 병목을 원할 것입니다.
It is best to split up data that has to be locked for update from data that can be shared with a get. Data that is going to be updated has to be owned by the updating task, whereas data that is just read can be shared by multiple reading tasks. When multiple tasks have to access fields on a Managed Object that is being updated by at least one of them, that Managed Object becomes a potential bottleneck. For best performance, you want as few bottleneck Managed Objects as possible.
이러한 고찰을 통하여 볼 때 세번째 고려사항은 잘 동작하는 PDS 어플리케이션을 작성하기 위하여 가장 크리티컬한 주제입니다.

Of all these considerations, the third is the most critical to a well-running PDS application.

플레이어 매니지드 오브젝트 (The Player Managed Object)
외부에서 이벤트가 발생할 때 매니저에 의한 이벤트 핸들러가 get 메소드를 호출하기 위하여 매니지드 오브젝트는 그 자신을 등록할 것입니다. 첫번째 가장 중요한 매니지드 오브젝트 타입이 플레이어 매니지드 오브젝트 입니다. 플레이어 매니지드 오브젝트는 ClientSessionListener 인터페이스를 상속하고 AppListener의 loggedIn 콜백으로부터 리턴값을 받을 때 시스템에 리턴됩니다. 이 후, 어떤 입력되는 데이터 패킷과 게임 플레이어의 접속해제 이벤트에 의해 호출될 것입니다.
플레이어 매니지드 오브젝트는 여러 매니지드 오브젝트 사이에서 마치 플레이어와 같이 동작을 수행할 것입니다. 게임 플레이어는 PDS Client API를 사용하여 서버로 데이터 패킷을 보냅니다. 이는 플레이어 매니지드 오브젝트의 userDataReceived 메소드를 호출하는 태스크의 결과로 시스템에서 userDataReceived 이벤트를 발생시킵니다. 플레이어 매니지드 오브젝트는 이를 수행함을 찾아내기 위해 패킷을 분석한 후, 필요한 자기 자신의 역할을 실행하며 요청된 태스크를 완료하기 위하여 다른 매니지드 오브젝트를 실행합니다.
Managed Objects register themselves as event handlers with a manager in order to get called when outside events occur. One very important type of Managed Object is the Player Managed Object. A Player Managed Object implements the ClientSessionListener interface and is returned to the system as the return value from the loggedIn callback on the AppListener. From then on, it will get called for any incoming data packets and disconnect events from that player.
The Player Managed Object acts as a proxy for the player in the world of Managed Objects. The player sends data packets to the server using the PDS Client API. This causes a userDataReceived event in the system, which results in a task that calls the Player Managed Object's userDataReceived method. The Player Managed Object should parse the packet to find out what it is supposed to do, and then act on itself and other Managed Objects in order to accomplish the requested task.

Figure 2) 두 게임 플레이어가 PDS에 클라이언트로 연결되는 간단한 매니지드 오브젝트 구조
Figure 2 shows our simple Managed Object world, with two players connected to the PDS as clients.

Figure 2: Client connections to the simple ManagedObject world

플레이어 매니지드 오브젝트는 룸 매니지드 오브젝트를 포인팅하는 매니지드 레퍼런스인 "current room" 필드를 갖고 있습니다. 룸 매니지드 오브젝트는 매니지드 레퍼런스의 리스트인 인벤토리 리스트를 갖고 있습니다. 현재, 이 리스트에는 세 아이템들이 있습니다. 두 게임 플레이어와 한 개의 칼입니다. 이들 각각은 매니지드 오브젝트를 표현하고 있습니다(Player 1 Managed Object, Player 2 Managed Object, and Sword Managed Object).
The Player Managed Objects have a “current room” field, which is a Managed Reference that points to the Room Managed Object. The Room Managed Object has an inventory list, which is a list of Managed References. Currently, there are three items in the list: the two players and a sword. Each is represented by a Managed Object (Player 1 Managed Object, Player 2 Managed Object, and Sword Managed Object).

The AppListener
룸 매니지드 오브젝트의 매니지드 오브젝트를 구성하는 월드 상위에, 소드 매니지드 오브젝트와 한 상의 플레이어 매니지드 오브젝트가 있습니다. 그러나 PDS에서 처음 게임을 시작할 때 매니지드 오브젝트의 월드는 이와같지 않습니다. 사실 그것은 다음과 같습니다.
Above we had a world of Managed Objects consisting of a Room Managed Object, a Sword Managed Object and a couple of Player Managed Objects. However, when we start the game in the PDS for the first time, the world of Managed Objects doesn't look like that. In fact it looks like this:

말하고자 하는 것은, 그렇습니다. 텅 비어 있다는 겁니다.
첫번째로 오브젝트 스토어에 있는 매니지드 오브잭트가 얼마나 되겠습니까?
그 때답은 AppListener라는 특별한 매니지드 오브젝트 하나입니다. AppListener를 특별하게 하는 두가지가 있습니다.
  • AppListener 인터페이스를 상속하면 다음 두 메소드를 정의하여야 합니다.
    • initialize
    • loggedIn
  • 해당 어플리케이션의 AppListener에 상세히 기술되었습니다.

Which is to say, it is empty.
How then do the Managed Objects get into the Object Store in the first place?
The answer is a special Managed Object called the AppListener. There are two special things about the class that defines the AppListener:
  • It implements the AppListener interface. This interface defines two methods:
    • initialize
    • loggedIn
  • It has been specified as the AppListener class for this application.
다음 방법을 따라 두가지 속성을 결합합니다.
These two properties combine in the following way:
  • PDS가 부팅될 때(또는 PDS로 새로운 어플리케이션이 인스톨될 때), PDS는 오브젝트 스토어에 해당 어플리케이션에 동작하는 AppListener를 위치시킬 것입니다.
Upon the boot of the PDS (or the installation of a new application into the PDS), the PDS attempts to locate the AppListener for that application in the Object Store.
  • 어플리케이션이 이전에 부팅된 적이 없다면, 이 어플리케이션의 오브젝트 스토어는 공백상태가 될 것이며 PDS는 AppListener의 동작에 실패할 것입니다. 이러한 이유로 어플리케이션은 매니지드 오브젝트인 AppListener를 생성해야 하며 초기화를 호출하는 태스크를 시작하여야 합니다.
If the application has never been booted before, then its Object Store in the PDS is blank (as in Figure 3), and the PDS will fail to find the AppListener. In that case, it creates the AppListener Managed Object itself, and then starts a task that calls initialize.
  • 반대로, 어플리케이션이 적어도 한번 이상 부팅된 적이 있다면, 오브젝트 스토어는 이미 메니지드 오브젝트인 AppListener는 오브젝트 스토어내에 포함되어 있을 것입니다. 따라서, 시스템이 다운되었을 때, 새로운 연결이 시도되었을 때, 이전에 동작했었던 어떤 주기적인 태스크를 동작시키고자 할 때의 실행은 그냥 재시작만 하면 됩니다.
If, on the other hand, the application has been booted at least once, the Object Store will contain the AppListener Managed Object already. In this case, execution just resumes from where it left off when the system came down, listening for new connections and executing any periodic tasks that were running before.
다음 데모 어플리케이션의 의사코드에서 이와 같은 점을 볼 수 있습니다.
In the case of our little demo application, the boot method will have a block in it that, in pseudo-code, looks something like this:

initialize {

CREATE ROOM MANAGED OBJECT

CREATE SWORD MANAGED OBJECT

ADD REF TO SWORD MANAGED OBJECT TO ROOM'S INVENTORY

SAVE A MANAGED OBJECT REF TO ROOM FOR LATER

}

일반적으로, 처음 실행되는 동안의 매니지드 오브젝트의 초기화를 담당하는 것이 AppListener입니다.
In general, it is the responsibility of the AppListener to create the initial world of Managed Objects during the first startup.

Figure 4: AppListener ManagedObject creates initial ManagedObject world


이제 게임과 같은 윤곽이 드러났습니다. 그렇지만 아직 플레이어 매니지드 오브젝트는 없습니다. 여러분은 AppListener에서와 같은 방법으로 사용자 접속과 같은 플레이어 매니지드 오브젝트를 만들 것입니다. 보통 처음엔 사용자 로그인이 나타납니다. 그리고 그 사용자에 대한 플레이어 오브젝트를 생성시킵니다. 이후 매번 사용자가 로그인할 때 마다 존재하고 있는 해당 플레이어 매니지드 오브젝트를 연결하기만 하면 됩니다. 그러므로 시스템은 필요할 때 플레이어 매니지드 오브젝트를 생성하고 사용자 정보를 기억하고 있습니다.
그렇다면 사용자가 로그인 할 때 어떤 작업을 수행하여야 하겠습니까?
AppListener:loggIn 콜백이 그 해답입니다. 매번 사용자가 PDS 어플리케이션에 로그인할 때, 그 어플리케이션의 AppListener의 loggIn 메소드를 호출하는 태스크가 시작됩니다. AppListener의 loggIn 콜백이 호출된다면 다음 의사코드와 같이 기술된 코드에 따라 실행됩니다.
Now we have something that is beginning to look like our game. We still don't have Player Managed Objects, however. We will create the Player Managed Objects as users join, in much the same way the AppListener Managed Object was created. The first time we see a user log in, we create a new Player Managed Object for that user. After that, every time that user logs in, we just reconnect him to his existing Player Managed Object. Thus, the system creates Player Managed Objects as needed, and remembers user information between logins.
So how do we find out when a user has logged in?
The answer is the second callback on our AppListener: loggedIn. Every time a user logs into a PDS application, a task is started that calls the loggedIn method on the application's AppListener. When the loggedIn callback is called on our AppListener, it executes the following code, presented as pseudocode.

 
loggedIn {
managedObject_name = “player_”+ SESSION.PLAYER_NAME;
IF MANAGED OBJECT EXISTS(managedObject_name){
FIND MANAGED OBJECT(managedObject_name);
} ELSE {
CREATE NAMED PLAYER MANAGED OBJECT(managedObject_name);
}
SET currentRoom on PLAYER MANAGED OBJECT
TO SAVED MANAGED OBJECT REF TO ROOM
GET ROOM MANAGED OBJECT
ADD PLAYER REF TO ROOM MANAGED OBJECT'S PLAYERS LIST
REGISTER PLAYER MANAGED OBJECT AS SessionListener(SESSION);
}

SessionListener는 또다른 이벤트 인터페이스입니다. 이는 서버로 데이터를 보내는 프로세스나 사용자의 로그아웃과 같은 client API를 사용하는 클라이언트의 액션에 대해 응답하는 태스크상의 get 메소드의 호출되는 메소드를 정의합니다.
SessionListener is another event interface. It defines methods that get called on tasks to respond to actions the client takes with the client API, such as the client sending data to the server for processing and the client logging out.
Figure 5) 매니지드 오브젝트들이 보여지기 시작되는 개요

Figure 5 illustrates that our Managed Object world is starting to look the way we want it to.




Figure 5: Client sends data to server

두번째 사용자가 로그인 할 때 원래 월드가 이전에 있다는 것을 볼 수 있습니다.
When a second user logs in, we will be back to our original world.
Figure 6) 이전 사용자를 포함한 게임상의 재시작 이후의 모형

Figure 6 illustrates our world after restarting the game with our previous players:


Figure 6: AppListener ManagedObject reestablishes simple world

지금까지 의사코드로만 로직이 보여졌습니다. 실행 가능한 코드는 부록A SwordWorld 어플리케이션과 같이 수록되어 있습니다. 실행 가능한 어플리케이션 코드는 간단한 명령어의 구현보다 이상의 것이며 사용자로부터 보내지는 명령들을 어떻게 핸들링해야 하는지를 보여줄 것입니다.
So far, the logic has been laid out in pseudo-code. The actual code to implement this application is included in Appendix A as the SwordWorld application. The actual application code goes a bit further in that it also implements a look command, to show you how the Player Managed Object actually handles commands being sent from the client.


서버 API 클래스 위치시키기 (Locating the Server API Classes)
모든 PDS API 클래스는 com.sun.sgs.app.* 패키지에 포함되어 있습니다.
다음은 Project Darkstar Server API 클래스 상세입니다.
All the Project Darkstar Server API classes are in the com.sun.sgs.app.* package.
These are the Project Darkstar Server API classes with brief descriptions:

System Classes and Interfaces
Class
Description
AppContext
해당 어플리케이션에서 가용한 자원 접근의 편의성을 제공합니다. 최초에 레퍼런스를 찾기 위해 매니저가 사용합니다. 이는 시스템에 어플리케이션의 코드를 실행하는 시작점입니다.
Provides access to facilities available in the current application. Primarily used to find references to managers. This is the starting point for the application code to talk to the system.
AppListener
어플리케이션 레벨의 이벤트를 위한 리스너를 제공하는 인터페이스입니다. 이 리스너는 어플리케이션이 최초 실행될 때 그리고 사용자 세션이 로그인 될 때 호출됩니다.
Interface representing a listener for application-level events. This listener is called when the application is started for the first time, and when client sessions log in.
ManagerNotFoundException
요청된 매니저를 찾을 수 없을 때 발생합니다.
Thrown when a requested manager is not found.
ClientSession
사용자와 서버간의 로그인 세션을 위해 하나씩 제공되는 인터페이스입니다.
Interface representing a single, connected login session between a client and the server.
ClientSessionListener
서버에 참여한 클라이언트 세션으로부터 보내지는 메시지를 처리하기 위한 리스너입니다.
Listener for messages sent from an associated client session to the server.


Task Manager Classes and Interfaces
Class
Description
TaskManager
스케쥴된 태스크들의 편의성을 제공합니다.
Provides facilities for scheduling tasks.
Task
태스크 매니저에 의해 실행되는 어플리케이션의 동작의 정의입니다.
Defines an application operation that will be run by the Task Manager.
PeriodicTaskHandle
주기적으로 실행되는 태스크 매니저의 태스크 스케줄에 대한 관리를 용이하게 합니다.
Provides facilities for managing a task scheduled with the Task Manager to run periodically.
TaskRejectedException
자원의 제한으로 요청한 태스크를 거부하여 실패된 작업을 스케쥴링할 때 발생합니다.
Thrown when an attempt to schedule a task fails because the Task Manager refuses to accept the task due to resource limitations.
ExceptionRetryStatus
재요청이 필요한 예외가 발생할 때의 동작이 필요한 클래스의 예외 클래스의 구현입니다.
Implemented by exception classes that want to control whether an operation that throws an exception of that exception should be retried.


Data Manager Classes and Interfaces
Class
Description
DataManager
공유/퍼시스턴스 오브젝트 접근 관리의 편의성을 제공합니다.
Provides facilities for managing access to shared,persistent objects.
ManagedObject
데이터 매니저에 의해 관리되는 공유/퍼스시턴스 오브젝트와 같은 인터페이스의 구현체입니다.
A marker interface implemented by shared, persistent objects managed by the Data Manager.
ManagedReference
매니지드 오브젝트의 레퍼런스를 제공합니다.
Represents a reference to a managed object.
ObjectIOException
매니지드 오브젝트를 접근시 I/O 오류가 있을 때 발생합니다.
Thrown when an operation fails because of an I/O failure when attempting to access a Managed Object.
ObjectNotFoundException매니지드 오브젝트를 접근할 때 그 오브젝트를 찾을 수 없을 때 발생합니다.
Thrown when an operation fails because it attempted to refer to a Managed Object that was not found.
TransactionAbortedException
어떤 동작에서 그 때의 트랜션이 시스템에 의해 중지되었을 때 발생합니다.
Thrown when an operation fails because the system aborted the current transaction during the operation.
TransactionConflictException
어떠한 동작이 다른 트랜잭션과 충돌이 일어나서 시스템이 해당 유효한 트랜잭션을 중지시켰을 때 발생합니다.
Thrown when an operation fails because the system aborted the current transaction when it detected a conflict with another transaction.
TransactionException
유효한 트랜잭션이 오류를 일으켰을 때 발생합니다.
Thrown when an operation fails because of a problem with the current transaction.
TransactionNotActiveException
유효하지는 않지만 동작하고 있는 트랜잭션이 오류를 일으켰을 때 발생합니다.
Thrown when an operation fails because there is no current, active transaction.
TransactionTimeoutException
허용된 최대의 트랜잭션 숫자를 넘어서 유효한 동작을 시스템이 중지시켰을 때 발생합니다.
Thrown when an operation fails because the system aborted the current transaction when it exceeded the maximum permitted duration.
NameNotBoundException
바운드되지 않은 오브젝트를 참조하였을 때 발생합니다.
Thrown when an operation fails because it referred to a name that was not bound to an object.



Channel Manager Classes and Interfaces
Class
Description
ChannelManager
채널을 획득하거나 생성하기 위한 매니저입니다.
Manager for creating and obtaining channels.
Channel
다중 사용자 세션과 서버간에 존재하는 통신그룹을 제공하는 인터페이스입니다.
Interface representing a communication group, a channel consisting of multiple client sessions and the server.
ChannelListener
한 채널은 ChannelListener에 의해 생성될 수 있으며, ChannelListener는 어떠한 사용자 세션에서 한 채널로 메시지를 보냈을 때 이를 통지합니다. 추가적으로 서버는 한 세션당 한 리스너를 지정할 수 있습니다.(채널에 사용자 세션이 참가하였을 때 채널상의 각 사용자 세션에 의해 보내지는 메제지를 통지할 수도 있습니다.)
A channel can be created with a ChannelListener, which is notified when any client session sends a message on that channel. Additionally, a server can specify a per-session listener (to be notified when messages are sent by an individual client session on a channel when joining a client session to a channel).
Delivery
메세지 전달 요청을 제공합니다.  하나의 전달요청은 하나의 채널을 생성합니다.
Representation for message delivery requirements. A channel is created with a delivery requirement.
NameExistsException
바운드되지 않은 오브젝트를 참조하였을 때 발생합니다.
Thrown when an operation fails because it referred to a name that is currently bound to an object.
NameNotBoundException
ChannelManager.getChannel()로 채널의 이름을 지정할 때 채널이 바운드되지 않는다면 NameNotBoundException이 발생합니다.
Thrown if a channel is not bound to a name specified to ChannelManager.getChannel().





Lesson One: Hello World!
전통적으로 어떠한 튜토리얼이던지 첫번째 예제는 콘솔에 "HelloWorld"를 출력하는 간단한 프로그램입니다. 이는 아무리 간단한 어플리케이션이라도 어플리케이션 로직의 진짜 세상에 도달하기 전 요구되는 추측들을 예상할 수 있게 합니다. 간단하게 시작하기 위하여, 이번 레슨과 다음 두 레슨은 client-server 네트웍을 시작하는 것 보다느 그냥 서버 콘촐에 출력하는 방법을 따릅니다. 클라이언트가 접속되어 있다 하여도, 서버측 로그 메세지는 어플리케이션을 모니터링하는 것과 디버깅에 매우 중요합니다.
It is traditional in any programming tutorial for the first example to be a simple program that prints “HelloWorld” to the console. This lets a programmer see the plumbing required to start even a basic application before diving into real-world application logic. For simplicity, this lesson and the two that follow it print their output on the server console, rather than starting
off with client-server networking. Even when a client is connected, server-side log messages are invaluable for debugging and monitoring the application.

HelloWorld 코딩하기 (Coding HelloWorld)
모든 PDS 어플리케이션은 AppListener와 함께 시작합니다. AppListener는 어플리케이션의 시작과 사용자 로그인 이벤트를 핸들링하는 객체입니다. 어플리케이션의 AppListener는 AppListener 인터페이스의 간단한 구현 클래스입니다. AppListener 또한 매니지드 오브젝트이지만 AppListener는 마커 인터페이스인 Serializable 인터페이스를 반드시 상속하여야 합니다.
위에 언급한 내용에 따르면, AppListener는 두 메소드를 포함합니다. initialize와 loggedIn 입니다. 어플리케이션의 오브젝트 스토어가 비어있든 그렇지 않든 initialize 메소드는 어플리케이션이 처음 시작할 때 호출됩니다. 어플리케이션의 오브젝트 스토어가 삭제되거나 시스템이 깨끗한 "전혀 실행된 적이 없는" 상태가 아닌 이상, 관례적으로 이 뜻은 어플리케이션 하나당 한번 생성된다는 뜻입니다.
HelloWorld AppListener는 다음과 같습니다.
All PDS applications start with an AppListener. An AppListener is the object that handles an application's startup and client login events. An application's AppListener is simply a class that implements the AppListener interface. Since an AppListener is also a Managed Object, it must implement the Serializable marker interface as well.
As mentioned above, AppListener contains two methods: initialize and loggedIn. The initialize method gets called on the startup of the application if and only if the Object Store for this application is empty. The AppListener is automatically created in the Object Store by the system the first time the application is started up; in practice, this means that it is created once per application, unless the Object Store for this application is deleted and the system is returned to its pristine “never having run this application” state. 6
A “Hello World” AppListener looks like this:

HelloWorld
/*
* Copyright 2007 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 3 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sun.sgs.tutorial.server.lesson1;
import java.io.Serializable;
import java.util.Properties;
import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
/**
* Hello World example for the Project Darkstar Server.
* Prints {@code "Hello World!"} to the console the first time it's
* started.
*/
public class HelloWorld implements AppListener, // to get called during application startup.
                                              Serializable // since all AppListeners are ManagedObjects.
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;

/**
* {@inheritDoc}
* <p>
* Prints our well-known greeting during application startup.
*/
public void initialize(Properties props) {
System.out.println("Hello World!");
}

/**
* {@inheritDoc}
* <p>
* Prevents client logins by returning {@code null}.
*/
public ClientSessionListener loggedIn(ClientSession session) {
return null;
}
}


HelloWorld 실행하기 (Running HelloWorld)
HelloWorld를 실행하려면 다음과 같은 것이 필요합니다.
To run HelloWorld you need the following:
  • 여러분의 시스템에 Project Darkstar Server가 설치되어야 합니다.
  • The Project Darkstar Server installed on your system.
  • 1.5.0_11 이상의 JDK™ 5 가 설치되어야 합니다. 설치된 버전은 다음 명령으로 확인할 수 있습니다.
  • A JDK™ 5 installation, version 1.5.0_11 or better. The version can be found with:
java -version
path에 자바가 걸려있지 않다면 JAVA_HOME에 자바가 설치된 경로의 root를 설정하여야 합니다.
If java is not in your default execution path, you will need to set JAVA_HOME to point to the root of its installation on your system.

Path 규칙(Path Conventions)
Unix 또는 Unix와 같은 시스템은 디렉토리의 경로를 슬래쉬(/)로 구분합니다. 그러나 Win32에서는 역슬래쉬(\)로 표기합니다. 이 문서는 특별히 윈도우즈의 표기법을 설명하지 않는 한 Unix의 표기법을 따릅니다.
여러분이 윈도우즈에서 작업한다면 슬래쉬로 표기된 경로를 바꾸어야 한다는 것을 기억하십시오.
Unix and many Unix-derived systems use a forward slash (/) to show subdirectories in a file path. Win32, however, uses a backslash (\) for this purpose. Throughout this document we use the Unix convention for file paths unless it is in a Windows-specific example.
Please remember that you may have to substitute backslashes for forward slashes in the generic examples if you are working in Windows.

튜토리얼의 폴더를 보시면 컴파일된 tutorial.jar 파일에서 모든 튜토리얼의 예제를 찾을 수 있습니다. 모든 각각의 예제는 오브젝트 스토어의 데이터를 포함한 데이터 서브디렉토리가 포함되어 있습니다.
The tutorial.jar file in the tutorial folder contains pre-compiled .class files for all the tutorial examples. The data directory contains subdirectories for the object store data for all the different examples.

여러분의 컴퓨터에 PDS가 인스톨되어 있는 루트 디록토리에서 sgs 스크립트를 실행하여 튜토리얼 예제를 실행할 수 있습니다.
You run a tutorial example by using the sgs script in the root of the PDS installation on your computer. It has the following form:
  • For Unix:
sgs.sh app_classpath app_config_file …
  • For Windows:
sgs app_classpath app_config_file …
app_classpath는 어플리케이션 클래스들이 위치하고 있는 디폴트 경로이고, app_config_file은 실행하고자 하는 각 어플리케이션의 설정파일입니다.(이 튜토리얼에서는 일반적으로 한번에 한번 실행할 것입니다.) 설정된 경로에 기본 설정 파일이 제공되어 있을 것입니다. 해당 경로는 SDK가 설치된 경로의 tutorial 폴더에 있습니다. 이제 설정된 경로에서 HelloWorld를 실행하기위해 다음을 따르십시오.
Where app_classpath is a default classpath in which to find the application classes, and app_config_file … are configuration files for each application you want to launch. (In this tutorial you will generally only launch one at a time.)
We have provided default configuration files that use relative paths. These paths assume your working directory is the tutorial folder in the SDK. So, to run HelloWorld as it is shipped to you in the tutorial directories, do the following:
  1. PDS가 설치되어 있는 경로로 SGSHOME 환경변수를 추가합니다. Add the environment variable SGSHOME to your environment and set it to the directory where you installed the Project Darkstar Server.
  2. SGSHOME 환경변수를 path에 추가합니다. Add SGSHOME to your execution path, where SGSHOME is your PDS install directory.
  3. 쉘을 엽니다. Open a Unix shell, a Windows command window, or whatever you do to get a command line on your development system.
  4. 튜토리얼 디렉토리로 이동합니다. 유닉스 환경이라면 다음과 같이 명령을 낼 것입니다. Change your working directory to the tutorial directory of your PDS. In Unix, the command might be something like this:
cd ~/sgs/tutorial
  1. 다음을 타이핑 하십시오. Type the following:
  • For Unix:
sgs.sh tutorial.jar HelloWorld.properties
  • For Windows:
sgs tutorial.jar HelloWorld.properties
  1. Add the environment variable SGSHOME to your environment and
    set it to the directory where you installed the Project Darkstar Server.
  2. Add SGSHOME to your execution path, where SGSHOME is your PDS install directory.
  3. Open a Unix shell, a Windows command window, or whatever you do to get a command line on your development system.
  4. Change your working directory to the tutorial directory of your PDS. In Unix, the command might be something like this:

cd ~/sgs/tutorial


  1. Type the following:

  • For Unix:
sgs.sh tutorial.jar HelloWorld.properties
  • For Windows:
sgs tutorial.jar HelloWorld.properties
여러분은 "HelloWorld!"라는 출력을 표준출력으로 볼 수 있을 것이고 이후는 동작하지 않을  것입니다. 이 시점에서 여러분은 어플리케이션을 강제종료할 수 있읍니다. 유닉스 또는 윈도우즈 환경에서 Ctrl-c를 눌러 서버를 종료하고 프롬프트로 돌아갈 수 있습니다. 구미가 당긴다면 PDS 어플리케이션의 시작 스크립트인 sgs.bat 또는 sgs.sh를 시험해볼 수 있고, PDS에서 어플리케이션 설정이 어떻게 되어 있는지에 대한 상세한 내용을 HelloWorld.properties 파일을 통하여 볼 수 있습니다.
You should see the application print out “Hello World!” to standard output (as well as a couple of PDS startup log messages) and then sit doing nothing. At this point you can kill the application; in Unix or Win32, just type Ctrl-c in the shell window to stop the server and get the prompt back. If you are interested, you can examine the script files sgs.bat and sgs.sh to see how to run PDS applications, and the HelloWorld.properties file to see the details of how you set up an application configuration to run in the PDS.

(역자주:PDS 어플리케이션이 동작하고 개발환경을 설정하기 위해, 또 PDS 소스로부터 sgs.jar로 컴파일하려고 한동안 삽질했었습니다. 또 다른 뉴비가 삽질하지 않도록 실행, 디버그 환경을 설정하는 방법을 남깁니다.)

개발 OS는 Windows XP이고, JDK 1.6, eclipse 3.2 입니다.

  1. CLASSPATH가 제대로 되어 있는지 확인하세요.
  2. eclipse가 제대로 구동되는지 확인하세요.
  3. Project Darkstar Server 바이너리(sgs-0.9.5.1-r3730.zip)를 다운로드하고 압축을 풀면 다음과 같은 디렉토리를 볼 수 있습니다.
    1. bdb-4.5.20

    2. mina-1.1
    3. sgs-0.9.5.1-r3730
    4. slf4j-1.4.0
  4. sgs-0.9.5.1-r3730 디렉토리를 eclipse의 workplace에 복사하고 darkstar라고 디렉토리명을 바꾸세요.

  5. bdb-4.5.20 디렉토리의 이름을 bdb로 변경하고 darkstar\lib에 bdb 디렉토리를 통채로 복사하세요. 그러면 darkstar\lib\bdbdb.jar, darkstar\lib\bdb\win32-x86 와 같이 디렉토리, 파일이 설정될 겁니다.
  6. 환경변수에 SGSHOME을 설정하세요, 제 경우는 D:\src\workplace\darkstar 이렇게 되었습니다.
  7. PATH에도 SGSHOME을 추가해주세요.
  8. eclipse를 실행하시고 새 프로젝트를 만듭니다. 이클립스의 메뉴에 File -> New -> Java Project 를 선택하시면 됩니다.
  9. Content 박스에 Create project from existing source를 선택하시고, Browse 버튼을 클릭하여 SGSHOME로 설정한 PDS를 설치한 경로를 선택하세요.
  10. Project Name은 반드시 darkstar가 되어야 할 겁니다. 그렇게하고 Finish 버튼을 눌러 프로젝트를 만들면 알아서 lib등의 경로를 검색하여 프로젝트를 구성할 겁니다.
  11. SGSHOME 디렉토리에 새 파일을 만들어 이름을 Main.launch라고 지정합니다.
  12. Main.luanch을 텍스트 에디터로 열어 http://www.projectdarkstar.com/wiki/doku.php/darkstar.launch 의 내용을 기록하고 저장하세요.
  13. 이클립스의 Package Editor를 리프레쉬(F5)하면 Main.luanch 파일이 추가되어 있을 것이고 Run 버튼에 Main 메뉴가 추가되었을 것입니다.

  14. Run 버튼을 누르면 파일을 선택할 다이얼로그가 보일 것인데, *.properties 파일을 선택하시면 PDS가 실행될 것입니다.
    1. 여기서 잠깐! .properties 파일을 열어보면 com.sun.sgs.app.root=data/HelloWorld 라고 적혀 있는것을 볼 수 있습니다.
    2. SGSHOME은 D:\src\workplace\darkstar 이라고 설정되었을때 어플리케이션의 루트(com.sun.sgs.app.root)는 D:\src\workplace\darkstar이고 튜토리얼의 .properties에 기술된 대로 실행된다면 D:\src\workplace\darkstar\data\HelloWorld에서 어플리케이션을 실행하려고 하여 에러가 뜹니다.
    3. 정상적으로 실행하려면 .properties의 com.sun.sgs.app.root=data/HelloWorld를 com.sun.sgs.app.root=tutorial/data/HelloWorld로 바꾸시거나 tutorial의 모든 파일과 디렉토리를 SGSHOME에 복사하셔야 합니다. 전 전자를 택했습니다.








HelloWorld 재시작하기 (Rerunning HelloWorld)
PDS를 중지하고 HelloWorld 어플리케이션을 다시 동작시키려 한다면, "HelloWorld!" 출력이 나오질 않고 에러를 보게 될 것입니다. 이는 오브젝트 스토어에 이전에 동작하고 있던 AppListener가 이미 존재하므로 호출되지 않았던 초기화를 하여야 합니다.
HelloWorld를 다시 동작하고 싶다면, 여러분은 오브젝트 스토어의 내용을 지워야 합니다
  • For Unix:

rm -r data/HelloWorld/dsdb/*


  • For Windows:

del /s data\HelloWorld\dsdb\*.*

(역자주 : 저도 지우려고 했는데 잘 지워지지 않더군요. JVM을 Kill해도 dll로 호출된(bds)것들이 아직 메모리상에서 동작하기 때문에 종료할 때 까지 기다려줘야 합니다. Darkstar 포럼에서는 10초 이후 파일을 지울 수 있다고 합니다만, PDS 소스를 확인해보는 수 밖에 없습니다.)
If you stop the PDS and then run the HelloWorld application again, you will notice that you don't get a “HelloWorld!” output the second time. This is because the AppListener already exists in the Object Store from the previous run, and thus the initialize method on it is never called.
If you want to see “Hello World” again, you can do it by clearing the Object Store with the following commands:
  • For Unix:
rm -r data/HelloWorld/dsdb/*
  • For Windows:
del /s data\HelloWorld\dsdb\*.*



Lesson Two: Hello Logger!

HelloLogger 코딩하기 (Coding HelloLogger)
PDS는 java.util.logging.* 패키지의 자바 로깅 메커니즘 표준을 지원합니다. 이는 유연하고 설정하기 쉬운 로깅 API이며 자바로 작성된 대부분의 서버에서 사용됩니다. PDS 서버는 다양한 내부 상태와 이벤트를 기록하기 위하여 스스로 로거를 사용합니다.
아래에 표준출력 보다는 로거로 "Hello World!"를 보내는 HelloWorld 프로그램이 재작성되어 있습니다. 
The PDS supports the standard Java logging mechanisms in the java.util.logging.* package. This is a flexible and configurable logging API used by most servers written in Java. The PDS server itself uses the logger to report various internal states and events. It is highly recommended that applications use the same logging mechanisms for their reporting.
Below is a rewrite of HelloWorld that sends the “Hello World!” string to the logger rather than to standard out:

HelloLogger

/*
* Copyright 2007-2008 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sun.sgs.tutorial.server.lesson2;
import java.io.Serializable;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
/**
* Hello World with Logging example for the Project Darkstar Server.
* It logs {@code "Hello World!"} at level {@link Level#INFO INFO}
* when first started.
*/
public class HelloLogger
implements AppListener, // to get called during application startup.
Serializable // since all AppListeners are ManagedObjects.
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
/** The {@link Logger} for this class. */
private static final Logger logger = Logger.getLogger(HelloLogger.class.getName());
/**
* {@inheritDoc}
* <p>
* Logs our well-known greeting during application startup.
*/
public void initialize(Properties props) {
logger.log(Level.INFO, "Hello World!");
}
/**
* {@inheritDoc}
* <p>
* Prevents client logins by returning {@code null}.
*/
public ClientSessionListener loggedIn(ClientSession session) {
return null;
}
}


로깅 속성 파일(The Logging Properties File)
자바 로깅 API는 엄격한 메세지 레벨로 고려되어 있습니다. 우리가 로깅하려는 엄격한 info level에서의 메세지 시스템이 Level.INFO에 설명됩니다.
로깅 속성 파일에 의해 자바 로거가 동작합니다. 여러분이 어플리ㅔ이션을 동작하시키기 위하여 사용하는 sgs-boot.jar 파일은 PDS가 설치된 conf 디렉토리상에 제공되는 sgs-logging.properties 파일을 사용합니다. 이는 PDS 프로세스의 자바 커맨드 라인의 java.util.logging.config.file의 속성을 셋팅하여 완성됩니다.
이 파일의 디폴트 레벨은 info입니다. 이는 "fine"과 같은 디버깅 메세지 등의 info 레벨의 아래에 있는 로깅 메세지는 출력되지 않다는 것을 뜻합니다. 이 파일을 설정하는 더 자세한 설명은 JDK™ 6 API documentation을 참조하시기 바랍니다.

The Logging Properties File
The Java logging API has a concept of message severity level. By logging at Level.INFO, we are telling the system we want to log this message at the info level of severity.
The Java logger's behavior is controlled by a logging properties file. The sgs-boot.jar file you use to run your applications uses the file sgs-logging.properties that is present in the conf directory of your PDS installation.
This is accomplished by setting the java.util.logging.config.file property on the java command line of the PDS process.
By default this file sets the logging level to info. This means that logging messages below the level of info, such as “fine” debugging messages, will not be printed. You can change this by editing the sgs-logging.properties file. For more information on how to edit this file, please see the JDK™ 6 API documentation.


Lesson 3: Tasks, Managers, and Hello Timer!

태스크(Tasks)
Lesson 1, 2에서 PDS 시스템은 이벤트의 응답(이 케이스에서는 새로 설치된 PDS 어플리케이션의 초기화 동작)으로 초기화 메소드를 자동으로 호출하였습니다. 이는 PDS 환경과 함께 실행되는 것을 뜻합니다.
PDS 환경에서 모든 코드의 실행은 태스크의 한 부분이 되어야 합니다. 어플리케이션 개발자 관점에서 보면, 태스크는 단일 스레드이고, 트랜잭션으로 동작하며 이벤트 모델의 코드입니다. 이는 태스가 단지 한순간 실행되는 태스크라는 것과 전체가 아니면 아무것도 실행되지 않는 것과 같이 데이터의 변경이 종료되는 것을 뜻합니다.
In Lessons 1 and 2, the system automatically invoked the initialize method for us in response to an event (in this case, the initial run of a newly installed PDS application). This meant executing code within the PDS environment.
All code run in the PDS environment must be part of a task. From the point of view of the application coder, a task is a piece of monothreaded, transactional, event-driven code. This means that the task runs as if it were the only task executing at that moment, and all actions done by the task to change data occur in an all-or-nothing manner.

태스크 실행의 실체(The realities of task execution)
각각의 태스크는 단일 스레드로 실행됩니다. 그러나 우리가 차례대로 실행하지 않는 다면, 한 태스크가 각각의 태스크를 이전에 끝나는 것과 다음 시작하는 것을 기다릴때, 그 태스크는 PDS 제공자를 조정되지 못할 것입니다.
그 대신에 PDS는 수많은 단일 스레드 태스크를 동시에 실행하고 각 매니지드 오브젝트의 경쟁 상태를 주시합니다. 두 매니지드 오브젝트가 경쟁하고 있다면, 한 태스크는 멈추게 되고 다른 태스크가 끝나기를 기다릴 것입니다.
많은 태스크가 경쟁상태가 아닌 상태로 같은 시간에 같은 매니지드 오브젝트로 읽혀질 수 있습니다. 만약 어떤 태스크가 매니지드 오브젝트로 사용되어야 한다면 경쟁상태가 되더라도 같은 매니지드 오브젝트로 읽혀지거나 사용됩니다.
최적화된 성능을 위해, 데이터 구조와 가능한한 잠재적 오프젝트 경쟁을 포함한 게임 로직을 디자인 하는 것은 중요합니다. 동시에 발생하는 그리고 똑같은 매니지드 오브젝트로 쓰여져야만 할 지도 모르는 다중 태스크의 공간을 효율적으로 또 세심하게 설계하시기 바랍니다. 모든 태스크는 run 메소드를 갖고 있는 Task 인터페이스를 PDS 스캐쥴러에 등록됩니다.
태스크는 가능한한 빨리 또는 최소 지연시간 이후에 실행되기 위하여 스캐줄러에 등록됩니다. 태스크는 한번 실행되고 끝나거나 반복될 수 있습니다. 반복되는 태스크라면 반복 주기가 설정되어 있을 것입니다.
반복되는 태스크는 전통적인 게임 시스템에서의 "timer" 또는 "heartbeat"와 같습니다. 이는 여러분이 밀리세컨드 단위로 설정된 이벤트를 효율적으로 생성할 수 있게 합니다.
Each individual task executes in a monothreaded manner. However, if we executed them serially,
waiting for each one to finish before the next one started, it would not be possible to get the kind of scaling the PDS provides.
Instead, the PDS executes many of these monothreaded tasks simultaneously and watches for
contention on the individual Managed Objects. If two tasks contend for control over a Managed
Object, one task will be held up and will wait for the other to finish before it can proceed.
Many tasks can read the state of the same Managed Object at the same time without causing
contention. If any of them wants to write to it, however, that can cause contention with tasks that read from or write to the same Managed Object.
To achieve optimal performance, it is important to design your data structures and game logic
with as little potential object-contention as possible. Be especially wary of places where multiple tasks that are likely to occur simultaneously might have to write to the same Managed Object.
All tasks registered with the PDS scheduler implement the interface Task, which has one method on it . run.6
A task may be submitted to the scheduler to be executed either as soon as possible, or after a minimum delay time. A task can be one-shot or repeating. If it is a repeating task, it is also submitted with a period of repeat.
A repeating task is the same thing as a “timer” or “heartbeat” in traditional game systems; it lets you effectively generate an event to be handled every specified number of milliseconds.

매니저(Managers)
서버 어플리케이션의 게임로직과 외부와의 모든 통신은 매니저를 통하여 완료됩니다. 아래에 세가지 타입의 매니저가 기술되어 있습니다.
● 태스크 매니저 Task Manager
● 데이터 매니저 Data Manager
● 채널 내니저 Channel Manager

인스톨 특화 매니저가 있을 수 있습니다. 이는 다른 서버 어플리케이션의 개발자에 의해 작성되고 PDS의 백엔드로 배포된 것일 수 있습니다. AppContext 클래스상의 고정 호출 흐름은 매니저와 통신하는 서버 어플리케이션 코드에서 사용됩니다. AppListener와 같은 이벤트 핸들링 인터페이스가 태스크를 구현하지 않더라도, 이 리스너는 일반 PDS 어플리케이션 코드와 같이 내부 태스크로 호출 될 수 있습니다.
● getTaskManager()
● getDataManager()
● getChannelManager()
● getManager(managerClass)

이 튜토리얼은 처음의 세가지를 포함합니다. 그 마지막은 인스톨 특화 매니저의 호출이며 이는 다음 버전의 Project Darkstar Server Extension Manual에 포함될 것입니다.
IMPORTANT: 매니저 레퍼런스는 호출된 매니저안의 태스크 전에에 대한 유일한 검증입니다. 그러므로 여러분은 AppContext로부터 호출하는 것 대신에 외부로부터 매니저를 참조하는 것을 시도하지 않는 것이 좋습니다.
태스크 매니저는 스캐줄러를 포함하는 PDS의 한 부분이고 우리가 사용할 스캐쥴 태스크입니다. 실행된 태스크와 같이 태스크 매니저 내에 스캐쥴링 되기 위하여, 오브젝트는 반드시 직렬화되어야 하며 Task 인터페이스를 상속하여야 합니다. 0.5초 주기를 갖고 5초 딜레이 이후 "Hello Timer" 메세지를 로깅하는 AppListener의 예제가 하단에 있습니다.
IMPORTANT: PDS 스택이 스케쥴링에서 태스크를 실행하기 위하여 가장 많은 노력을 기울이는 동안, 과부하가 걸린 태스크가 반복 실행되는 동안 한 태스크가 밀려날 수 있습니다. 이런 상황에서 해당 태스크는 그 기간에 실행을 스킵할 것이고 다음 스캐쥴링에서 실행될 것입니다. 추가로 매니지도 오브젝트의 경쟁은 지연된 태스크의 원인이 될 수도 있습니다.
스캐쥴링된 태스크의 요청된 반복 주기는 복잡한 그래픽일 수록 낮은 수로 실행되는 게임 클라이언트의 목표 프레임율과 비슷합니다. 여러분의 어플리케이션 로직이 일정 시간내에 실행되지 않을 정도로 타이트하다면 여러분은 설정된 시간과 그 실행 로직에서 스킵된 부분을 점검하고 핸들링할 것입니다.
All communication between your server application's game logic and the world outside of it is accomplished through managers. As described above, there are three standard managers:
● Task Manager
● Data Manager
● Channel Manager
There can also be installation-specific managers; these can be written by the author of the server application and deployed with the application into a PDS back end. The following static calls on the AppContext class are used by server application code to get a reference to a manager to talk to:
Although the event-handling interfaces like AppListener do not implement Task, these listeners get called from an internal Task just like regular PDS application code.
● getTaskManager()
● getDataManager()
● getChannelManager()
● getManager(managerClass)
The first three are covered in this tutorial. The last is a generic call to get an installation-specific manager; it will be covered in the forthcoming Project Darkstar Server Extension Manual.
IMPORTANT: A Manager Reference is valid only for the life of the task within which the get manager call was invoked. Therefore, you should not try to cache manager references for use outside of that one invocation chain; get them from the AppContext instead.
The Task Manager is the part of the PDS that contains the scheduler, and thus what we use to schedule tasks. In order to be scheduled with the Task Manager as a task to be run, an object must be serializable and must implement the Task interface.7 The example below turns our AppListener into a task and starts it logging “Hello Timer” messages after a five-second delay at a half-second repeat period.
IMPORTANT: While the PDS stack makes a best effort to run tasks on schedule, it may back off execution of repeating tasks under heavy load. In that case, the task will skip execution of this period and reschedule to the next period. Additionally, contention for Managed Objects may cause a timed task to delay its execution.
The requested repeat frequency of a timed task is similar to a target frame rate in a game client, where frames may be dropped if they are taking too long to compute. If your application logic is tied to elapsed time or absolute number of “beats” in a given period of time, you'll need to check the elapsed time and handle skipped periods in your run logic.

HelloTimer 코딩하기(Coding HelloTimer)
아래의 HelloTimer 어플리케이션은 반복되는 태스크를 스케쥴링하기 위하여 TaskManager를 사용합니다. 해당 태스크는 500ms의 주기로 5000ms 지연 이후에 실행될 것입니다.
The HelloTimer application below uses the TaskManager to schedule a repeating task. The task will run after a delay of 5000ms at a frequency of once every 500ms.

HelloTimer
/*
* Copyright 2007-2008 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sun.sgs.tutorial.server.lesson3;

import java.io.Serializable;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
import com.sun.sgs.app.Task;
import com.sun.sgs.app.TaskManager;

/**
* A simple timed-task example for the Project Darkstar Server.
* It uses the {@link TaskManager} to schedule itself as a periodic task
* that logs the current timestamp on each execution.
*/

public class HelloTimer
implements AppListener, // to get called during application startup.
Serializable, // since all AppListeners are ManagedObjects.
Task // to schedule future calls to our run() method.
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
/** The {@link Logger} for this class. */
private static final Logger logger =
Logger.getLogger(HelloTimer.class.getName());
/** The delay before the first run of the task. */
public static final int DELAY_MS = 5000;
/** The time to wait before repeating the task. */
public static final int PERIOD_MS = 500;
// implement AppListener

/**
* {@inheritDoc}
* <p>
* Schedules the {@code run()} method to be called periodically.
* Since SGS tasks are persistent, the scheduling only needs to
* be done the first time the application is started. When the
* server is killed and restarted, the scheduled timer task will
* continue ticking.
* <p>
* Runs the task {@value #DELAY_MS} ms from now,
* repeating every {@value #PERIOD_MS} ms.
*/
public void initialize(Properties props) {
TaskManager taskManager = AppContext.getTaskManager();
taskManager.schedulePeriodicTask(this, DELAY_MS, PERIOD_MS);
}

/**
* {@inheritDoc}
* <p>
* Prevents client logins by returning {@code null}.
*/
public ClientSessionListener loggedIn(ClientSession session) {
return null;
}
// implement Task

/**
* {@inheritDoc}
* <p>
* Logs the current timestamp whenever this {@code Task} gets run.
*/
public void run() throws Exception {
logger.log(Level.INFO,
"HelloTimer task: running at timestamp {0,number,#}",
System.currentTimeMillis());
}
}


이제 반복 이벤트를 만들었고 우리는 정지되고 재시작할 때 어떤 동작을 일으키는 첫번째 어플리케이션을 만들었습니다. 태스크 등록은 지속적이어야 합니다. 이는 다운되거나 재시동할 때 상태를 지속할 수 있습니다. 이 동작을 살펴보기 위해 서버를 정지시키고 재시작해보십시오.
주기적인 태스크는 오브젝트 스토어 내에 여러분의 오브젝트와 함께 저장된 정보입니다. 따라서 여러분이 데이터 디렉토리를 삭제하고 초기화되어있지 않은 처음의 상태로 되돌린다면 주기적인 태스크 또한 초기화될 것입니다.
이 행동은 여러분이 서버가 항상 동작하는 것 처럼 여러분의 코드를 작성하는 것을 허용하며, 여러분이 중요한 로직을 포함한 주기적 태스크의 동작 메소드 내의 경과된 시간을 점검하는 것에 주의를 요합니다.
어떻게 마지막 실행 시간 추적을 유지하는냐에대한 주제가 다음 레슨의 주제입니다.
Now that we have a repeating event, we have our first application that will do something when stopped and restarted. Task registration is persistent, which is to say, it survives a crash and reboot. Try stopping the server and restarting it again to see this in action.
The periodic task is information stored in the Object Store along with your managed objects, so if you clear the data directory and return it to its pristine, uninitialized state, the periodic tasks will also get cleared.
This behavior allows you to write your code as if the server were always up, with the caveat that you do have to check elapsed time in your periodic task's run method if a delay between that and the last time it was run has significance to your logic.
How you keep track of the last time run was called is the subject of the next lesson.


Tutorial Lesson 4: Hello Persistence!
Lesson 3 explained that tasks that are run on a delay or repeat don't necessarily happen exactly at the time you asked for. They could happen a bit later if (for example) the system is very loaded, or a lot later if (for example) the entire data center has actually come down and had to be restarted.
To track the last time the run task was called and calculate the true time-delta, we need a way of storing the past time value so that it will survive the system going down. This is called persistent storage, and in real games it is very important. Imagine how your users would react if your machine went down and they all lost their characters and everything on them!
A Managed Object is an object for which the system tracks state and which the system makes persistent. We mentioned above that AppListener interface inherits the Managed Object interface and that your AppListener instance is automatically created by the system for you. The system also registers your AppListener as a Managed Object with the Data Manager. This means that its state will be preserved by the PDS for you.

Coding HelloPersistence
Since our HelloTimer task is a Managed Object, all we need to do is add a field to track the last time run was called. Below is the code for HelloPersistence.
Run HelloPersistence as a PDS application. Stop the PDS, wait a minute, and then start it again. You will see that the elapsed time reported includes the down time. This is because currentTimeMillis is based on the system clock, and time kept moving forward even when the PDS wasn't running.
Persistence is that simple and automatic in the PDS. Any non-transient field on a registered Managed Object will be persisted.10
HelloPersistence
/*
* Copyright 2007-2008 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
8 A full Project Darkstar Server production environment provides failover mechanisms so that the loss of one server won't
bring the game down. In a true disaster, such as loss of power across the entire data center, it is possible the entire back
end might go off-line.
9 Depending on your operating system, you may see the elapsed time reported by HelloPersistence while the PDS is
running to be a bit over or a bit under 500ms. This is because currentTimeMillis does not necessarily have a 1 ms
accuracy. In particular, Windows systems tend to have a lower currentTimeMillis accuracy than other Java™ SE
platforms.
10 A transient field is one marked with the transient key word. Some values aren't valid beyond the task in which they are
used, and thus should be marked transient . for example, a field that caches a Manager during the current task.
Project Darkstar Server Application Tutorial 12/19/08 23
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sun.sgs.tutorial.server.lesson4;
import java.io.Serializable;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.Task;
import com.sun.sgs.app.TaskManager;
/**
* A simple persistence example for the Project Darkstar Server.
* As a {@link ManagedObject}, it is able to modify instance fields,
* demonstrated here by tracking the last timestamp at which a task
* was run and displaying the time delta.
*/
public class HelloPersistence
implements AppListener, // to get called during application startup.
Serializable, // since all AppListeners are ManagedObjects.
Task // to schedule future calls to our run() method.
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
/** The {@link Logger} for this class. */
private static final Logger logger =
Logger.getLogger(HelloPersistence.class.getName());
/** The delay before the first run of the task. */
public static final int DELAY_MS = 5000;
/** The time to wait before repeating the task. */
public static final int PERIOD_MS = 500;
/** The timestamp when this task was last run. */
private long lastTimestamp = System.currentTimeMillis();
// implement AppListener
/**
* {@inheritDoc}
* <p>
* Schedules the {@code run()} method to be called periodically.
* Since SGS tasks are persistent, the scheduling only needs to
* be done the first time the application is started. When the
* server is killed and restarted, the scheduled timer task will
* continue ticking.
* <p>
* Runs the task {@value #DELAY_MS} ms from now,
* repeating every {@value #PERIOD_MS} ms.
*/
public void initialize(Properties props) {
TaskManager taskManager = AppContext.getTaskManager();
taskManager.schedulePeriodicTask(this, DELAY_MS, PERIOD_MS);
24 12/19/08 Project Darkstar Server Application Tutorial
}
/**
* {@inheritDoc}
* <p>
* Prevents client logins by returning {@code null}.
*/
public ClientSessionListener loggedIn(ClientSession session) {
return null;
}
// implement Task
/**
* {@inheritDoc}
* <p>
* Each time this {@code Task} is run, logs the current timestamp and
* the delta from the timestamp of the previous run.
*/
public void run() throws Exception {
long timestamp = System.currentTimeMillis();
long delta = timestamp - lastTimestamp;
// Update the field holding the most recent timestamp.
lastTimestamp = timestamp;
logger.log(Level.INFO,
"timestamp = {0,number,#}, delta = {1,number,#}",
new Object[] { timestamp, delta }
);
}
}


Coding HelloPersistence2While we could put all the fields of our application on the AppListener, there are many good reasons not to do this. As any Managed Object grows larger, it takes more time for the system to store and retrieve it. Also, although PDS task code is written as if it were monothreaded, many tasks are actually executing in parallel at any given time. Should the tasks conflict in what data they have to modify, then one will either have to wait for the other to finish or, in a worst-case situation, actually abandon all the work it had done up to that point and try again later.
For these reasons, an application will want to create other Managed Objects of its own. Luckily, that's easy to do!
All Managed Objects must meet two criteria:
● They must be Serializable.
● They must implement the ManagedObject marker interface. (AppListener actually inherits the
ManagedObject marker interface for you.)
One good way to break your application up into multiple Managed Objects is by the events they handle. A Managed Object can handle only one event at a time, so you want to separate all event handlers for events that might occur in parallel into separate Managed Objects. Below is the code to HelloPersistence2. It creates a separate TrivialTimedTask Managed Object from the AppListener to handle the timed task.

HelloPersistence2
/*
* Copyright 2007-2008 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sun.sgs.tutorial.server.lesson4;
import java.io.Serializable;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
import com.sun.sgs.app.TaskManager;
/**
* A simple persistence example for the Project Darkstar Server.
*/
public class HelloPersistence2
implements AppListener, Serializable
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
/** The {@link Logger} for this class. */
private static final Logger logger =
Logger.getLogger(HelloPersistence2.class.getName());
/** The delay before the first run of the task. */
public static final int DELAY_MS = 5000;
/** The time to wait before repeating the task. */
public static final int PERIOD_MS = 500;
// implement AppListener
/**
* {@inheritDoc}
* <p>
* Creates a {@link TrivialTimedTask} and schedules its {@code run()}
* method to be called periodically.
* <p>
* Since SGS tasks are persistent, the scheduling only needs to
* be done the first time the application is started. When the
26 12/19/08 Project Darkstar Server Application Tutorial
* server is killed and restarted, the scheduled timer task will
* continue ticking.
* <p>
* Runs the task {@value #DELAY_MS} ms from now,
* repeating every {@value #PERIOD_MS} ms.
*/
public void initialize(Properties props) {
TrivialTimedTask task = new TrivialTimedTask();
logger.log(Level.INFO, "Created task: {0}", task);
TaskManager taskManager = AppContext.getTaskManager();
taskManager.schedulePeriodicTask(task, DELAY_MS, PERIOD_MS);
}
/**
* {@inheritDoc}
* <p>
* Prevents client logins by returning {@code null}.
*/
public ClientSessionListener loggedIn(ClientSession session) {
return null;
}
}



TrivialTimedTask
This is the Managed Object we are going to have respond to the repeating task.
/*
* Copyright 2007-2008 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sun.sgs.tutorial.server.lesson4;
import com.sun.sgs.app.AppContext;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.Task;
/**
* A simple repeating Task that tracks and prints the time since it was
* last run.
Project Darkstar Server Application Tutorial 12/19/08 27
*/
public class TrivialTimedTask
implements Serializable, // for persistence, as required by ManagedObject.
ManagedObject, // to let the SGS manage our persistence.
Task // to schedule future calls to our run() method.
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
/** The {@link Logger} for this class. */
private static final Logger logger =
Logger.getLogger(TrivialTimedTask.class.getName());
/** The timestamp when this task was last run. */
private long lastTimestamp = System.currentTimeMillis();
// implement Task
/**
* {@inheritDoc}
* <p>
* Each time this {@code Task} is run, logs the current timestamp and
* the delta from the timestamp of the previous run.
*/
public void run() throws Exception {
// We will be modifying this object.
AppContext.getDataManager().markForUpdate(this);
long timestamp = System.currentTimeMillis();
long delta = timestamp - lastTimestamp;
// Update the field holding the most recent timestamp.
lastTimestamp = timestamp;
logger.log(Level.INFO,
"timestamp = {0,number,#}, delta = {1,number,#}",
new Object[] { timestamp, delta }
);
}
}


Coding HelloPersistence3
A Managed Object does not actually become managed by the Data Manager, and thus persistent, until the Data Manager is made aware of it. The reason HelloPersistence2 works is because the Task Manager persisted the TrivialTimedTask object for us. In order to persist other Managed Objects, though, an application needs to take on the responsibility of informing the Data Manager itself. One way the Data Manager can become aware of a Managed Object is through a request for a Managed Reference.
Managed Objects often need to refer to other Managed Objects. This is done with a Managed Reference. It is very important that the only fields on one Managed Object that reference another Managed Object be Managed References. This is how the Data Manager knows that it is a reference to a separate Managed Object. If you store a simple Java reference to the second Managed Object in a field on the first Managed Object, the second object will become part of the first object's state when the first object is stored. The result will be that, the next time the
first object tries to access the second, it will get its own local copy and not the real second Managed Object.
HelloPersistence3 below illustrates this by creating a second persistent object that is called from the TrivialTimedTask and that keeps the last-called time as part of its persistent state.

HelloPersistence3
HelloPersistence3, below, is a task that delegates to a sub-task (a TrivialTimedTask that is not scheduled to run on its own). The sub-task is stored in a Managed Reference on HelloPersistence3.
/*
* Copyright 2007-2008 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sun.sgs.tutorial.server.lesson4;
import java.io.Serializable;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
import com.sun.sgs.app.DataManager;
import com.sun.sgs.app.ManagedReference;
import com.sun.sgs.app.Task;
import com.sun.sgs.app.TaskManager;
/**
* A simple persistence example for the Project Darkstar Server.
*/
public class HelloPersistence3
implements AppListener, Serializable, Task
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
/** The {@link Logger} for this class. */
private static final Logger logger =
Logger.getLogger(HelloPersistence3.class.getName());
/** The delay before the first run of the task. */
public static final int DELAY_MS = 5000;
/** The time to wait before repeating the task. */
public static final int PERIOD_MS = 500;
/** A reference to our subtask, a {@link TrivialTimedTask}. */
private ManagedReference<TrivialTimedTask> subTaskRef = null;
/**
Project Darkstar Server Application Tutorial 12/19/08 29
* Gets the subtask this task delegates to. Dereferences a
* {@link ManagedReference} in this object that holds the subtask.
* <p>
* This null-check idiom is common when getting a ManagedReference.
*
* @return the subtask this task delegates to, or null if none is set
*/
public TrivialTimedTask getSubTask() {
if (subTaskRef == null)
return null;
return subTaskRef.get();
}
/**
* Sets the subtask this task delegates to. Stores the subtask
* as a {@link ManagedReference} in this object.
* <p>
* This null-check idiom is common when setting a ManagedReference,
* since {@link DataManager#createReference createReference} does
* not accept null parameters.
*
* @param subTask the subtask this task should delegate to,
* or null to clear the subtask
*/
public void setSubTask(TrivialTimedTask subTask) {
if (subTask == null) {
subTaskRef = null;
return;
}
DataManager dataManager = AppContext.getDataManager();
subTaskRef = dataManager.createReference(subTask);
}
// implement AppListener
/**
* {@inheritDoc}
* <p>
* Schedules the {@code run()} method of this object to be called
* periodically.
* <p>
* Since SGS tasks are persistent, the scheduling only needs to
* be done the first time the application is started. When the
* server is killed and restarted, the scheduled timer task will
* continue ticking.
* <p>
* Runs the task {@value #DELAY_MS} ms from now,
* repeating every {@value #PERIOD_MS} ms.
*/
public void initialize(Properties props) {
// Hold onto the task (as a managed reference)
setSubTask(new TrivialTimedTask());
TaskManager taskManager = AppContext.getTaskManager();
taskManager.schedulePeriodicTask(this, DELAY_MS, PERIOD_MS);
}
/**
* {@inheritDoc}
* <p>
* Prevents client logins by returning {@code null}.
*/
30 12/19/08 Project Darkstar Server Application Tutorial
public ClientSessionListener loggedIn(ClientSession session) {
return null;
}
// implement Task
/**
* {@inheritDoc}
* <p>
* Calls the run() method of the subtask set on this object.
*/
public void run() throws Exception {
// Get the subTask (from the ManagedReference that holds it)
TrivialTimedTask subTask = getSubTask();
if (subTask == null) {
logger.log(Level.WARNING, "subTask is null");
return;
}
// Delegate to the subTask's run() method
subTask.run();
}
}


Another way to register a Managed Object to the Data Manager is with the setBinding call. This call does not return a Managed Reference, but instead binds the Managed Object to the string passed in with it to the call.
Once a Managed Object has a name bound to it, the Managed Object may be retrieved by passing the same name to the getBinding call. Note that name bindings must be distinct. For each unique string used as a name binding by an application, there can be one and only one Managed Object bound.
Retrieving a Managed Object by its binding has some additional overhead, so it's better to keep Managed References to Managed Objects in the other Managed Objects that need to call them. There are, however, some problems that are best solved with a name-binding convention; one common example is finding the player object for a particular player at the start of his or her session.
There are a number of other interesting methods on the Data Manager. You might want to look at the Javadoc now, but discussion of them will be put off until required by the tutorial applications.

Lesson 5: Hello User!
Up till now the tutorial lessons have focused on getting your logic up and running in the PDS. But there is another side to the online game equation ─ the users and their computers. This lesson shows how to start communicating between clients and the PDS.
In this tutorial, the server side of that communication will be explained and illustrated using a simple pre-built client. For the client-side coding, please see the Project Darkstar Client Tutorial.
Knowing When a User Logs In The first step in communicating with users is knowing who is available to communicate with. The PDS provides a callback method on the AppListener for this: loggedIn. The loggedIn method gets passed an object that describes the user; this object is called a ClientSession.11
Below is the code for HelloUser, a trivial application that logs the login of a user.

HelloUser
/*
* Copyright 2007-2008 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sun.sgs.tutorial.server.lesson5;
import java.io.Serializable;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
/**
* Simple example of listening for user {@linkplain AppListener#loggedIn login}
* in the Project Darkstar Server.
* <p>
* Logs each time a user logs in, then kicks them off immediately.
11 In fact, ClientSession describes the new connection session, the user being one of those parameters. This distinction is
important, in that you cannot save a ClientSession object and expect it to be valid after the session has ended, which is
when the user disconnects.
32 12/19/08 Project Darkstar Server Application Tutorial
*/
public class HelloUser
implements AppListener, // to get called during startup and login.
Serializable // since all AppListeners are ManagedObjects.
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
/** The {@link Logger} for this class. */
private static final Logger logger =
Logger.getLogger(HelloUser.class.getName());
// implement AppListener
/** {@inheritDoc} */
public void initialize(Properties props) {
// empty
}
/**
* {@inheritDoc}
* <p>
* Logs a message each time a new session tries to login, then
* kicks them out by returning {@code null}.
*/
public ClientSessionListener loggedIn(ClientSession session) {
// User has logged in
logger.log(Level.INFO, "User {0} almost logged in", session.getName());
// Kick the user out immediately by returning a null listener
return null;
}
}


Direct Communication
You will note that, when you run the server application above and connect to it with a client, the client is immediately logged out. This is because we are returning null from loggedIn. The PDS interprets this as our rejecting the user. To accept the user and allow him or her to stay logged in, you need to return a valid ClientSessionListener. To be valid, this object must implement both ClientSessionListener and Serializable.
Below is HelloUser2, which does this.

HelloUser2
HelloUser2 is identical to HelloUser except for the loggedIn method:
/*
* Copyright 2007-2008 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
Project Darkstar Server Application Tutorial 12/19/08 33
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sun.sgs.tutorial.server.lesson5;
import java.io.Serializable;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
/**
* Simple example of listening for user {@linkplain AppListener#loggedIn login}
* in the Project Darkstar Server.
* <p>
* Logs each time a user logs in, and sets their listener to a
* new {@link HelloUserSessionListener}.
*/
public class HelloUser2
implements AppListener, // to get called during startup and login.
Serializable // since all AppListeners are ManagedObjects.
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
/** The {@link Logger} for this class. */
private static final Logger logger =
Logger.getLogger(HelloUser2.class.getName());
// implement AppListener
/** {@inheritDoc} */
public void initialize(Properties props) {
// empty
}
/**
* {@inheritDoc}
* <p>
* Logs a message each time a new session logs in.
*/
public ClientSessionListener loggedIn(ClientSession session) {
// User has logged in
logger.log(Level.INFO, "User {0} has logged in", session.getName());
// Return a valid listener
return new HelloUserSessionListener(session);
}
}


HelloUserSessionListener
/*
* Copyright 2007-2008 Sun Microsystems, Inc.
34 12/19/08 Project Darkstar Server Application Tutorial
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sun.sgs.tutorial.server.lesson5;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
import com.sun.sgs.app.ManagedReference;
/**
* Simple example {@link ClientSessionListener} for the Project Darkstar
* Server.
* <p>
* Logs each time a session receives data or logs out.
*/
class HelloUserSessionListener
implements Serializable, ClientSessionListener
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
/** The {@link Logger} for this class. */
private static final Logger logger =
Logger.getLogger(HelloUserSessionListener.class.getName());
/** The session this {@code ClientSessionListener} is listening to. */
private final ManagedReference<ClientSession> sessionRef;
/** The name of the {@code ClientSession} for this listener. */
private final String sessionName;
/**
* Creates a new {@code HelloUserSessionListener} for the given session.
*
* @param session the session this listener is associated with
*/
public HelloUserSessionListener(ClientSession session) {
if (session == null)
throw new NullPointerException("null session");
sessionRef = AppContext.getDataManager().createReference(session);
sessionName = session.getName();
}
Project Darkstar Server Application Tutorial 12/19/08 35
/**
* Returns the session for this listener.
*
* @return the session for this listener
*/
protected ClientSession getSession() {
// We created the ref with a non-null session, so no need to check it.
return sessionRef.get();
}
/**
* {@inheritDoc}
* <p>
* Logs when data arrives from the client.
*/
public void receivedMessage(ByteBuffer message) {
logger.log(Level.INFO, "Message from {0}", sessionName);
}
/**
* {@inheritDoc}
* <p>
* Logs when the client disconnects.
*/
public void disconnected(boolean graceful) {
String grace = graceful ? "graceful" : "forced";
logger.log(Level.INFO,
"User {0} has logged out {1}",
new Object[] { sessionName, grace }
);
}
}

HelloUserSessionListener is a glue object that listens for either data from the user or the disconnect of the user;
it allows our server code to respond to these events. So far, all we do is log some information, but in a complete PDS application, these would both be important events to which we would want to respond.
There are two kinds of communication in the PDS:
● Direct Communication
● Channel Communication
Direct Communication is built into the core of the system and provides a pipe for the flow of data between a single user client and its PDS application.
Channel Communication is provided by a standard manager, the Channel Manager, and provides for publish/subscribe group communications. While there is nothing in the Channel Manager's functionality that could not be implemented on top of the Direct Communication mechanisms, putting the channel functionality in a manager allows for a much more efficient implementation.
The HelloEcho PDS application echoes back to the user anything the user sends to the application. Besides the name, there is only one line difference in HelloEchoSessionListener from HelloUserSessionListener: the addition of a session.send call.

HelloEchoSessionListener
/*
* Copyright 2007-2008 Sun Microsystems, Inc.
*
36 12/19/08 Project Darkstar Server Application Tutorial
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sun.sgs.tutorial.server.lesson5;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
import com.sun.sgs.app.ManagedReference;
/**
* Simple example {@link ClientSessionListener} for the Project Darkstar
* Server.
* <p>
* Logs each time a session receives data or logs out, and echoes
* any data received back to the sender.
*/
class HelloEchoSessionListener
implements Serializable, ClientSessionListener
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
/** The {@link Logger} for this class. */
private static final Logger logger =
Logger.getLogger(HelloEchoSessionListener.class.getName());
/** The session this {@code ClientSessionListener} is listening to. */
private final ManagedReference<ClientSession> sessionRef;
/** The name of the {@code ClientSession} for this listener. */
private final String sessionName;
/**
* Creates a new {@code HelloEchoSessionListener} for the given session.
*
* @param session the session this listener is associated with
*/
public HelloEchoSessionListener(ClientSession session) {
if (session == null)
throw new NullPointerException("null session");
sessionRef = AppContext.getDataManager().createReference(session);
sessionName = session.getName();
}
Project Darkstar Server Application Tutorial 12/19/08 37
/**
* Returns the session for this listener.
*
* @return the session for this listener
*/
protected ClientSession getSession() {
// We created the ref with a non-null session, so no need to check it.
return sessionRef.get();
}
/**
* {@inheritDoc}
* <p>
* Logs when data arrives from the client, and echoes the message back.
*/
public void receivedMessage(ByteBuffer message) {
ClientSession session = getSession();
logger.log(Level.INFO, "Message from {0}", sessionName);
// Echo message back to sender
session.send(message);
}
/**
* {@inheritDoc}
* <p>
* Logs when the client disconnects.
*/
public void disconnected(boolean graceful) {
ClientSession session = getSession();
String grace = graceful ? "graceful" : "forced";
logger.log(Level.INFO,
"User {0} has logged out {1}",
new Object[] { sessionName, grace }
);
}
}

Running the Examples
To try all the examples in this part of the server tutorial, you need a simple client capable of logging in, as well as direct client/server communication. You can find this client as part of Lesson 1 of the Project Darkstar Client Tutorial (com.sun.sgs.tutorial.client.lesson1.HelloUserClient in the tutorial-client.jar file).


Lesson 6: Hello Channels!
The previous lessons have introduced the Task Manager and Data Manager. The final standard manager is the Channel Manager. The core of the PDS provides us with basic client/server communications. For simple games, this may be enough. However, for games that organize players into groups, either to isolate game sessions (such as in many casual and fast action games), or to tame the n-squared user-to-user communications scaling issues inherent in massive numbers of simultaneous players, something with lower overhead and more control is
required.
The Channel Manager provides publish/subscribe channels. The server application can create these channels and then assign users to one or more of them. Communication between users in a channel does not involve the Task or Data Manager.
Coding HelloChannels
The HelloChannels Managed Object is similar to our previous AppListener implementations with the addition
that it opens two reliable channels, Foo and Bar.

HelloChannels
/*
* Copyright 2007-2008 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sun.sgs.tutorial.server.lesson6;
import java.io.Serializable;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.Channel;
import com.sun.sgs.app.ChannelManager;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
import com.sun.sgs.app.Delivery;
import com.sun.sgs.app.ManagedReference;
/**
* Simple example of channel operations in the Project Darkstar Server.
Project Darkstar Server Application Tutorial 12/19/08 39
* <p>
* Extends the {@code HelloEcho} example by joining clients to two
* channels.
*/
public class HelloChannels
implements Serializable, AppListener
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
/** The {@link Logger} for this class. */
private static final Logger logger =
Logger.getLogger(HelloChannels.class.getName());
/* The name of the first channel {@value #CHANNEL_1_NAME} */
static final String CHANNEL_1_NAME = "Foo";
/* The name of the second channel {@value #CHANNEL_2_NAME} */
static final String CHANNEL_2_NAME = "Bar";
/**
* The first {@link Channel}. The second channel is looked up
* by name.
*/
private ManagedReference<Channel> channel1 = null;
/**
* {@inheritDoc}
* <p>
* Creates the channels. Channels persist across server restarts,
* so they only need to be created here in {@code initialize}.
*/
public void initialize(Properties props) {
ChannelManager channelMgr = AppContext.getChannelManager();
// Create and keep a reference to the first channel.
Channel c1 = channelMgr.createChannel(CHANNEL_1_NAME,
null,
Delivery.RELIABLE);
channel1 = AppContext.getDataManager().createReference(c1);
// We don't keep a reference to the second channel, to demonstrate
// looking it up by name when needed. Also, this channel uses a
// {@link ChannelListener} to filter messages.
channelMgr.createChannel(CHANNEL_2_NAME,
new HelloChannelsChannelListener(),
Delivery.RELIABLE);
}
/**
* {@inheritDoc}
* <p>
* Returns a {@link HelloChannelsSessionListener} for the
* logged-in session.
*/
public ClientSessionListener loggedIn(ClientSession session) {
logger.log(Level.INFO, "User {0} has logged in", session.getName());
return new HelloChannelsSessionListener(session, channel1);
}
}


The HelloChannelsSessionListener is identical to HelloEchoSessionListener except for the constructor. When we create the session listener, we also join its session to two channels. One channel is passed in, while the second is looked up by name.
The first channel.join is passed null for a ChannelListener, so all communication on it is only received by clients. The second channel joined however is given a channel listener. This will be called back whenever a message from this session is posted to that channel. The listener can examine the message and sender and decide to discard the message, send a different message to the channel, or send the original, unmodified message to the channel.
Note that, with this code, each session has it own listener for messages on the second channel. This is preferable to registering a single channel-wide listener, since messages from different clients can be processed in parallel.
However, if your design really requires a single listener to all messages sent by any client on a channel, you would declare the listener as the second parameter to the createChannel call.

HelloChannelsSessionListener
/*
* Copyright 2007-2008 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sun.sgs.tutorial.server.lesson6;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.Channel;
import com.sun.sgs.app.ChannelManager;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
import com.sun.sgs.app.DataManager;
import com.sun.sgs.app.ManagedReference;
/**
* Simple example {@link ClientSessionListener} for the Project Darkstar
* Server.
* <p>
* Logs each time a session receives data or logs out, and echoes
* any data received back to the sender.
*/
class HelloChannelsSessionListener
implements Serializable, ClientSessionListener
{
Project Darkstar Server Application Tutorial 12/19/08 41
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
/** The {@link Logger} for this class. */
private static final Logger logger =
Logger.getLogger(HelloChannelsSessionListener.class.getName());
/** The session this {@code ClientSessionListener} is listening to. */
private final ManagedReference<ClientSession> sessionRef;
/** The name of the {@code ClientSession} for this listener. */
private final String sessionName;
/**
* Creates a new {@code HelloChannelsSessionListener} for the session.
*
* @param session the session this listener is associated with
* @param channel1 a reference to a channel to join
*/
public HelloChannelsSessionListener(ClientSession session,
ManagedReference<Channel> channel1)
{
if (session == null)
throw new NullPointerException("null session");
DataManager dataMgr = AppContext.getDataManager();
sessionRef = dataMgr.createReference(session);
sessionName = session.getName();
// Join the session to all channels. We obtain the channel
// in two different ways, by reference and by name.
ChannelManager channelMgr = AppContext.getChannelManager();
// We were passed a reference to the first channel.
channel1.get().join(session);
// We look up the second channel by name.
Channel channel2 = channelMgr.getChannel(HelloChannels.CHANNEL_2_NAME);
channel2.join(session);
}
/**
* Returns the session for this listener.
*
* @return the session for this listener
*/
protected ClientSession getSession() {
// We created the ref with a non-null session, so no need to check it.
return sessionRef.get();
}
/**
* {@inheritDoc}
* <p>
* Logs when data arrives from the client, and echoes the message back.
*/
public void receivedMessage(ByteBuffer message) {
ClientSession session = getSession();
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Message from {0}", sessionName);
}
session.send(message);
42 12/19/08 Project Darkstar Server Application Tutorial
}
/**
* {@inheritDoc}
* <p>
* Logs when the client disconnects.
*/
public void disconnected(boolean graceful) {
String grace = graceful ? "graceful" : "forced";
logger.log(Level.INFO,
"User {0} has logged out {1}",
new Object[] { sessionName, grace }
);
}
}


HelloChannelsChannelListener is a simple skeletal listener that just logs what it receives.
HelloChannelsChannelListener
/*
* Copyright 2007-2008 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sun.sgs.tutorial.server.lesson6;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.Channel;
import com.sun.sgs.app.ChannelListener;
import com.sun.sgs.app.ClientSession;
import java.nio.ByteBuffer;
/**
* Simple example {@link ChannelListener} for the Project Darkstar Server.
* <p>
* Logs when a channel receives data.
*/
class HelloChannelsChannelListener
implements Serializable, ChannelListener
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
Project Darkstar Server Application Tutorial 12/19/08 43
/** The {@link Logger} for this class. */
private static final Logger logger =
Logger.getLogger(HelloChannelsChannelListener.class.getName());
/**
* {@inheritDoc}
* <p>
* Logs when data arrives on a channel. A typical listener would
* examine the message to decide whether it should be discarded,
* modified, or sent unchanged.
*/
public void receivedMessage(Channel channel,
ClientSession session,
ByteBuffer message)
{
if (logger.isLoggable(Level.INFO)) {
logger.log(Level.INFO,
"Channel message from {0} on channel {1}",
new Object[] { session.getName(), channel.getName() }
);
}
channel.send(session, message);
}
}

Running HelloChannels
To try HelloChannels you need a client that will connect and allow you to talk on selected channels. One is provided in Lesson 2 of the Project Darkstar Client Tutorial
(com.sun.sgs.tutorial.client.lesson2.HelloChannelClient in tutorial-client.jar).

Conclusion
At this point you know all the basics of writing PDS applications. The applications you write using the PDS API on a single-node system will operate unmodified in exactly the same way on a large-scale multiple-node PDS production back end. In that environment they will scale out horizontally, handle failover, and be fault-tolerant.
There are, however, some best practices to follow to ensure optimal scalability for your application. Failing to follow these can seriously limit how many users your application will be able to support at once.
Best Practices
● Do not design with bottleneck Managed Objects.
You can think of each Managed Object as an independent worker who can only do one task at a time.
When one task is modifying the state of a Managed Object, any other tasks that want to read or change its state must wait. A Managed Object may have many readers at once if no one is writing to it, but any writing turns it into a potential bottleneck. In general, a pattern of one writer and many readers is the best configuration, although it is not always possible.
In the worst case, multiple lockers of the same objects will cause potential deadlock situations that are computationally more expensive to resolve and can result in processing delays (additional latency).
● Avoid contending for object access.
Although the PDS will detect and resolve contentions for write access to objects between parallel events such that your code never actually stops processing, this deadlock avoidance can significantly load the CPU and slow response time to the users. In the worst case, contention can actually prevent parallel processing, seriously impacting the scalability of your code.
A classic example of a deadlock is two combatants who have to modify both their own data and that of their enemies when resolving an attack. If all the data is on a combatant Managed Object, then both combat tasks need to lock both objects, typically in the opposite order, which is a formula for deadlock.
One solution is to divide the combatants' combat statistics into two groups, the ones they modify themselves and the ones modified by an attacker, and store them on separate Managed Objects. The combatant object would then retain Managed References to these objects, and only lock them as needed.
Another practical solution is partial serialization of the problem. This is especially appropriate if you want the combatants attacks to be processed in a fixed order. Here you have a single “combat clock” task (generally a repeating task) that processes in sequence all the attacks from all the combatants in a given battle.
Not all contention situations are this easy to spot and design around. The PDS gives you feedback at runtime as to what objects are deadlocking. Use this to tune your application.
● Give all serializable objects a fixed serialVersionUID.
The PDS uses Serialization internally. If you don't give your Serializable classes a serialVersionUID,
then any change to their public interface could invalidate the stored copies, leading to a need to delete the entire Object Store. Giving them a fixed serialVersionUID will allow you to make “compatible changes” without invalidating your existing store. (For what constitutes a compatible change, please see the JDK™ documents on Serialization.)
● Avoid inner classes in Serializable objects.
Serialization of objects whose classes contain non-static inner classes gets complicated. It is best to avoid inner classes, including anonymous inner classes. If you must use them, the safest thing is to declare them as static inner classes.
● Do not create non-final static fields on Managed Objects.
The most important reason for this is that static fields exist only within the scope of a single VM, and a PDS back end floats Managed Objects between many different VMs.
● Do not use the synchronized keyword in Managed Objects.
First, this is unnecessary in a PDS application. Synchronization is used to prevent contention over data between multiple parallel threads of control. The PDS programming model handles this transparently for you. Second, it won't work, since synchronization is relative to a single VM and a full PDS back-end operates over many VM instances simultaneously. Finally, it causes interactions between tasks that can defeat the system's deadlock-proofing feature and actually cause your code to lock up.
Any task that locks up for too long will be forced by the PDS to yield its control of Managed Objects back to the system, but this is a last-ditch safety feature and will result in significant delays in code execution.
● Do not make blocking I/O calls or stay in a loop for a long period.
The system contains the assumption that tasks are short-lived. If a task lives too long, it will be forcibly terminated by the back end. The right way to do blocking I/O and the like is to create an extension manager, do the blocking calls in it, and submit a task to the Task Manager to handle the results when done. (Writing and installing custom managers will be covered in the Project Darkstar Server Extension Manual.)
● Do not catch java.lang.Exception.
Instead, catch the explicit exceptions you are expecting. The PDS also uses exceptions to communicate exceptional states to the execution environment. Although the system does its best to do the right thing even if you hide these exceptions from it, it will operate more efficiently if you don't.
● Do not carry a non-transient Java reference on a Managed Object to another Managed Object.
Instead, use a Managed Reference. Any object that is referred to by a Java reference chain that starts at the Managed Object is assumed to be part of the private state of that particular Managed Object. This means that, while you may set two Java references on two different Managed Objects to the same Java object during a task, they will each end up with their own copy of that object at the termination of the task.
● Never try to save a Manager Instance on a non-transient field of a Managed Object.
This is because Manager References are only valid for the life of the task that fetched them. Manager instances are not serializable objects. Any attempt to save a reference to one in your Managed Object will cause the Data Manager to throw a non-retriable exception and the entire task to be abandoned.
● Carefully manage the life cycle of your Managed Objects.
Remember that the Object Store does no garbage collection for you. Managed Objects are “real objects” in the simulation sense. They don't exist until explicitly created, exist in one and only one state at any given time, and persist until explicitly destroyed. These are very good properties from a simulation programming stance, but if you go wild creating Managed Objects and don't destroy them when they are no longer useful, you can load down the Object Store with garbage and potentially impact performance.
Be aware that any PDS API call that accepts a Managed Object may create a Managed Reference to that object. If this is the first time the Data Manager has been made aware of the Managed Object, this will result in the Managed Object being added to the Object Store. It is still the developer's responsibility to remove the Managed Object from the Object Store when it is no longer in use. For this reason, it is best to avoid passing Managed Objects that your application is not explicitly managing into the PDS APIs.

Things Not Covered in This TutorialThis tutorial is intended to introduce you to the fundamentals of coding a PDS application. Although we present all basic uses of the standard managers, these managers have additional functions and capabilities not covered here. Please see the Javadoc for those other functions.
As discussed, the list of managers in a given PDS back end is extensible. This tutorial does not cover writing or using plug-in managers. For that, see the forthcoming document on PDS extensions.
Although we use PDS client programs in Lessons 5 and 6, this tutorial does not explain how those are written.
For those explanations, please see the Project Darkstar Client Tutorial.
Finally, for the sake of clarity, this tutorial shows very simple examples. For more complex patterns of PDS
usage, please see the community examples at www.projectdarkstar.com.


Appendix A: SwordWorld Example Code - 뺍니다. PDS 홈페이지에서 다운받으세요



















수트를 제대로 입는 Tip 95가지 -GQ

<< 수트를 제대로 입는 Tip 95가지 -GQ >>


1. 재킷에 달린 단추 갯수가 두개든, 세개든 단추를 잠글 때는하나만 잠근다.

2. 즉, 2버튼 수트는 윗단추를, 3번튼 수트는 가운데 단추를 잠그는 것이다.

3. 베스트를 입는다고 꼭 뚱뚱해 보이는 것은 아니다.그러나 가능하면 베스트 자체를 입지 말 것.

4. 만약 당신이 수트를 한 벌만 가질수 있다면 그것은 차콜그레이어야 한다.

5. 만약 당신이 다행히도 수트를 두 벌 가질수 있다면 그것은 차콜그레이와 네이비블루이어야 한다.

6. 만약 당신이 신 혹은 부모님, 재벌 여자친구의 도움으로 수트를 세 벌 맘대로 가질 수있다면 그것은 차콜그레이, 네이비블루, 그리고 그레이어야 한다.

7. 남자가 여자보다 더 신중해 보인다면 그것은 남자의 옷 색깔이 여자의 옷 색깔보다 더 진하기 때문이다.

8. 처음에 사는수트들은 무늬가 없는 것이 좋고, 점점 스트라이프나 체크같은 페턴을 시도해본다.

9. 키가 커 보이고 싶은 남자에게는짙은 색상의 2버튼 수트가 좋다.

10. 좀 날씬해 보일 필요가 있는 남자도 짙은 색 3버튼 수트를입어라.

11. 좀 뚱뚱한 사람이라면 더블이 좋다. 색상도 어둡게.

12. 왜소한 체격을 감추기 위해 헐렁하게 입은 수트는 당신을 더욱 왜소하고 초라하며 결국에는 자신감도 취향도 없는 이상한 사람으로보이게 만든다.

13.다른 사람이 입은 수트 상표를 안다고 하더라도 그것을 화제에 올리지 마라. 설사 그가 아라비아 왕자들만 입는 지구 최고급 수트를 입고 있다 할지라도..

14. 입어보지 않고 수트를 사는 것은 얼굴 한 번 보지 못한 여자와 결혼하는 것과 같다.

15. 수트 재킷에는 골드 컬러 단추를 달지 않는다. 왜냐면 그건 블레이저용이니까.

16. 재킷 주머니에는 가능한 한 아무것도 넣지 마라.

17. 재킷 가슴 포켓에 넣을 수 있는 건 포켓스퀘어 뿐이다.

18. 바지 길이는 아무리 길어도 구두 뒷굽을 덮지 않아야 한다. 지금보다 3cm 줄여라. 4cm 줄여도 무방한 사람이 대부분일 것이다.

19. 수트엔 긴 소매 드레스셔츠를 입는 것이 기본이다.

20. 셔츠 안에 러닝셔츠를 입지 말아라. 그렇게하면 구속되는 법이 입안되기를 소망한다.

21. 꼭 러닝셔츠를 입어야겠다면 아예 수트를 입지 말아라.

22. 수트에는 반드시 흰색 셔츠만 입어야 한다는 고정관념을 버려라.

23. 그렇다고 하와이언 셔츠처럼 화려한 셔츠를 입으려는 생각은 더욱 버려라.

24. 아시다시피 셔츠 깃과 소매 끝은 늘 청결해야 한다.

25. 셔츠 소매는 약 1.5cm정도 재킷 소매 밖으로 나오게 입는 것이 적당하다.

26. 제대로 다려지지 않은 셔츠를 입고 출근하느니, 셔츠를 다려 입은 다음 상사에게 지각에 대한 주의를 듣는 편이 낫다. 단, 이 사항은 회사 문화와도 관련있으므로 탄력적으로 적용!

27. 아무리 주말이라 할지라도 수트 속에 터틀넥을 입는 것은 곤란하다. 수트는 Formal. 터틀넥같은 Kniw는 Casual.

28. 비지니스 수트에 실크 셔츠는 전혀 어울리지 않는다.

29. 버튼 다운 칼라 셔츠도 모든 수트에 어울리지 않는다. 그것은 재킷과는 매치가 가능하다.

30. 반팔 셔츠를 입고 넥타이 매고자 하는 사람도 아까 런닝입는 사람과 함께 구속했으면 좋겠다.

31. 여행을 다닐 때는 반드시 수트 케이스를 따로 챙겨 간다. 언제 어느 상황에서 품위있는 레스토랑에 초청될지 모른다.

32. 어떠한 상황에서도 바지 밑단을 걷어올리지 마라.

33. 수트를 입을 때 반드시 벨트를 매어야 한다.

34. 벨트 색깔은 반드시 블랙 아니면 브라운이어야 한다. 게다가 벨트 색상과 구두 색상 정도는 맞춰주는 센스.

35. 요란한 디자인의 금색 벨트는 정말이지 곤란하다. 조폭이시라면뭐 할수 없고.

36. 정장용 벨트를 청바지에 하는 것은 웃기지만 캐주얼용 벨트를 정장에 매는 것은 더 웃기다.

37. BY THE WAY 페라가모 벨트는 정말 별로다.페라가모의 로퍼는 더 후지다. 커다란 로고꼴 하고는....

38. 벨트와 서스펜더는 함께 하지 않는다. 속옷을 두 개 입는 것과 같다.

39. 수트 차림에 시간이 숫자로 표시되는 러버 잰드 시계는 어울리지 않는다.

40. 디자인이 복잡하고 화려한 시계 역시 수트 차림에 어울리지 않는다.

41. 수트에 어울리는 시계는 블랙 혹은 브라운 가죽 스트랩.

42. 설사 구두를 닦으러 보낸 동안이라 하더라도 수트 차림에 슬리퍼를 신고 있지 말아라.

43. 기온이 40도까지 올라간다 하더라도 수트 차림에 샌들을 신는 것은 금물이다.

44. 스니커는 당신을 어려보이게 해주지만 때로는 당신을 코메디언처럼 보이게도 한다.

45. 로퍼 역시 중요한 사업 파트너를 만나러 갈 때 신기엔 너무 캐주얼 하다.

46. 한 켤레 구두를 이틀 연속해서 신는 것은 무조건 피하는 것이 좋다.

47. 소위 말하는 '불광'을 이용해 구두를 닦는 곳에는 구두를 맡기지 마라. 구두 수명이 1/2로 단축된다.

48. 여자들은 당신 구두가 얼마짜리인지 귀신같이 알아챈다. 단, 관리를 잘한다면 귀신도 모른다.

49. 구두는 아무리 깨끗해도 지나치지 않다.

50. 구두는 운동화와 다르다. 구겨신지 마라.

51. 구두 소재는 반드시 천연가죽이어야 한다.

52. 뱀피로 만들어진 신발은 레니 크레비츠를 위한 것이지 당신을 위한 것은 아니다.

53. 악어 가죽 역시 마찬가지다.

54. 흰 색 신발은 조깅화만으로도 충분하다.

55. 어지러운 프린트의 실크 타이보다 검은 색 니트 타이가 훨씬 더 멋스럽다.

56. 타이를 고를 때 신경 써야 할 것은 컬러와 패턴만이 아니다. 반드시 얼마나 모양이 잘 만들어지는지 살펴보아야 한다. 매어보지 못하게 하는 타이 매장은 두번 다시 가지 마라.

57. 재킷 라펠 크기가 커지면 넥타이 매듭도 커져야 한다. 이 두가지는 함께 가는 것이다.

58. 넥타이는 세탁하면 금방 망가진다. 세탁하지 않아도 되도록 깨끗하게 매라.

59. 세로 줄무늬 타이는 사지 마라. 선물로 받았다면? 그래도 매지 마라.

60. 보우 타이를 맬 수 있는 옷은 원칙적으로는 턱시도 뿐이다. 랄프로렌에서 디스플레이한다고 해서 직접 하려는 시도를? 용기만은 지지.

61. 넥타이 끝은 젤대 벨트 가장 아랫부분보다 아랫쪽에 위치해서는 안된다.

62. 헤비 메탈 가수나 로커 얼굴이 프린트되어있는 넥타이는 스무살이 넘은 남자에겐 어울리지 않는다.

63. 자동차 안전벨트는 반드시 넥타이 밑에 있어야 한다.

64. 어떤 상황에서도 넥타이 끝부분을 셔츠 가슴 포켓에 구겨 넣지마라. 차라리 풀어라.

65. 넥타이 핀은 타이 뒷쪽 상표가 위치한 자리에 꽂아준다. 하지만 왠만하면 하지 마시라.

66. 브리프케이스는 비지니스맨 필수품이다.

67. 수트에 백팩을 메도 멋있는 건 정우성과 조인성 뿐이다.

68. 싸구려 브리프케이스를 들고 다닐바에는 100원짜리 서류봉투를 들고 다녀라.

69. 양말은 반드시 신어야 한다.

70. 양말 색은 바지 색에 맞춘다.

71. 표현을 하기 위해 일부러흰 양말을 선택한다면 굳이 말리지는 않겠다.

72. 장지갑을 바지 뒷주머니에 꽂는 것은 소매치기를 유혹할 때나 하는 짓이다. 머니 클립을 이용해 보자.

73. 바지 뒷주머니에는 잘 다려진 손수건을 넣어 두어라.

74. 잘 정돈된 헤어스타일은 값비싼 수트처럼 당신을 멋져 보이게 한다.

75. 헤어스타일은 적어도 한달에 한번, 가능하다면 한달에 두번 다듬어 준다.

76. 반지는 하나면 충분하다.

77. 장교 반지나 졸업 반지 등은 하나도 곤란하다.

78. 타이 핀이나, 반지, 커프링크스 등 장신구 컬러는 통일하는 것이 좋다.

79. 안경이 갑자기 부러질 때를 대비해서 사무실 서랍에 여벌의 안경을 준비해 놓는 것이 좋다. 수트를 입었다면 부드러운 표정을 지어라.

80. 안경은 벨트와 구두 색깔에 맞추는 것이 좋다.

81. 사람들은 당신의 수트가 얼마짜리인지 알아채기에 앞서 당신 코에 삐져나온 코털을 먼저 발견하게 된다.

82. 면도가 잘되어 있는 턱은 깨끗한 셔츠만큼이나 중요하다.

83. 간혹 턱은 깨끗하게 면도 하면서 코와 입 사이를 잊는 남자들이 있다. 거울은 장식품이 아니다.

84. 지저분한 손톱은 당신의 수트까지 싸구려로 전락시킨다.

85. 네일케어 숍에서 손톱을 정리하는 것도 나쁘지 않다. 메니큐어만 칠하지 않는다면..

86. 입가에 허옇게 일어난 각질은 어깨 위에 떨어진 비듬보다 더 심각하게 당신의 이미지를 손상시킨다.

87. 그렇다고 어깨위에 떨어진 비듬이 괜찮다는 건 절대 아니다.

88. 지저분한 100만원짜리 브랜드보다 깨끗한 10만원짜리 시장 더 멋지게 보일수 있음

89. 당신의 옷장에 적당한 여유 공간을 남겨두어야 수트가 숨을 쉴 수 있다.

90. 수트를 걸어둘 땐 반드시 나무로 된 수트용 옷걸이를 사용한다.

91. 수트 재킷 주머니에는 절대 손을 넣지 않는다.

92. 바지 주머니에도 가급적 손을 넣지 않는 것이 좋다.

93. 손이 시리면 장갑을 껴라.

94. 수트에 어울리는 모자는 훌륭한 액세서리가 된다. 그러나 스틱은 당신이 예순이 될때까지는 잡지 않는 것이 좋다.

95. 수트를 입었다고 신사가 되는 것은 아니다. 수트를 입을 때는 자신의행동이 수트차림에 어울리는 것인지 항상 생각해 보는 것이 좋다

프로그래머 십계명

1. 정보모음에 소홀히 하지 말고 설명서를 읽음에 게을리 하지 말지어다.

오늘 필요 없는 정보는 내일 필요하리라. 가장 가치 있고도 저렴한 지식은 책 속에 있느니라. 서점과 동료의 책꽂이에 무엇이 꽂혀 있는지 때때로 살피어라. 무심코 흘렸던 종이 한 장이 너의 근심을 풀어 주었으리라. 설명서는 충분히, 꼼꼼히 읽을지어다. 모든 의문은 설명서를 안 보는데서 생기니라. 그렇더라도 모두 다 읽을 필요는 없느니라. 많은 정보가 능사는 아니니라. 정보의 가치를 찾는 법부터 배우라. 세상엔 너무나 많은 자료와 정보가 넘쳐난다.

일일이 모두 끌어 모을 생각을 하기 보단 정보를 하나로 꿰는 법부터 먼저 배우는 것이 너의 근심에서 쉽게 벋어나게 하는 방법이 되리라. 일을 시작하기전에 필요한 정보를 꼼꼼히 먼저 챙기는 법부터 배워라. 너희는 먼저 개발 의뢰서를 꼼꼼히 읽을지어다. 만약 개발 의뢰서가 없다면 발주자에게 요구할 지어다. 개발 의뢰서 없는 프로그램은 존재하지 않으니라.

2. 너의 PC가 안전하다고 믿지 말지어다.

5분 후에 정전이 되고 내일 너의 하드가 맛이 가리라. 그러니 너의 소중한 소스코드는 정기적으로 여러 군데에 단계별로 백업해 두어라. PC는 평상시엔 안전하다. 그런 실수를 저지르는 것은 네 자신이거나 아니면 외부적인 요인에 기인한다. 항상 백업을 철저히 해두며 백업에 백업까지도 챙겨두라. 그리고 백업을 했다면 리스트를 작성하라. 쓸데없는 백업은 백해 무익하나니 리스트를 항상 유지할 지어다. 너희는 노트를 옆에 끼고 살 지어다. 노트는 너의 생명이며, 너희가 기억하지 못하는 모든 것을 상기시켜 줄지어다.

3. 변하는 수를 다룰 때에는 늘 조심할지어다.

정수가 절대로 그 한계를 넘지 않으리라 가정하는 것은 어리석음이라. 127 ,-128 ,255 ,32767 ,-32768 ,65535, 이 숫자들을 너의 골수에 새기어라. 0.0은 0이 아니니 실수는 원래부터 결코 정밀하지 않느니라. 부호 없는 것과 있는 것을 어울리거나 정수끼리 나눌 때에는 늘 조심하여라. 변수는 프로그램의 근원, 프로그램을 작성할때 가장 유의 할 것이 바로 변수의 이름 짓기니라. 이름보고도 성격을 알 수 있게 해두라. 그러나 변수는 성질이 드러우니 변수에 성격을 부여할때는 조심스럽게 할지어다. 너희는 코딩하기 이전에 계획을 할 지어다. 이는 프로그래머가 코더가 아닌 것이니라.

4. 무슨 일을 반복시킬 때에는 처음과 끝에 유의할지어다.

너의 컴퓨터는 1보다는 0을 좋아 하니라. 배열의 첨자가 그 범위를 넘지 않을지 손 댈 때마다 따져 보아라. 수식에 1을 더하거나 뺄 때에는 늘 긴장하라. 너의 프로그램은 단지 한 번 덜해서 틀리고 한 번 더해서 다운되느니라. 프로그램을 작성할 땐 계산, 판단, 비교를 그 모든 걸 컴에게 되도록 맡기지 말라. 네 손으로 미리 계산하고 그 결과를 사용하는 방법이 최선이니라. 컴퓨터는 의지가 없나니 네가 잘못하든 잘하든 아무런 상관이 없느라. 너희는 머리가 악세사리가 아님을 기억하고 항상 생각하고 항상 노트에 적을 지어다.

5. 항상 모든 경우의 수를 고려하고 섣불리 생략하지 말지어다.

절대로 일어 나지 않을 일은 반드시 일어나고, 가장 드물게 일어날 일이 가장 너를 괴롭히리라. 그러하니 언제나 논리에 구멍이 없는지 꼼꼼히 따져 보고, if를 쓸때에는 else 부터 생각하라. 논리적인 오류는 성급함에서 생기나니 처음엔 항상 원리와 원칙을 지키라. 생각은 네가 하라 그리고 그 결과를 컴에게 시켜라. IF를 쓰기전에 규칙을 세우라. 먼저 IF의 결과에 대한 규칙부터 세우고 따져라. 그리고 논리적인 계산을 IF문장안에서 하지 말라. 하나의 IF문장속에 수많은 논리연산은 버그의 원인이니라. 어느 정도의 프로그램에 대한 윤곽이 잡히면 프로토 타입을 만들지어다. 프로토타입은 프로그램에 대한 시뮬레이션이며 발주자의 요구를 빨리 수용 하는 방법이니라.

6. 함수 안에서 매개 변수값은 결코 믿지 말지어다.

지금 그 매개 변수가 결코 가질 수 없다는 값을 내일부터는 가지리라. 그러하니 매개 변수 값이 올바름을 항상 검사할지어다. 그렇더라도 처리 속도가 문제가 되는 경우는 예외이니라. 함수도 하나의 독립적인 프로그램이란 것을 잊지말며, 네가 프로그램을 작성할땐 모든 함수가 되도록이면 독립적으로 돌아가도록 할지어다. 함수의 매개변수는 항상 그 옆에 작은 코맨트와 초기화를 잊지말라. 처음부터 속도문제를 생각하지 말라. 모든 루틴을 최적화 할 수는 없다. 전체 프로그램중에 단 20%가 전체 실행시간에 80%를 점유한다. 프로토 타입에 대한 발주자의 의견을 꼼꼼히 들을 지어다. 이는 발주자에 대한 신뢰도의 척도니라.

7. 오류를 알려 주는 기능은 있는 대로 모두 활용할지어다.

컴파일러의 경고는 모두 켜두어라. 경고는 곧 오류이니라. 오류를 알리는 함수의 결과를 확인하지 않는 우를 범하지 말지어다. 모든 파일 입출력과 모든 메모리 할당은 조만간 실패할 것이라. 컴파일러가 모든 경고기능을 동원해도 알려주지 않는 것은 많다. 중요한 건 오류가 생기기전에 규칙을 지켰는지 생각하라. 파일의 입출력과 메모리의 할당은 항상 쌍으로 생각해서 열었다면 닫아주고 할당받았다면 돌려주라. 프로그램의 매인턴앤스를 게을리하지 말지어다. 이는 프로그램 만드는 일 보다 중요한 일이니라.

8. 한 번의 수정과 재컴파일만으로 연관된 모든 것이 저절로, 강제로 바뀌도록 할지어다.

어떠한 것을 수정했을 때에 연관된 것이 따라서 변하지 않는다면 그것이 곧 벌레이니라. 컴파일러로 하여금 매개 변수 리스트를 완전하게 검사하도록 하고 언젠가 손대야 하거나 따라서 변해야 하는 수치는 전부 매크로로 치환하며, 형 정의를 적극 활용하여라. 이미 완벽한 루틴을 손대지 말라. 프로그램이 무너지는 가장 첫번째이유는 도미노 현상 때문이나니 한번의 수정과 재컴파일로 쓸데없는 것을 손대게 하지 말라. 컴파일러가 매개변수 리스트를 챙기지 말게 하라. 프로그램에 들어가기 전엔 미리 함수명과 매개변수 리스트를 만들어라. 너희는 프로그램의 도큐멘트를 만드는 일에 게으르지 말지어다. 이는 사용자가 너의 프로그램에 대해서는 바보이기 때문이니라.

9. 사용자가 알아서 잘 써 주리라고 희망하지 말지어다.

너의 프로그램은 항상 바보만이 쓰느니라. 사용 설명서를 쓸 때에는 결코 빠뜨리지 말아라. 빠뜨린 만큼 사용자는 너를 괴롭힐 것이니라. 사용자는 나쁜놈이다. 쓸데없는 짓을 잘한다. 무식하다. 인간성도 더럽다. 대부분이 바보며 가끔 똑똑한 사람도 있는데 그 놈은 더 하다. 모든 것을 설명하지 말며 온갖기능을 가진 하나의 프로그램을 작성하려 들지 말라. 많은 기능이 필요한 프로그램은 나누어서 작게 따로 만들어주라. 너희는 공부하는데 게으르지 말지어다. 자고나면 새로운 하드웨어와 새로운 소프트웨어가 나오기 때문이니라.

10. 매사에 겸손하고 항상 남을 생각할지어다.

가장 완벽한 프로그램일수록 가장 완벽하게 숨은 벌레가 있느니라. 네가 이 세상 최고의 프로그래머라고 떠들며 자만할 때, 옆집 곳간에서는 훨씬 더 뛰어난 것을 묵묵히 만들고 있느니라. 아무렴 프로그래밍은 혼자 잘나서 할 게 아니니, 너로 인해 다른 사람들도 더불어 잘 되면 그 얼마나 좋은 것이냐. 프로그래머는 논리적으로 생각하여야 하지만 프로그램은 비논리적으로 작성하라. 프로그래머가 경지에 들면 누가 누가 잘하는지 알수가 없는 법, 또한 프로그래머로서의 '무지'에 대해서 잊지마라. 프로그래머의 '무지'는 생략하고, 선택하고, 단순화시키고, 명백하게하는 것이니라. 항상 새로운 아이디어와 새로운 생각으로 무장하라. 그리고 나누라 나누는곳에 기쁨있나니 너희는 모든 프로그램에 대해서 위의 프로시줘를 따를 지니라.

iPhone 사용자에 관한 연구 보고서


루비콘 컨설팅(Rubicon Consultin) 기사다.

이메일을 가장 많이 사용한다. 특히 쓰기보다 읽기를 중요하게 생각한다. 
웹브라우징을 많이 사용한다.
사용자의 1/3은 세컨드 폰으로 음성/이메일을 사용한다.
절반은 30세 미만, 15%는 학생이다.
대부분의 사용자가 애플의 소비자였다.


루비콘 컨설팅의 보고서

[Rubicon Consulting] The Apple iPhone: Successes and Challenges for the Mobile Industry



해왔던 일들 정리

SI에 몸담고 있다보면 여러가지 일을 격게된다.
나는 개발자라고, 프로그램을 만든다고 말을 하고 일을 하고 있지만 사람을 기계의 부품 정도로 여기는 풍조나 좋은 프로그램이 아닌 적당히 굴러갈 수 있는 프로그램 제작에 관심을 기울일 수 밖에 없는 상황을 만나기도 한다.
그리고 그것은 가끔의 상황이 아닌 자주 일어나는 일상이 되어버리곤 한다.
컬럼니스트로 유명한 김국현씨의 블로그에 쓰라린 이야기가가 있다.
이러한 이야기는 김국현씨의 컬럼외에도 수없이 등장하는 얘기거리지만 다시 생각할 수 있는 시간을 만들어주었다.

개발이라는 숙명을 갖고 있을지도 모른다. 나 또한 그 운명을 기쁘게 생각한다. 나는 기술이 사람을 행복하게 만들 수 있다는 확신을 갖고 있기 때문이다.
그러나 과연 내 손으로 만들어진 것들이 내 신념이 그려졌던 것일까? 되돌아보고 싶었다.
지금까지 2년이 조금 넘는 세월동안 한 일들을 곰곰히 생각해봤다. 과연 이 일을 해서 사람을 행복하게 했을지 그렇지 않을지.
LGChem Beijing POP 구축 - 주어진 일을 했다. 받을 그들은 이 일로써는 행복이 무언지도 몰랐다.
LG 화학 POP 개선 - 주어진 일을 했다. 단지 일을 위한 일이었다.
LG 전자 MES - 사용자들은 프로그램이 좋던 나쁘던 사용을 강요받았다. 그래도 내가 만든 건 사람이 편하게 할 지도 몰랐다.
LGChem Poland POP 구축 - 주어진 일을 했다. 그래도 그들이 행복하길 바랬지만 그렇지 못할 것이다.
KT&G PDA 개선 - 분명 이전보다 좋아진 것을 만들었다. 개선된 것에 대해선 행복을 느꼈을 것이다.
LGChem Taiwan POP 구축 - 주어진 일을 했다. 이것도 일을 위한 일이었다.

지금도 생각은 바뀌질 않는다. 기술은 사람을 기쁘게 하기 위해서 쓰여야 한다. 그리고 난 그 기술을 사용하며 더욱 갈고 닦고 싶다.
지금 내가 탄 배가 그곳으로 나아가고 있을까? 이것은 그렇지 않다는 확신이 매번 든다. 그리고 계속 그렇지 않다는 증거만을 갈무리하여 머리속에 남겨나가고 있다.
배에서 내리기 위해선 육지가 필요하다. 그렇지 않으면 다른 배가 필요하다. 내 손에는 깃발이 있고 어디엔가 꽂고 싶다.

때와 장소를 찾자.