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:
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:
태스크와 매니저 (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 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.
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.
클라이언트로부터 생성된 이벤트를 핸들링하는 태스크는 특정 규칙에 의해 실행이 보증됩니다. 한 클라이언트의 이전 이벤트가 모두 끝나기 전에는 그 이후 발생될 클라이언트 이벤트를 핸들링하는 태스크가 실행하지 않을 것입니다. 자식 태스크는 그 부모 태스크를 주목하고 있으나 형제 태스크에 관하여는 그렇지 않습니다. 이것은 자식 태스크의 실행이 부모 태스크의 실행이 끝나기 전 까지 시작되지 않는 것을 의미합니다. 그러나 형제 태스크에 관한 순서에 대한 보증은 존재하지 않습니다.
중요 : 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:
특별히 중요한 경우는 태스크의 실행주기가 좀 더 긴 것을 블럭하기 위해 시스템 콜을 호출하는 경우입니다. 이 경우 반드시 정교한 커스텀 매니저를 개발하는 것이 필요합니다.
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.
다른 태스크에 요청될 수 있는 자원들의 과도한 요청으로 블럭되지 않기 위해 태스크는 짧은 주기로 제공됩니다. 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.
특별히 중요한 경우는 태스크의 실행주기가 좀 더 긴 것을 블럭하기 위해 시스템 콜을 호출하는 경우입니다. 이 경우 반드시 정교한 커스텀 매니저를 개발하는 것이 필요합니다.
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:
반대로 말하면, 매니지드 오브젝트의 상태가 변경되지 않을 것이라면 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.
데이터 매니저는 오브젝트 스토어라고 하는 오프젝트의 풀에 저장된 매니지드 오브젝트의 퍼시스턴스 셋을 보존하고 있습니다. 보통 자바 오브젝트와 같이, 각 매니지드 오브젝트는 데이터와 데이터를 처리하기 위한 메소드를 포함합니다. 매니지드 오브젝트를 작성하려면 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:
일반적으로 매니지드 오브젝트는 세가지 타입입니다.
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 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 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.
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.
Of all these considerations, the third is the most critical to a well-running PDS application.
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.
외부에서 이벤트가 발생할 때 매니저에 의한 이벤트 핸들러가 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 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 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:
룸 매니지드 오브젝트의 매니지드 오브젝트를 구성하는 월드 상위에, 소드 매니지드 오브젝트와 한 상의 플레이어 매니지드 오브젝트가 있습니다. 그러나 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를 특별하게 하는 두가지가 있습니다.
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:
These two properties combine in the following way:
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:
첫번째로 오브젝트 스토어에 있는 매니지드 오브잭트가 얼마나 되겠습니까?
그 때답은 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.
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.
그렇다면 사용자가 로그인 할 때 어떤 작업을 수행하여야 하겠습니까?
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 OBJECTADD 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.
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:
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.
튜토리얼의 폴더를 보시면 컴파일된 tutorial.jar 파일에서 모든 튜토리얼의 예제를 찾을 수 있습니다. 모든 각각의 예제는 오브젝트 스토어의 데이터를 포함한 데이터 서브디렉토리가 포함되어 있습니다.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. |
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:
- PDS가 설치되어 있는 경로로 SGSHOME 환경변수를 추가합니다. Add the environment variable SGSHOME to your environment and set it to the directory where you installed the Project Darkstar Server.
- SGSHOME 환경변수를 path에 추가합니다. Add SGSHOME to your execution path, where SGSHOME is your PDS install directory.
- 쉘을 엽니다. Open a Unix shell, a Windows command window, or whatever you do to get a command line on your development system.
- 튜토리얼 디렉토리로 이동합니다. 유닉스 환경이라면 다음과 같이 명령을 낼 것입니다. Change your working directory to the tutorial directory of your PDS. In Unix, the command might be something like this:
cd ~/sgs/tutorial
- 다음을 타이핑 하십시오. Type the following:
- For Unix:
sgs.sh tutorial.jar HelloWorld.properties
- For Windows:
sgs tutorial.jar HelloWorld.properties
- Add the environment variable SGSHOME to your environment and
set it to the directory where you installed the Project Darkstar Server. - Add SGSHOME to your execution path, where SGSHOME is your PDS install directory.
- Open a Unix shell, a Windows command window, or whatever you do to get a command line on your development system.
- Change your working directory to the tutorial directory of your PDS. In Unix, the command might be something like this:
cd ~/sgs/tutorial
- 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 입니다.
|
HelloWorld 재시작하기 (Rerunning HelloWorld)
PDS를 중지하고 HelloWorld 어플리케이션을 다시 동작시키려 한다면, "HelloWorld!" 출력이 나오질 않고 에러를 보게 될 것입니다. 이는 오브젝트 스토어에 이전에 동작하고 있던 AppListener가 이미 존재하므로 호출되지 않았던 초기화를 하여야 합니다.
HelloWorld를 다시 동작하고 싶다면, 여러분은 오브젝트 스토어의 내용을 지워야 합니다
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:
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. */ logger.log(Level.INFO, "Hello World!"); } 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 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. { /** The version of the serialized form of this class. */ TaskManager taskManager = AppContext.getTaskManager(); } return null; } logger.log(Level.INFO, "HelloTimer task: running at timestamp {0,number,#}", } } |
이제 반복 이벤트를 만들었고 우리는 정지되고 재시작할 때 어떤 동작을 일으키는 첫번째 어플리케이션을 만들었습니다. 태스크 등록은 지속적이어야 합니다. 이는 다운되거나 재시동할 때 상태를 지속할 수 있습니다. 이 동작을 살펴보기 위해 서버를 정지시키고 재시작해보십시오.
주기적인 태스크는 오브젝트 스토어 내에 여러분의 오브젝트와 함께 저장된 정보입니다. 따라서 여러분이 데이터 디렉토리를 삭제하고 초기화되어있지 않은 처음의 상태로 되돌린다면 주기적인 태스크 또한 초기화될 것입니다.
이 행동은 여러분이 서버가 항상 동작하는 것 처럼 여러분의 코드를 작성하는 것을 허용하며, 여러분이 중요한 로직을 포함한 주기적 태스크의 동작 메소드 내의 경과된 시간을 점검하는 것에 주의를 요합니다.
어떻게 마지막 실행 시간 추적을 유지하는냐에대한 주제가 다음 레슨의 주제입니다.
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 홈페이지에서 다운받으세요