首页 > Read smartfoxserver

Objects_mod_v2package,Read smartfoxserver

互联网 2021-01-21 06:05:20
在线算命,八字测算命理

» How to use the documentation and examples

This document provides a quick tutorial on how to obtain the best from the provided SmartFoxServer 2X (aka SFS2X) documentation. Our first recommendation is to consult the articles provided in this section before proceeding with the examples and technical docs. Whether you are a seasoned SmartFox developer or you have just moved your first step in the multiplayer world, you will find the initial articles particularly useful to get started. The Getting Started section will guide you in the client and server setup phase while the Advanced Topics section provides an insight on the new Extension system, the server API and lots more.

» The Examples

We're strong advocates of our patent-pending Learning-While-DoingTM methodology. Of course we are kidding about the patent thing, but we are serious about learning by following a series of examples of increasing complexity. SmartFoxServer comes packed full with simple and advanced examples made in ActionScript 3, Objective-C, Java and C#, where you can learn the very basics and quickly move to the more interesting and powerful features. Each example comes with source code for both client and server side and attempts to build on the previous examples in the serie to provide a sense of continuity.

» The Java/AS/C# doc

Once you have gotten an idea of what the new SmartFoxServer 2X can do for you and tested some of the examples, you will be probably eager to start playing with the API and prototyping some ideas. This is of course the moment where the ActionScript/C#/etc docs (client) and Javadoc (server) will come in handy. Below follows a list of tips to get started with API without getting lost in the host of packages and classes that you will encounter.

The client side

The client-side API main object is the SmartFox class, found in the com.smartfoxserver.v2package. This is the main entry point of the client API. This object allows you to manage your event listeners, start a connection and send requests via the send() method. Another important section of the client framework is the com.smartfoxserver.v2.requestspackage. Here you will find dozens of different

classes, each one representing a specific client request such as LoginRequest, JoinRoomRequest, SendPublicMessageRequest and many more. There are also two separate subpackages, game and buddylist, where you can find advanced API for building games and managing buddy lists respectively.

The server side

The classes that act as entry points to the server-side API are found in thecom.smartfoxserver.v2.api packge. Specifically: · SFSApi: here you find dozens of methods for the most common server operations: login, create/remove rooms, send messages, join users, set variables, etc. · SFSGameApi: game specific API · SFSBuddyApi: buddy list specific API

IMPORTANT NOTE While browsing the Javadoc you might at times find fields or methods with little to no documentation. Besides a few exceptions due to the current state of the documentation, this is done on purpose to indicate that these methods shouldn't be used directly. The API classes already use these lower level methods for you behind the scenes and you don't have to deal with them directly.

Programming to interfaces

In general, throughout the client and server API, you will notice that all important classes of the framework are backed by an interface. For instance: · · · · · · · · SFSZone implements Zone SFSRoom implements Room SFSUser implements User SFSBuddy implements Buddy SFSObject implements ISFSObject SFSArray implements ISFSArray SFSRoomVariable implements RoomVariable SFSUserVariable implements UserVariable

You will also notice that the whole framework uses these interfaces in almost each and every method signature or return type. We would like to encourage and emphasize the use of these interfaces in your code too. The reason is that this helps swapping different implementations easily and without side effects.

In future release we might introduce new implementations to these interfaces which will affect your code minimally if you stick to this habit as much as possible.

» SmartFoxServer 2X platform stack

In this document we take a bird's eye view at the SmartFoxServer 2X platform and briefly discuss each of the components in the stack. Starting at the core of the server we find the network engine (codenameBitSwarm) which is the third incarnation of the original socket engine built for SmartFoxServer 1.x. This layer provides unique features to SFS2X that are typically not found in other competitor's solutions (we discuss all the technical details in a separate comprehensive white paper). In essence BitSwarm provides TCP/UDP connectivity, Session management, network security tools, the HRC (High-Resilient-Connections) system, clustering services, monitoring and more, using an highly scalable non-blocking design.

» Core Services and Managers

The lower layer of SmartFoxServer 2X provides a number of essential services and managers such as configuration services, logging, security, task scheduling, zone/room/user management, buddy lists, banned user management, remote administration, JMX, email services, http services, database integration and lots more. All these services coalesce in a well organized set of Server API that provide developers a host of high level functionalities acting as the building blocks of their applications.

» Server API and Extensions

Extensions are the mechanism by which developers can plug their own application logic in the system and leverage the Server API. In a similar way to a servlet container, the SFS2X framework provides an efficient way to handle custom client requests and server events. Rapid development is guaranteed by a rational workflow that provides hot-redeploy, automatic dependency discovery and a well-thought class loading mechanism.

» Client-side API

The topmost element in the stack is the public API, which is exposed directly to the clients. Any application speaking the SFS2X protocol can access this API and interact with the server very easily and securely. The client libraries also provide a consistent framework across all supported platforms, making it very easy to create multiple clients in Flash, Unity3D, iPhone and iPad, Android devices, etc. Launching a new game, interacting with friends in the buddy-list or moderating a chat is a matter of a few method calls. Additionally the client can access all the extra functionalities exposed by the custom Extensions, offering a limitless set of possibilities.

» SmartFoxServer 2X features overview

SmartFoxServer 2X takes the core SmartFoxServer philosophy and expands it in new directions, introducing hundreds of improvements and focusing on a few precise goals: simplicity of use, versatility and performance. · Simplicity of use: we have cleaned up all unnecessary complexities of previous versions of SmartFoxServer, making the client/server API more intuitive, streamlined and richer.

·

Versatility: we have re-designed parts of the architecture to make it largely more flexible. In turn this helps developers to design their applications more conveniently and with a finer-grained level of control.

·

Performance: SmartFoxServer is already known for its top-notch performance. SFS2X continues this trend employing version 3.0 of its custom network engine (codename BitSwarm) which provides binary protocol with dynamic compression, UDP support, JMX monitoring, transparent reconnection system, high performance http-tunnelling and heaps more... It's also been benchmarked against

well known all-purpose socket engines (Mina and Netty) showing top performance under all conditions.

» Server core features

Fully multi-platform Visual config and management Runs on Windows (32/64), Linux (32/64) and other Unixbased variants. No more messing around with XML. All configuration is done via the new Administration Tool (aka AdminTool) which also provides advanced realtime statistics, runtime Zone/Room/User management, Ban management and lots more. The new AdminTool also offers a modular architecture allowing integration of third party plug-ins. BitSwarm is version 3.0 of our ultra-light, high performance TCP/UDP socket engine providing unique features for multiplayer games:

·

High performance network engine

· · · · · ·

highly scalable non-blocking design which beats most of the all-purpose socket engines in both stability and performance further improved architecture and performance with lightweight memory footprint low latency and minimized unnecessary buffer/memory copy core support for Non-Player Characters (NPC) at Session level (socket-less) pluggable Dropped Packet Policy manager configurable threading model high performance HTTP-Tunnelling for clients unable to establish socket connections (using our proprietary BlueBox technology) transparent re-connection system which allows sessions to transparently reconnect to the application without loosing their state after and abrupt disconnection

·

Highly efficient protocol

New binary protocol providing major improvements in server and network performance, delivering an average of 5x in encoding times and 6x in packet size reduction. Many new security features are built-in:

·

Enhanced security

login data is always transmitted via secure login mechanism to avoid password detection at the

·

·

·

·

socket level the Permission Manager allows to create any number of User profiles that allow different clients to access only a portion of the server features according to your custom rules new Anti Flood Filter protects the server from flooding attacks and offers a fine grained configuration for each possible server request. the improved Words Filter now supports regular expressions and white-list/black-list modes, offering a more flexible tool to filter profanities. The filter can be applied to public/private messages, Room and User names and accessed via simple API from server side code. It also provides events for logging and further customizations an advanced Banning System provides tools for manual and automatico banning, persistence and fine-grained settings for each banned User IP Filtering allows to control the maximum number of sockets coming from the same IP address

·

Painless dependency management

Any additional Java dependency (db drivers, development libraries, etc) can be deployed in a snap without messing with the classpath. Just drop the files in the right folder and you are ready to go.

Effortless deployment Whether you deploy in the cloud or on a regular server, the setup process is simplified by the unfied installer supporting all 32/64bit architectures and providing installation of the software as a service. BlueBox 2.0 Enhanced and now fully integrated BlueBox technology provides unbeatable performance for HTTP tunnelling. Provides connectivity to all those clients behind restricted network conditions. We strive to provide the best balance between performance and rock-solid relaiabilty. The server engine has been stress tested for thousand of hours under high pressure including crushing traffic, unreal request load, bandwidth and resource constraints, unstable networks with high packet loss, flooding attacks and more.

Outstanding stability

» Platform features

Rich client-server framework

Rich client and server Framework with dozens of functionalities out-of-the-box: Session management, Room management, chatting, advanced lobby and Game Matching features, persistent Buddy Lists, moderation, server Variables and lots more. Plug your Java server-side code to create complex interactions, advanced game logic, custom credentials checks and tons more. Server-side Extensions are the most powerful tool at your disposal to build your application. From a simple turn-based game to a massive virtual world or a fast real-time action game. Additionally you can use scripts to quickly test your idea or create a prototype and then convert it to fast Java production code. SFS2X features enhanced configuration, Room Groups, improved Room management, fine-grained event configuration, better security filters integration and lots more. Now Rooms support renaming, locking, resizing, hiding and more. A new set of API provide powerful tools for matchmaking, game invitations and challenges, public and private games, custom player and game searches based on any numbers of custom parameters and tons more. A refined Buddy List system provides richer server-side events, pluggable persistence, temporary buddies, online/offline status, nicknames, Buddy messages and more. The HRC system provides transparent reconnection to unstable client connections allowing players to re-enter a game without losing their state. Indispensable for any betting or contest-based game. Vastly improved, simplified and better organized serverside framework including:

· · · · ·

Fully extensible

Vastly improved Zone architecture

Enhanced Room features New Game API

BuddyList 3.0

High-ResilientConnections (HRC) System Redesigned Java Extension API

new server events simplified development flow and deployment cycles hot-redeploy of Java Extensions (no server restart needed) simplified Class Loader management simplified classpath management (auto-detection

of jar files, no need to mess with the core classpath) Match Expression Engine Allows clients and server to execute complex queries on Users and Rooms in order to extract very specific items. Matching expressions are chain-able and easily built using a natural syntax that mimicks programming languages. Highly flexible data structures used throughout the framework, they represent the client/server message contents at a high level. Additionally they allow to finely tune how each bit of data is encoded at the wire level, providing support for 20 different data types. Room Variables, User Variables and Buddy Variables allow to keep custom state for each entity on the server providing auto-updates to the interested clients. New advanced capabilities have been added such as the use of nested objects, global Room Variables and serveronly Variables. Unique and simplified way of accessing data sources based on the ODBC and JDBC standards including configurable connection pooling. Provides remote management via the standard Java Monitoring interface, allowing to gather statistics and other runtime status and integrate them with external control panels and monitoring software. Multi-language support for error-messages. All error messages are code-based. We provide English based description of each error and developers can add their own translations. Improved exception reporting with friendly messages and verbose description of issues. The use of Log4J allows improved flexibilty and provides tools for log analysis even from a remote location.

SFSObject/SFSArray

Server Variables

Database integration

JMX management

Localized error messages

Logging and reporting

» Client features

Truly multi-platform SmartFoxServer 2X supports all major web-based and mobile technologies with a set of consistent API, allowing to create real multi-device access to your games and virtual worlds. Support includes:

· · · · · ·

Flash platform (Flash/Flex/AIR) Unity3D 2.x and 3.x (standalone and web-player) iPhone/iPad via Unity3D API iPhone/iPad via native Objective-C API Java2 SE and Android Silverlight (*)

High level functionalities

The API hide all the low-level networking details, packet serialization, etc, providing very high level tools to the developer. Creating a Room, launching a game, starting a private chat, managing the buddy list, etc, are all performed in a few lines of code, without getting in the way of your application logic. Room management has been improved under many aspects with respet to previous SmartFoxServer version:

·

Largely improved Room management

· ·

· ·

better Room List management; the room list is now obtained at login time, eliminating an extra call to the server for the room list (getRoomList) clients do not need to be in a Room to interact with the server anymore Rooms not managed by the local Room Manager can now be joined by just knowing one of their IDs (room id or name) ability to hide Rooms thanks to the isHidden flag Room Variables can be seen from outside the parent Room by setting the isGlobalflag the client can subscribe/unsubscribe to specific Room Groups to avoid listening for too many Room events

·

Advanced connection events

The CONNECTION_RETRY and CONNECTION_RESUME events signal a temporary disconnection and relative reconnection success to the server. This allows the developer to know when the HRC system is performing the transparent auto-reconnection and freeze their game logic or provide feedback to the user accordingly.

Disconnection reasons The API can now be aware of various reasons of a disconnection such as:

· ·

manual: disconnection originated by the client itself kick: user was kicked by the server or an

· ·

administrator ban: user was banned by the server or an administrator idle: user was disconnected because he has been inactive for too long unknown: a network error occurred

·

Flash components Simplified workflow

Included SmartFoxBits 2.0 UI components for quick design of Flex and Flash multiplayer GUIs. Simplified workflow and improved architecture allow faster development and easier coding.

» The SmartFoxServer 2X client-server protocol

SmartFoxServer 2X uses a highly effiecient binary protocol that makes the server performance outstanding under different aspects. Messages are light on the bandwidth and fast to parse by the client and server engine. Additional on-the-fly compression allows for extra size reduction without impacting the general performance. This is an example of object transmission using the XML-based SmartFoxServer 1.x protocol and the SmartFoxServer 2X binary protocol: » SmartFoxServer 1.x

var testObj:Object = {} testObj.smallInt = 15 testObj.medInt = 120700 testObj.bigInt = 10900800700 testObj.doubleVal = Math.PI testObj.muppetNames = ["Kermit", "Fozzy", "Piggy", "Animal", "Gonzo"] testObj.fruits = [ {name:"Apple", color:"red", inStock:true, price:1.50}, {name:"Lemon", color:"yellow", inStock:false, price:0.80}, {name:"Apricoat", color:"orange", inStock:true, price:1.90}, {name:"Kiwi", color:"green", inStock:true, price:2.30} ]

This is the message dump, which takes 1027 bytes:

<msg t='sys'><body action='asObj' r='1'><![CDATA[<dataObj><var n='smallInt' t='n'>15</var><var n='medInt' t='n'>120700</var><obj t='a' o='fruits'><obj t='o' o='0'><var n='inStock' t='b'>1</var><var n='color' t='s'>red</var><var n='price' t='n'>1.5</var><var n='name' t='s'>Apple</var></obj><obj t='o' o='1'><var n='inStock' t='b'>0</var><var n='color' t='s'>yellow</var><var n='price' t='n'>0.8</var><var n='name' t='s'>Lemon</var></obj><obj t='o' o='2'><var n='inStock' t='b'>1</var><var n='color' t='s'>orange</var><var n='price' t='n'>1.9</var><var n='name' t='s'>Apricoat</var></obj><obj t='o' o='3'><var n='inStock' t='b'>1</var><var n='color' t='s'>green</var><var n='price' t='n'>2.3</var><var n='name' t='s'>Kiwi</var></obj></obj><var n='bigInt' t='n'>10900800700</var><obj t='a' o='muppetNames'><var n='0' t='s'>Kermit</var><var n='1' t='s'>Fozzy</var><var n='2' t='s'>Piggy</var><var n='3' t='s'>Animal</var><var n='4' t='s'>Gonzo</var></obj><var n='doubleVal' t='n'>3.141592653589793</var></dataObj>]]></body></msg>

» SmartFoxServer 2X This is the message dump, binary protocol, no compression. Size is 351 bytes, almost 1/3 of the XML version.

Binary Size: 351 12 00 06 00 09 64 6F 75 62 6C 65 56 61 6C 07 40 09 21 FB 54 44 2D 18 00 06 66 72 75 69 74 73 11 00 04 12 00 04 00 04 6E 61 6D 65 08 00 05 41 70 70 6C 65 00 05 63 6F 6C 6F 72 08 00 03 72 65 64 00 07 69 6E 53 74 6F 63 6B 01 01 00 05 70 72 69 63 65 06 3F C0 00 00 12 00 04 00 04 6E 61 6D 65 08 00 05 4C 65 6D 6F 6E 00 05 63 6F 6C 6F 72 08 00 06 79 65 6C 6C 6F 77 00 07 69 6E 53 74 6F 63 6B 01 00 00 05 70 72 69 63 65 06 3F 4C CC CD 12 00 04 00 04 6E 61 6D 65 08 00 08 41 70 72 69 63 6F 61 74 00 05 63 6F 6C 6F 72 08 00 06 6F 72 61 6E 67 65 00 07 69 6E 53 74 6F 63 6B 01 01 00 05 70 72 69 63 65 06 3F F3 33 33 12 00 04 00 04 6E 61 6D 65 08 00 04 4B 69 77 69 00 05 63 6F 6C 6F 72 08 00 05 67 72 65 65 6E 00 07 69 6E 53 74 6F 63 6B 01 01 00 05 70 72 69 63 65 06 40 13 33 33 00 08 73 6D 61 6C 6C 49 6E 74 02 0F 00 06 6D 65 64 49 6E 74 04 00 01 D7 7C 00 0B 6D 75 70 70 65 74 4E 61 6D 65 73 10 00 05 00 06 4B 65 72 6D 69 74 00 05 46 6F 7A 7A 79 00 05 50 69 67 67 79 00 06 41 6E 69 6D 61 6C 00 05 47 6F 6E 7A 6F 00 06 62 69 67 49 6E 74 05 00 00 00 02 89 BD 04 BC [email protected] .!.TD-...fruits. .......name...Ap ple..color...red ..inStock....pri ce.?........name ...Lemon..color. ..yellow..inStoc k....price.?L... ....name...Apric oat..color...ora nge..inStock.... price.?.33.....n ame...Kiwi..colo r...green..inSto [email protected] ..smallInt....me dInt....|..muppe tNames.....Kermi t..Fozzy..Piggy. .Animal..Gonzo.. bigInt.........

The next dump is with compression enabled: size is 239 bytes, an extra 32% reduction. Larger message can benefit a lot more than this, especially those containing lots of String data or complex nested objects. In that case the size reduction can be in the order of 2300%. In total the SFS2X request is 4.2 times smaller than the previous version and it is safe to assume that in general that new protocol provides a bandwidth reduction in the order of 36x.

Binary Size: 239 78 DA 6D CE 41 4A C3 40 14 C6 F1 2F 75 66 9A E2 42 AD 1B B7 1E C0 55 0E 60 03 A2 48 8B 08 15 F7 69 F2 1C 06 67 E6 85 49 42 49 F0 02 DE 49 C1 03 E8 DA 83 B8 74 63 DC A4 59 74 F7 78 F0 FB BF 37 87 C2 AC E0 66 63 E9 31 B3 D3 C5 EC FC F7 E1 EA E2 0C EA 29 34 A6 AE 4E 20 E6 10 10 3E 73 14 43 A6 65 69 09 32 67 CB 21 C6 41 A0 02 53 E3 D7 35 E7 CF 51 04 59 06 93 93 BA FC 00 C6 68 45 8E FD 80 54 4B D6 F2 76 E7 30 B8 D5 E7 D7 C8 C5 E9 FF 9A B3 7A 47 39 64 5E D3 BE 93 3F 49 32 A2 62 69 B6 66 60 52 07 22 BF 47 2D 4E 93 04 71 E5 32 6B 6F 7D 3D 39 82 72 54 F4 93 40 F4 FD 82 43 D7 94 25 D5 77 7D B1 3A 86 84 5A 52 70 A6 FF E6 9A BB [email protected]/uf.. B.....U.`..H.... i...g..IBI...I.. ....tc..Yt.x...7 ....fc.1........ ...)4..N....>s.C .ei.2g.!.A..S..5 ..Q.Y.......hE.. .TK..v.0........ ..zG9d^...?I2.bi .f`R.".G-N..q.2k o}[email protected] %.w}.:..ZRp.....

AE 85 BC 37 5A B7 50 A9 37 7D 00 F2 86 7D C7 50 1B A3 FB 82 04 30 79 7D 17 6F 7F 32 D5 5E 7D

...7Z.P.7}...}.P .....0y}.o.2.^}

» Protocol performance

The overall performance of the protocol parsing is also significantly improved in version 2X on both the server and client. Especially on the server side this has a notable effect. We have benchmarked the amount of messages per second that can be parsed with the old text protocol versus the new one. SmartFoxServer Text Protocol:

Protocol Type Fragmentation Req/sec Text Text Text No Average High 11819 11767 10676

SmartFoxServer Binary Protocol:

Protocol Type Fragmentation Req/sec Binary Binary Binary No Average High 68591 68204 55286

The improvements are in the order of 5-6x increase in message parsing. The fragmentation parameter introduced in the table indicates how much fragmented is the message. In other words during the benchmark test we simulated various levels of packet fragmentation to see how this influence the performance during the data aggregation phase. An average fragmentation level uses 6-10 fragments, while a high level uses 20-25 fragments. The original message was 350 bytes long.

» Compression

The compression feature is activated by the client and server when a message is larger than the configured amount. The algorithm employed is the inflate/deflate technique from GZip/Zip formats which is very fast and supported by all common platforms (Flash, Java, .Net, etc). Performance-wise the algorithm can decompress over 10K messages/sec and compress around 5K messages/sec on any dual core machine. This also means that on the client-side the effects of data compression is negligible.

» Zones and Rooms architecture

SmartFoxServer 2X introduces significant improvements in the way Zones and Rooms can be configured with respect to SmartFoxServer 1.x. In particular we have established a new simple concept called Room Groups that allows to organize Rooms into logical groups, separated from each others. Users will be able to subscribe for events happening in the Groups they are interested in, ignoring all the others. A major advantage of this approach is that the initial room list sent by the server will be smaller, and the number of events fired to each client is highly reduced, especially in case of high traffic applications. An example will clarify the concept:

An user could initially subscribe to the Lobby group and receive Room updates (such as newly created or removed rooms, user count changes and Room Variables updates) only for the Rooms under that Group. If the user is interested in playing games he can subscribe to one of the other game Groups, see which games are available and join them. Users can also unsubscribe any Group at any time. The

User could even avoid to subscribe to any Group at all and still be able to join any Room in the Zone, although he won't receive any updates from Rooms that are not joined. The new approach, in conjunction with the finer-grained level of Zone and Room configuration (more on this later), allows the application designer to create highly sophisticated systems whith multiple lobbies, games, personal rooms, private areas, etc. The new features also provide a more rational and organized way to manage thousands of Rooms in the Zone while keeping the amount of client updates to the bare minimum. The following list describes a few different possible scenarios.

·

User joins the Zone without subscribing to any Room Group: in this case the user will receive an empty room list and won't be updated by the server about any Room event. This is a good setup when you want to let the user interact with your server-side Extension without having to receive unwanted Room updates. A typical use case is the user registration form, consulting a user-search functionality or other user-profile related activities where the interaction with other clients is not required.

·

User joins the Zone and subscribes an initial Room Group: this is probably the most common use case, and very similar to what SmartFoxServer 1.x does. The difference here is that the room list will contain only the Rooms in the selected Group and will filter out all the others. The client will be updated about newly created or removed rooms by default. The developer will also be able to configure other events such as the user count update, Room Variables update, etc.

·

User joins the Zone and subscribes multiple Room Groups: subscribing multiple Groups allow for a more sophisticated Room filtering. The player will be able to obtain a custom room list composed by the Rooms in the selected Groups. Typically the client will join a Lobby Group, maybe a Chat group with a

specific theme and a Game group where he can look for games to play. Finally Groups can be created at runtime and subscribed/unsubscribed dynamically.

» Fine-grained Zone and Room events

With SmartFoxServer 2X you have more control over the events fired by each Zone and Room. This is an example from the Room configuration in the Administration Tool:

The above interface shows how you can fine tune each Room-level event and permission when creating a new Room. According to the above configuration users will be able to change the name and password of the Room, while the resizing (capacity change) will be denied. Public messages are also allowed in the Room and all available events will be notified. In another Room you may want to configure these settings differently, suppressing some of the events and providing a different set of permissions. Rooms define two categories of settings: Permissions and Events Permissions indicate which Room-related operations can be executed at runtime.

·

ROOM_NAME_CHANGE: determines if the Room name can be changed at runtime

·

PASSWORD_STATE_CHANGE: determines if the Room password can be changed/removed at runtime · PUBLIC_MESSAGES: determines if the Room supports public messages · CAPACITY_CHANGE: determines if the Room capacity can be modified at runtime Events indicate which events the Room will notify to the joined clients.

·

USER_ENTER_EVENT: determines if an update should be sent when a user joins the Room · USER_EXIT_EVENT: determines if an update should be sent when a user leaves the Room · USER_COUNT_CHANGE_EVENT: determines if an event should be fired each time the number of users inside a Room changes · USER_VARIABLES_UPDATE_EVENT: determines if and update should be sent when a user sets his User Variables Finally, Rooms have a new way of defining their Removal Mode, which indicates if and when the Room should be auto-removed. The following settings are available.

·

Default: the Room is removed according the default rules already present in SmartFoxServer 1.x:Regular Rooms are removed when empty and the creator goes offline, Game Rooms will be removed when empty · When_Empty: the Room is removed when it is empty · When_Empty_And_Creator_Is_Gone: the Room is removed when empty and its creator goes offline · Never_Remove: the Room is never autoremoved

» User count updates

One of the most bandwidth eating update in SmartFoxServer is the User Count Update event, which is fired to all users each time the number of users inside any Room changes. Under an heavy traffic this event can be fired dozens of times per second, leading to significant bandwidth consumption and dropped-messages problem.

SmartFoxServer 2X introduces a couple of engine-level optimizations that reduce the impact of user count updates by a large degree.

·

Packet Priority: the SmartFoxServer 2X engine (codename Bitswarm) allows to specify a priority level for each packet allowing lowpriority responses such as the User Count Update to be discarded if the client queue is getting too busy. This significantly reduces the dropped-messages problem when the client is using a slow connection.

·

User Count Update Throttling: to further increase the level of control over this event SmartFoxServer 2X can be configured to "slow down" the pace at which these updates are sent by aggregating the messages and only sending the significant ones. In other words you will be able to specify a specific interval of time after which the event should be fired. All updates between two intervals will be aggregated discarding the stale ones. To give you a better idea of how the throttling system works, let's suppose we have set the interval to2000 milliseconds. This is what happens between two intervals:

· · · · · ·

Lobby Room (10/20) Lobby Room (11/20) Lobby Room (12/20) HipHop Chat (33/35) HipHop Chat (32/35) HipHop Chat (31/35)

This is what is really sent as the update on the next interval:

· ·

Lobby Room (12/20) HipHop Chat (31/35)

Only the latest up-to-date user counts are transmitted and all the rest is filtered out.

» Room Variables and User Variables

SmartFoxServer 2X introduces significant improvements in the area of Room/User Variables adding more flexibility on both client and server side.

One of the most requested enhancement was to support nested objects and we have added that. Both arrays and dictionaries are now fully supported. Also we have added the following two new settings.

·

Hidden: when a Room/User Variable is hidden it will never be sent to the clients. This allows to decide which variables are server-side only and which are kept in synch with clients. The new feature eliminates the need of two different ways of keeping custom data attached to a Room/User object. Previously in SmartFoxServer 1.x you had Room/User Variables and Room/User Properties. WithSmartFoxServer 2X all custom data is handled via Room/User Variables.

·

Global: this flag is for Room Variables only. Global Room Variables are updated at the Zone Level, so in other words outside of the Room. This means that a Room Variable set as global will be updated on all clients in the same Room Group. This is another long awaited feature that has been asked many times. This is a quick example of how you can set Room Variables in an Extension: ?

1 2 3 4 5 6 7 8 9 10 11 12 13

private void setSomeVariables(Room room, User owner) { List<RoomVariable> listOfVars = new ArrayList<RoomVariable>(); listOfVars.add( new SFSRoomVariable("bgImage", "coolBackground.jpg") ); listOfVars.add( new SFSRoomVariable("stars", 4) ); RoomVariable hiddenVar = new SFSRoomVariable("isPremiumUsersOnly", true); hiddenVar.setHidden(true); listOfVars.add(hiddenVar); // Set variables sfsApi.setRoomVariables(owner, room, listOfVars)

}

As you can see the Room Variable called hiddenVar is marked as hidden and it will not be transmitted to the client. The server-side code will be able to check it and see if the Room is for premium users only.

On the client side (ActionScript 3), setting variables is equally straightforward: ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

private function setSomeVariables(room:Room):void { var listOfVars:Array = new Array() listOfVars.push( new SFSRoomVariable("bgImage", "coolBackground.jpg") ) listOfVars.push( new SFSRoomVariable("stars", 4) ) // nested object var chatSettings:SFSObject = new SFSObject() chatSettings.putUtfStringArray("allowedFonts", ["Verdana", "Arial", "Times New Roman"]) chatSettings.putIntArray("allowedFontSizes", [10,11,12,13]) chatSettings.putBool("isBoldAllowed", true) chatSettings.putBool("isItalicAllowed", true) chatSettings.putBool("isUnderlineAllowed", false) listOfVars.push( new SFSRoomVariable("settings", chatSettings)) // Set variables sfs.send( new SetRoomVariablesRequest(listOfVars, room) ) }

» SmartFoxServer 2X Installation

The installation of the SmartFoxServer 2X platform is a very simple operation that requires only a few steps. We recommend to review the system requirements and proceed consulting the specific installation guide for the operating system of your choice.

» System Requirements

SFS2X is a truly multi-platform solution supporting all major operating systems capable of running the Sun/Oracle Java Virtual Machine (JVM). This includes Windows (all versions), Linux, Mac OS X (10.5+) andother Unix variants. In order to evaluate and develop with SFS2X you just need 64MB of free memory and a 1.0Ghz processor. For productionenvironments we recommend a machine with at least 512MB of physical RAM and a 2.0Ghz processor.

· · · ·

Installing Installing Installing Installing

under under under under

Windows Linux Mac OS X other Unix variants

A step-by-step video tutorial showing how to install SmartFoxServer is also available in our YouTubechannel.

» Post-installation tasks

After the installation your SFS2X instance is ready to be started. By default the server binds exclusively thelocalhost IP address (127.0.0.1) on the default TCP port 9933. This will enable you to immediately test the server locally without worrying of security problems. Only your local machine will be able to talk to the server initially. In order to begin your evaluation just start the server and point your browser to http://localhost:8080. This will bring up the welcome page referencing the Administration Tool and some example applications. » Local firewall settings

If you have any problems accessing the server locally we recommend to check the settings of your personal firewall (every recent OS today provides one). You should make sure that traffic for TCP port 9933 is allowed bidirectionally. » Exposing the server to the local network In order to allow other computers in the LAN to access your SFS2X instance you will have to configure the server to listen on your private LAN address. Launch the Administration Tool, choose the Server Configurator module in the sliding Administration modules panel on the left and add one or more local addresses to the list of bound IPs.

Click on the Add button. The list of available IP addresses will be auto-detected and presented in the dropdown. Select one of the available addresses, and leave the the other values to default (unless you need to change port number). Finally hit Submit and restart the server.

» Exposing the server to the outside world In order to make the server available publicliy over the internet you will probably need the help of a router and setup port-forwarding, also known as NAT service. If your server hardware is directly connected to the internet this step is not necessary and you can skip this section entirely.

Setting up the NAT on your router will allow you to map your public IP address and port to the physical machine inside your network that runs the service (SFS2X in this case). In other words when the router will receive data on TCP port 9933 (SFS2X default) it will redirect those packets to the machine in your LAN running SmartFox. A specific step-by-step guide on how to configure your router cannot be provided here because each brand and model differs from the others. We highly recommend to check the documentation coming with your router and follow their instructions. Typically it's a very quick operation that can be done via a convenient control panel. If you are unsure about what your public address is, simply check this website. For more help on port forwarding we also suggest this website.

FINAL NOTE All the examples provided with SFS2X are preconfigured to connect to 127.0.0.1:9933 (the localhost). In order to use them when your server is exposed in the LAN or the internet you will have to change the IP address in the xml configuration file (usually called sfsconfig.xml) bundled with each example.

» SFS2X setup on a remote server

In order to setup SmartFoxServer 2X on a remote machine, for example an Amazon EC2 instance, after the installation you have to make some manual changes to the configuration before being able to access the Administration Tool. You should follow these steps:

1.

locate and open (in a text editor) the main

SFS2X xml configuration file: {sfs-installdir}/SFS2X/config/server.xml; 2. under the <socketAddresses> tag, substitute the 127.0.0.1 address with the private IP address of yor server (alternatively you can leave the localhost and add a new <socket> entry for the private IP); 3. make sure that the <allowedRemoteAddresses> list have no entries (delete them if necessary), then save the changes; 4. check the firewall settings: you have to allow communication over ports 8080 and 9933 (at least);

5.

start SmartFoxServer 2X, open a browser

and type http://your-public-ip:8080 to have access to the page which links the Administration Tool and some examples (please notice that the examples won't work ­ see the previous note); 6. open the Admin Tool, enter the public IP of the server in the host field and the default username/password: you should now have access to your SFS instance.

» Client API setup

This document provides step-by-step instructions to setup your client development environment for all the supported client technologies in SmartFoxServer 2X. If this is the first time you setup the client API we also recommend to take a look at the sources of the examples provided with the current release. The example apps are located side by side to the API library files.

» ActionScript 3 API

The API for ActionScript 3 supports Adobe Flash CS4 (and higher) and Adobe Flex Builder 3 (and higher). In Flex/Flash Builder UDP support is included for the AIR 2.0+ runtime. The API is distributed as a single SWClibrary that can be easily added to your Flash/Flex Project. The library's .swc file is located under the {sfs2x-install-dir}/Client/API/ folder. » Installing under Flash To install the API in Flash, follow these steps:

·

choose File > Publish Settings from the main Flash menu and click the Flash tab · click the Settings button · in the window select the Library path tab · click the red SWC icon and navigate to your API folder · select the API SWC file and click Ok several times to close the various windows » Installing under Flex/Flash Builder The quick way to add the SFS2X API to your Flex/Flash Builder is by adding the provided .swc file in the default libs/ folder available under every project.

If you prefer not to copy the library file you can do as follows:

·

from the Package Explorer right click the icon of your project and choose Properties · in the new window select Flex Build Path · select the Library path tab and click the Add SWC... button · click Browse, navigate to your API folder and select the provided SWC file · close the various dialogue boxes

» Unity 3D / .Net API

The API for Unity 3D requires Unity 2.6 or higher and fully supports the .Net and Mono platforms. The API is distributed as a single DLL library that can be easily added to your Visual Studio or MonoDevelop project. The DLL is located under the {sfs2x-install-dir}/Client/Unity/ folder. In order to add the API DLL library to your Unity 3D project simply copy the provided DLL to your project'sAssets/Plugins/ folder.

» Objective-C iOS API (iPhone / iPod Touch / iPad)

The iOS API library is located under the {sfs2x-install-dir}/Client/iPhone/ folder. If you don't have an existing project that you work on, open Xcode, select Create a new Xcode projectthen choose a template for your new project (the template type doesn't matter). » Adding SFS2X classes From Group & Files pane select your "Classes" group, right click it , select Add -> Existing Files.

Navigate to the API folder and open INFSmartFoxiPhoneClientAPI_2X/Classes folder. Select TouchXMLand SFS2X folder and click Add (it is up to you to choose the copy option after clicking add).

» Header Search Paths

From Xcode menu, select Project --> Edit Project Settings, and then click the Build tab. Type "header search paths" in search field (you should be able to see the "Header Search Paths" settinglisted after that).

Double click the value cell, press the plus icon (+) to add a new setting. Enable the Recursive option and enter /usr/include/libxml2 for the path.

» Other Linker Flags Now search for "Other Linker Flags" like you did in the previous step. Enter -lxml2 as the value.

» CFNetwork Framework Right click Frameworks in Groups & Files pane, select Add --> Existing Frameworks, then chooseCFNetwork.framework and click Add.

Finally import SmartFoxiPhoneClient.h in order to start using the SFS2X API.

» The Administration Tool

The Administration Tool, also known as AdminTool, is a powerful application for monitoring and managing SmartFoxServer 2X. Its main features are:

·

advanced visual configuration of all the server settings, including Zones and Rooms; · powerful runtime monitoring tools which allow checking Zones, Rooms and users on the live server, with advanced Rooms and users filtering capabilities, statistics and more; · complete runtime statistics which show the live server status at a glance, letting administrators to keep the server performance under strict control; · kick and ban users, and manage the banned usernames / IP addresses lists; · multiple administrators can access the tool at the same time and work together discussing issues live thanks to the included chat panel; · advanced server profiles management which allows connection parameters to multiple servers to be saved, grouped, exported to file and imported into another AdminTool instance; · modular architecture allowing fast deployment of additional administration modules (in the future also custom modules). The following paragraphs describe how to access the Administration Tool, how to connect to SmartFoxServer 2X and how to handle multiple server profiles, and give an overview on the AdminTool's user interface. The available administration modules are described in dedicated pages linked below.

Accessing the AdminTool

After completing the SmartFoxServer 2X installation successfully, in order to rapidly access the AdminTool point your browser to http://localhost:8080/admin/. When the main connection panel is displayed (more on this in the next paragraph), you can connect to your local SFS2X instance immediately by entering the following details:

· ·

Host: localhost Port: 9933

· ·

Username: sfsadmin Password: sfsadmin

The predefined administrator (sfsadmin) should be removed as soon as possible, creating a new administrator user in the Remote Admin tab of the Server Configurator module (see below). This is very important in particular when the server is put online and can be accessed publicly.

The connection panel

The connection panel is the first screen you will be presented after launching the AdminTool. It consists of aConnection tab and a Bookmarks tab.

The Connection tab allows the administrator to enter the connection details directly and access the server administration by pressing the Connect button:

·

Host: can be a domain name or an IP address; · Port: the TCP port dedicated to server administration; · Username and Password: the administrator's access credentials.

The provided connection details are validated against the settings entered in the Remote Admin tab of theServer Configurator module to allow or deny the access to the user.

The Bookmarks tab allows the creation of profiles in which the server connection settings can be saved (all except the password, for security reasons). In this way administrators can access several SFS2X instances within the same AdminTool (no matter where it is located) and without worrying about remembering IP/ports/usernames each time. Bookmarks can be arranged into custom groups to better organize the profiles. When you double click an existing profile (or click the Use profile button after selecting a profile), it will bring you back to theConnection tab and populate all the fields: simply enter the password and click the Connect button. Click on the buttons to add, edit or remove a group (left column) or a profile inside the selected group (right column). The AdminToll makes use of the Flash Player's shared objects to save the bookmarks. By means of the and buttons it is also possible, respectively, to export the groups and profiles to an xml file, and import them back. This allows moving the profiles to other client machines without the need to re-enter them each time. As an example, clicking on this link you can download a profiles file (unzip it to get the xml) to be imported in your AdminTool which contains the localhost connection settings mentioned in the Accessing the AdminTool paragraph above.

The administration modules

After the successful connection to a SmartFoxServer 2X instance, the following administration modules are available. Click on the module's name to access its specific documentation.

·

Runtime Statistics » Live server statistics (CPU, memory, traffic, threads, etc) · Zone Monitor » Runtime monitoring of SFS2X Zones, Rooms and users · BlueBox Monitor » Runtime monitoring of HTTP-tunnelled connections · Ban Manager » Advanced form for users banishment and ban lists management · Zone Configurator » Creation and configuration of SFS2X Zones and static Rooms · Server Configurator » Configuration of SFS2X global settings · License Manager » SFS2X license setup and test (coming soon) Modules can be loaded by accessing the Administration modules panel on the left side of the AdminTool (more informations in the next paragraph).

The AdminTool's main interface

The following image shows the main user interface of the AdminTool.

The central zone of the interface is where the administration modules are loaded (faded in the image). The other relevant interface elements are: A. the AdminTool's header contains the administrator's name and some informations about the server he is connected to: server's host and port number, SFS2X version and server uptime expressed in days, hours, minutes and seconds; B. the Halt and Restart buttons respectively stop the server and stop-restart it; of course this causes an immediate disconnection of the administrator; (coming soon)

C.

the

Online documentation button opens

the documentation of the currently selected module in a new browser window;

D.

the

Disconnect button closes the current

connection to SFS2X and switches the view back to the connection panel;

E.

the

Clear button has a dual usage: when

the log is displayed (see F), it deletes the whole log; when the log is hidden, it removes the currently displayed system message (see G);

F.

the

Log button shows/hides the area

where system messages are logged during the current AdminTool's usage session; G. the AdminTool's footer displays system messages, which are logged automatically after some seconds; red messages indicate specific errors or warnings. The vertical dark labels give access to the following collapsible panels.

Moving the mouse pointer over the vertical Administration modules label shows the panel containing the list of modules described previously. Click one of them to load it. The panel also contains a Save last switch at the bottom. When enabled (green), the AdminTool "remembers" the last opened module and will load it automatically the next time it is accessed. If disabled, the AdminTool will load the Runtime Statistics module by default. Leave the panel area with the mouse pointer to make the panel close. Clicking the vertical Administrators chat label shows the administrators' activity and chat panel. Using this panel, administrator connected to the same SFS2X instance can discuss in the internal public chat and check each other's activity: the table at the top of the panel shows the administrator's name, his connection time and the administration module he his currently using. Click the label again to close the chat panel.

» BlueBox 2X

The BlueBox is an HTTP-based technology that allow clients to connect from behind firewalls and proxies, when a socket connection is not available. This is a typical scenario for corporate offices, schools and other institutions where the network traffic is limited to a few selected services. The BlueBox enables players under restricted network configurations to play and enjoy fast multiplayer apps and games with little to no noticeable performance loss. All SmartFoxServer applications can take advantage of the BlueBox without any code change. Behind the scenes the BlueBox uses a so-called HTTP tunnel by wrapping the SmartFoxServer protocol into HTTP packets. The behavior of the BlueBox is pretty different from regular HTTP-polling solutions. The major disadvantages of a regular polling is the continuous traffic of requests coming from the clients, even during inactivity. Additionally the overall performance isn't very good especially if the polling interval is bigger than a couple of seconds. The BlueBox uses a more sophisticated approach that eliminates the need for continuous polling. Additionally the client API provide the developer with the ability to fine tune the server response times, allowing near real-time performance!

» What's new in BlueBox 2X

The BlueBox 2X code was largely rewritten to provide a tighter integration with the new SFS2X engine (codename BitSwarm). Although the BlueBox 2X features are very similar to its predecessor, its architecture is much lighter and the performance increase is significantly improved. The main difference in SFS2X is that the BlueBox plugs into the server engine directly, without the need to emulate a socket connection for each client (as in SFS 1.x). This in turn saves lots of resources and allows for better scalability of HTTP connections.

» How to use the BlueBox

Starting from SFS2X RC2 the BlueBox is active by default in the server and doesn't require any particular setup. At any time a client that fails to establish a socket connection can attempt to use the BlueBox. The only settings you need are located on the client side, in the external client config file.

<SmartFoxConfig> <!-- Mandatory Settings --> <ip>192.168.0.16</ip>

<port>9932</port> <zone>Bench</zone> <!-- End Mandatory Settings --> <debug>true</debug> <httpPort>8080</httpPort> <useBlueBox>true</useBlueBox> <blueBoxPollingRate>500</blueBoxPollingRate> </SmartFoxConfig>

The last three settings that you see are specific for the BlueBox: · · · httpPort: indicates the the port of the Jetty webserver; useBlueBox: toggles the option to connect to the BlueBox if sockets are not available; blueBoxPollingRate: controls the pause between multiple ping requests; the default value is already ideal for 99% of the situations, while for fast real-time action you can experiment with smaller values (250-300). We don't really recommend to use values below 150ms.

» Recommended settings

By default SFS2X uses the following ports: · · TCP 9933 for socket traffic TCP 8080 for HTTP traffic

In order to provide the best connectivity options to your clients you should use TCP ports that are usually not filtered by firewalls. Our recommendation is to use ports 80 (HTTP) and 443 (HTTPS) where possible (e.g. if there isn't another http server in the machine already running). If you are under Unix/Linux/MacOSX this will require that the server runs with root privileges.

» Monitoring the BlueBox

The connections handled by the BlueBox maintain a special session used by the enclosing HTTP protocol. You can monitor these sessions from the AdminTool by launching the BlueBox Monitor module. Additionally the BlueBox process logs specific usage data under the SFS2X/logs/bluebox/ folder.

» Known issues / limitations

The current BlueBox shipping with SFS2X RC2 is already production ready and has been tested under different scenarios of heavy traffic with excellent results. We are not aware of any issues at the moment. Should you find any problem, please report them in our Support forums. There a few minor elements that will be refined in the final release: · · · logging, we'll provide further config options; security, we'll add a few more settings to prevent security issues and misuse; performance, we'll provide a few extra config params to handle message queues and improve performance.

» Troubleshooting guide

In this guide we provide a few tips and tricks to overcome typical problems encountered while setting up or running the Server.

· · · ·

Unable to reach the server Flash crossdomain policy issues Server startup problems Dropped messages

» Unable to reach the server

One common runtime problem is the inability to connect to the test or production server after the first installation. Once your SFS2X instance is running you should make sure that no firewall (software or hardware) is blocking the TCP ports in use. Specifically you should open the TCP ports 9933 and 8080 to "the world". If this is something you can't do you should ask your system administrator to read this document. If your server is not connected directly to the internet you should also make sure that the NAT service(port-forwarding) is configured correctly. The NAT service allows to expose one or more services from your computer to the public internet address. For example a well configured router will send all traffic coming to port 9933 to the server machine running SmartFoxServer 2X. In order to determine if something is blocking your server you can try telnetting its public address from outside. Telnet is a popular command-line utility available on most operating

systems that allows to establish a TCP connection to a remote host. Execute the following steps to test the connection to your server.

·

Open the system console: o Under Windows use Windows Key + R and then type cmd in the dialogue box that appears. o Under MacOS X the terminal is found in /Applications/Utilities/Terminal. o Under Linux... well, every Linux user should know how to launch a terminal! :)

·

Type the following:

telnet <ip-address> 9933

where <ip-address> is the IP address to test.

· · · ·

You should get something like this:

Lapo$ telnet localhost 9933 Trying ::1... Connected to localhost.

Escape character is '^]'.

The server has started a new connection so it's reachable. If the attempt fails you should double check that no firewall is blocking the communication and that port-forwarding is properly configured.

» Flash crossdomain policy issues

If the server is reachable at its public address but you are still having connection problems, you should make sure that you are not incurring in any Flash Player sandbox restrictions. For security reasons the Flash Player does not allow connecting to external domains without setting explicit permissions via a crossdomain policy file. You can learn more about the Flash Player security settings in this white-paper.

NOTE SFS2X can automatically serve the policy file via socket. The file is located atSFS2X/config/crossdomain.xml and can be edited to meet your requirements.

» Server Startup problems

If the server doesn't complete the startup phase we recommend to check the log files. SFS2X runs a two-phase startup sequence: first the low-level network engine is booted, then the SmartFox services are started in sequence until the server is ready to work. The first low-level boot phase produces a detailed report in the {sfs2x-installdir}/SFS2X/logs/boot/ folder. The SmartFoxServer operative logs are located in the {sfs2xinstall-dir}/SFS2X/logs/ folder. Log files are rotated on a daily basis and easy to indentify by the date appended to the filename.

» Dropped messages

Among the questions asked in our support board many revolve around the topic of dropped messages. At times you might notice a very high count from the AdminTool's Runtime Statistics module, especially for the outgoing dropped messages. The Incoming Dropped Messages are very simple to explain: SFS2X drops any message that is malformed or not conform to its protocol. Additionally it will discard messages whose size is greater than the value set in the config/core.xml file for the maxIncomingRequestSize parameter. The Outgoing Dropped Messages value keeps a count of the server messages that were not sent to their respective recipients. Before discussing the possible reasons for dropping a message, let's take a look at the following diagram:

The server associate an outgoing message queue to each connected session in order to store data that cannot be written immediately. In fact the server attempts to deliver each packet as quick as possible, but there are times in which the user connection is congested and very little to no data can be sent. In this case the server is forced to store the remaining bits of data in the queue and wait for the network pipe to become available again for a new transmission. According to the user configuration, SmartFoxServer will keep data in each queue until its capacity is exhausted: at that point messages are dropped. This mechanism allows the server to protect itself from indefinite memory allocation which might eventually end up in a crash of the Java VM. You can fine tune the server's tolerance for dropped messages from the Server Configurator module in theAdminTool. » Causes of dropped outgoing messages

·

Bad or slow client connection: the user has a slow response time and not enough bandwidth to keep up with the server responses. This causes the socket to become busy very soon and forces the server to keep the data in the queue.

·

Too much data being sent too frequently: this is a slight variation on the previous theme. Only this time we can not

necessarily blame the client connection but it is probably depending on the server logic sending too large data or too frequent updates. It is important to remember that over the internet every connection experiences a certain amount of latency which usually varies between 50 to 200 milliseconds, but it can also reach several seconds. It is important to keep in mind these limitations and work around them with specific client and server algorithms to reduce the lag. In general it is not recommended to send more than 10-20 updates/sec via TCP, while this value could be significantly higher for UDP packets. If you are interested into learning more about network lag we suggest you to read this article.

·

Lack of bandwidth on the server side: as you reach the capacity of your hosting bandwidth you will experience a general slow down of the network performance. Your players will probably start to complain about the game or application getting slower and you should see a rising number of dropped messages. You can easily detect this issue by monitoring your bandwidth usage on a daily basis.

» SmartFoxServer 2X HOWTOs

In this document we have collected a number of quick tutorials on how to accomplish some simple and specific tasks that you might need during the course of your work with SmartFoxServer 2X. · · · · · How How How How How to to to to to add new Java libraries or Extension dependencies setup a connection to an external Database create an Extension-based custom login use the SmartFoxBits 2X check the server logs

· · ·

How to debug your Extensions How to schedule timed tasks in an Extension How to send an Email from an Extension

» How to add new Java libraries or Extension dependencies

Adding new libraries in SmartFoxServer 2X is a breeze. No messing around with the classpath: all you need to do is deploy the .jar file(s) in the proper directory and restart the server. There are two folders that we recommend to use: {SFS2XRoot}/lib/ This is where we would recommend adding libraries such as database drivers or other allpurpose libraries that will be used by all Zones and the Server itself. Another example would be a custom implementation of the BuddyList storage class. {SFS2XRoot}/extensions/__lib__/ This folder is recommended for libraries that will be shared by multiple Extensions in your system. For example the "Google Collections" jar, or the Hibernate jar, etc. Do not add your Extension jar to this folder, because the AdminTool won't ba able to detect it when you setup the Zone/Room configuration. Finally if you have one or more jar files that are going to be used by one specific Extension only, you can simply deploy these files in your Extension folder which will be something like: {SFS2XRoot}/extensions/{the-extension-name}. ^top menu

» How to setup a connection to an external Database

SFS2X allows connecting to databases in a way that is very similar to its predecessor. All you need to do is downloading the JDBC connector from your Database vendor (e.g. MySQL or MSSQL) and drop the .jar in the{SFS2XRoot}/lib/ folder. Of course if you use ODBC instead of JDBC you can even skip the driver deployment step. In fact theODBC driver is already provided by the Java Runtime. The following is a list of pages where you can find the JDBC drivers for the most common RDBMS:

· · · ·

MySQL JDBC Driver Microsoft SQLServer JDBC Driver Postgres JDBC Driver Oracle JDBC Driver

The next step is running the SFS2X AdminTool, launch the Zone Configurator module, select your Zone and finally click on the Database manager tab to edit the configuration.

At the end of the process click Submit and restart the server. ^top menu

» How to create an Extension-based custom login

Implementing a custom login on the server-side is a simple process. SFS2X fires the following two login events. · USER_LOGIN: fired when a client requests to join a Zone. Here you can validate the client credentials and decide if the User can continue the login process. At this stage the client is represented by aSession object, not by an SFSUser object yet. USER_JOIN_ZONE: notified when a client has successfully joined a Zone (and is turned into an SFSUser).

·

In order to add your custom login logic you should execute at least the first two of the following steps.

1) Configure the Zone Launch the AdminTool, open the Zone Configurator module and enable your Zone's custom Loginsetting; then sestart SFS2X. 2) Server code Create a new server-side Extension that extends the method should look like this: ?

Use

SFSExtension class. Your init()

1 2 3 4 5 6 7 8

@Override public void init() { trace("My CustomLogin extension starts!"); // Register for login event addEventHandler(SFSEventType.USER_LOGIN, LoginEventHandler.class);

}

Now create the LoginEventHandler class which will take care of the user name/password checking. In the following example two specific user names are not allowed to login. ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

public class LoginEventHandler extends BaseServerEventHandler { @Override public void handleServerEvent(ISFSEvent event) throws SFSException { String name = (String) event.getParameter(SFSEventParam.LOGIN_NAME); if (name.equals("Gonzo") || name.equals("Kermit")) { // Create the error code to send to the client SFSErrorData errData = new SFSErrorData(SFSErrorCode.LOGIN_BAD_USERNAME); errData.addParameter(name); // Fire a Login exception throw new SFSLoginException("Gonzo and Kermit are not allowed in this Zone!", errData); } } }

If one of the two unwanted names is detected, an SFSException can be fired. In doing so we provide a message that is logged on the server-side and an SFSErrorData object which

contains the error code (SFSErrorCode.LOGIN_BAD_USERNAME) and the bad name itself. Typical error codes used in this context are SFSErrorCode.LOGIN_BAD_USERNAME andSFSErrorCode.LOGIN_BAD_PA SSWORD, both taking an additional parameter which is the wrong name or password. Now this is a very simple example that just shows how to deny access to users with a name of Kermit orGonzo. Of course your logic might require a little more sophistication but you should get the idea. When you need to stop the execution of the login process you just throw an SFSLoginException. If no exception is thrown the system will accept the user and continue the login process. There are other things that could go wrong during this phase, for instance: · · · · · the Zone is full and no more logins are allowed; another user with the same name is already logged in; the user is currently in the banned list; the user name might contain bad words that are not acceptable (depending on your custom configuration); etc.

Once all these checks are passed the user is finally logged in the Zone. At this point the server code will receive an USER_JOIN_ZONE event, if you subscribed to it. This last step is optional and it won't be necessary in many cases. Typically you will use this when you need to perform specific actions after the user is logged in the system (like setting User Variables, auto-join a Room, etc).

TIP When working with asynchronous events such as USER_LOGIN and USER_JOIN_ZONE it's a bit more difficult to maintain the state of the current transaction/operation. A convenient way to maintain the state is to use the user Session object. In particular theSession object allows to store custom parameters as key-value pairs (see the JavaDoc, methods getProperty/setProperty, etc).

3) Secure passwords The user password is never transmitted in clear from the client to the server, for security reasons. In order to be able to compare the encrypted password with your database original password we provide a convenient method in the API. ?

1

getApi().checkSecurePassword(session, clearPass, encryptedPass);

The method call will return

true if the password match and false otherwise.

On the client side there's absolutely no difference between a "standard" login and a "custom" one. All you need to do is adding a listener for the SFSEvent.LOGIN event to receive the server response, and send aLoginRequest to log into a Zone. You can check the AS3 documentation and the examples for all the details on how to do this. For the other languages the process is the same. Another interesting reading related to this topic is the discussion about the User Permissions. 4) Change the user name at login time There are cases in which you need to change the name provided by the user at login time with another one extracted from the database. An example is when the user logs in with an email address as the login name, but on the database you have stored his nickname, which should be used instead of the email. We have established a simple convention that allows you to provide an alternative name to the login system. In the USER_LOGIN event you are passed an empty SFSObject that can be used to return custom data to the client. You just need to provide tha name in that object under a very specific (and reserved) key name. See code below: ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

public class LoginEventHandler extends BaseServerEventHandler { @Override public void handleServerEvent(ISFSEvent event) throws SFSException { String name = (String) event.getParameter(SFSEventParam.LOGIN_NAME); ISFSObject outData = (ISFSObject) event.getParameter(SFSEventParam.LOGIN_OUT_DATA); // ... // your login logic goes here // ... // Provide a new name for the user: String newName = "User-" + name; outData.putUtfString(SFSConstants.NEW_LOGIN_NAME, newName); } }

^top menu

» How to use the SmartFoxBits 2X

Together with SFS2X we provide the latest version of our SmartFoxBits components, which can save you a ton of time in setting up the application GUI if you are using the Adobe Flash platform to build your client. The base SmartFoxBits pack contains five components: · · · · · Connector: manages the connection/disconnection process (supporting the new reconnection system too) Login: handles the login process RoomList: manages the Rooms list, including the Groups feature (allowing to create multiple views of the list based on groups) UserList: manages the user list allowing private chat ChatBox: provides a basic chatting component

You can check all the details and read the documentation on the SmartFoxBits page. ^top menu

» How to check the server logs

SFS2X provides detailed logging of all its activities. You can consult them at any time by checking the logs/folder. Additionally under the logs/boot/ folder you will find detailed logs of the boot phase. Should the server have any issues starting the boot logs will help you pinpoint the problem quickly. ^top menu

» How to debug your Extensions

You can easily attach a Remote Debugger to the server and enable your favorite Debugger for bug hunting in your Extensions. SmartFoxServer 2X can be started with the sfs2x.bat (Windows) or sfs2x.sh (Linux/Unix/MacOSX) scripts. By adding the following JVM switches to the launch script you can enable remote debugging. · Edit the start script with your favorite text editor

·

·

Add the following switches in the command line: -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=n Save the script as debug.sh or debug.bat

Now run the debug script and you will be able to start a remote debugging session. ^top menu

» How to schedule timed tasks in an Extension

Often times in the server-side game logic it is necessary to use timers for recurring events that are sent to the clients (e.g. the end of a turn time, npc actions, etc). A quick solution to this problem is using the ScheduledThreadPoolExecutor class provided in the JDK which offers a convenient task executor backed by a pool of threads. SFS2X already runs its own instance of this Executor (wrapped in a class called TaskScheduler). The following snippet of Java code shows how to run a looping task using the SmartFoxServer's ownTaskScheduler. ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

public class SchedulerTest extends SFSExtension { private class TaskRunner implements Runnable { private int runningCycles = 0; public void run() { runningCycles++; trace("Inside the running task. Cycle: if (runningCycles >= 10) { trace("Time to stop the task!"); taskHandle.cancel(); } } }

" + runningCycles);

// Keeps a reference to the task execution ScheduledFuture<?> taskHandle; @Override public void init()

22 23 24 25 26 27 28 29 30 31

{ SmartFoxServer sfs = SmartFoxServer.getInstance();

// Schedule the task to run every second, with no initial delay taskHandle = sfs.getTaskScheduler().scheduleAtFixedRate(new TaskRunner(), 0, 1, TimeUnit.SECONDS); } }

The scheduleAtFixedRate method takes four arguments:

1. a Runnable object that will execute the Task's code;

2. the initial delay before the execution starts; 3. the interval at which the task will be executed; 4. the time unit used to express the time values. The Scheduler also exposes a schedule method that executes a Runnable task once after the specified amount of time. Finally the Scheduler's thread pool can be resized on-the-fly at runtime via theresizeThreadPool() method.

NOTE The initial size of the system TaskScheduler's thread pool can be adjusted via the Server Configurator module in the AdminTool.

^top menu

» How to send an email from an Extension

Emails can be sent directly from your server side code in order to provide registration confirmations, updates or even debugging and stats reports. In order to enable email sending you will need to setup the Emailer Service from the Admin Tool: from theServerConfigurator choose the Emailer tab, turn on the service and set your SMTP parameters. Restart the server and you are now ready to send emails using a couple of lines of code like this: ?

1

Email myEmail = new SFSEmail("[email protected]", "[email protected]", "Test mail", "He

2

from SFS2X"); SmartFoxServer.getInstance().getMailService().sendEmail(myEmail);

There are three modalities to send emails: "blindly", with a confirmation event and delayed. You can learn about this and lots more from the SFSPostOffice class in the javadoc.

» Development Basics

In this section we will guide you through the basic concepts of multiplayer development using SmartFoxServer 2X. From a generic perspective all multiplayer games and applications work very similarly regardless of their different genres (multiplayer action game, MMORPG, MMORTS, virtual worlds etc...). In essence they all consist of clients connected via a persistent connection (using the TCP protocol) to a central server which is responsible for maintaining the game states and synchronize players with each others. Under SmartFoxServer we use the concept of Zone to indicate a portion of the server dedicated to a specific application, allowing the developer to run multiple different games and apps without them interfering with each others. You could think of Zones as different "virtual hosts" under an HTTP server, where you can run multiple web apps at the same time, in proper isolation.

Three basic steps:

An SFS2X client will typically follow a few simple steps to start the communication with the server: · · · Attempt a connection with the server Login in one of the available Zones Send and receive requests to the server's "System Controller" or to the available Extensions and handle the server events.

In this section we will analyze each phase in detail providing code examples, suggestions and illustrating advanced options for each step. · · · · · Connection phase Login phase Joining and creating Rooms System requests Extension requests

The Server controllers:

Each time a client sends a request to the server he's talking to a Server Controller which is a service responsible for handling requests and sending user updates. Under SFS2X there are two main controllers: · System Controller: this is the main one, responsible for handling all common client requests such as login, join, create room and all the other calls documented in the client API. In other words this controller exposes all the public SFS2X API to the connected users. This includes advanced features such as the Game API and BuddyList API. Thanks to the Privilege Manager offered by SFS2X you can also limit specific type of users (guest, regular, moderator, etc...) to access certain Controller's features.

·

Extension Controller: this controller is exclusively dedicated to handling the custom client requests directed to the server extensions. In other words this service will route custom requests to your server side code and return responses in form of events. This controller is also able to communicate via UDP protocol allowing faster updates for real-time games.

The SFS2X client API takes care of directing the client request to the appropriate controller behind the scenes, so there is no particular action required on the developer's end.

» Development Basics: the Connection Phase

A connection to SFS2X is performed in two steps: 1. A physical TCP connection is opened to the server 2. An "handshake" is performed between client and server exchanging a certain number of parameters. During the handshake the server verifies that the client API version is supported and sends back a number of settings that the client stores locally.

The following snippet of Actionscript 3 code is derived from the SFS2X-Connector example provided in the SFS2X download:

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

public class SFS2XConnector extends Sprite { private var sfs:SmartFox public function SFS2XConnector() { // Create an instance of the SmartFox class sfs = new SmartFox() // Turn on the debug feature sfs.debug = true // Add SFS2X event listeners sfs.addEventListener(SFSEvent.CONNECTION, onConnection) sfs.addEventListener(SFSEvent.CONNECTION_LOST, onConnectionLost) sfs.addEventListener(SFSEvent.CONFIG_LOAD_SUCCESS, onConfigLoadSuccess) sfs.addEventListener(SFSEvent.CONFIG_LOAD_FAILURE, onConfigLoadFailure) // Connect button listener bt_connect.addEventListener(MouseEvent.CLICK, onBtConnectClick) dTrace("SmartFox API: "+ sfs.version) dTrace("Click the CONNECT button to start...") } private function onBtConnectClick(evt:Event):void { // Load the default configuration file, config.xml sfs.loadConfig() } private function onConnection(evt:SFSEvent):void { if (evt.params.success) { dTrace("Connection Success!") } else { dTrace("Connection Failure: " + evt.params.errorMessage) } } private function onConnectionLost(evt:SFSEvent):void { dTrace("Connection was lost. Reason: " + evt.params.reason) }

42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67

private function onConfigLoadSuccess(evt:SFSEvent):void { dTrace("Config load success!") dTrace("Server settings: " + sfs.config.host + ":" + sfs.config.port) } private function onConfigLoadFailure(evt:SFSEvent):void { dTrace("Config load failure!!!") } //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: // Utility Methods //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: private function dTrace(msg:String):void { ta_debug.text += "-- " + msg + "\n"; }

}

The code starts by creating the SmartFox object, which is the main API class. We then proceed by registering the server events that we want to listen for using the SFSEvent class. The next step is loading an external .xml configuration file that contains the details of our connection. This is a convenient way of keeping the connection parameters separate from the code so that they can be changed at any time without having to recompile the code. If you are familiar with SmartFoxServer 1.x you will find this system very similar to its predecessor.

NOTE: You don't have to specify any configuration file name if you adopt the standard convention of using a file called sfs-config.xml.

You can open the example file with Flash CS4 or higher and test the application on your local system. Of course it will be necessary to start SFS2X before running the test.

What can I do after the connection is established?

Starting a connection with the server ensures that the client is able to "speak" the SFS2X protocol but at this point the client is not recognized in the system as a real User. There is still one mandatory step to take before the client can start to freely interact with the Server and the other users. This is the login phase which promotes the connected client to a true User and joins him in one of the available Zones... but before we move on with the second phase there are still a few things about the connection that we need to examine.

What could go wrong?

In a local environment it is very unlikely that any problem will arise and you should be able to connect in a snap, however in a production server with "real clients" connecting over the internet, a few issues could show up. Typically you might find users complaining about a connection failure for one of the following reasons: · Firewall issues: the client is behind a firewall that doesn't allow connections on the default server port (TCP 9933). If the client is running a local firewall he can be advised to give permissions to the SFS2X port. On the other hand if the client is behind a corporate firewall this can be solved in a different way using a BlueBox connection. (more on this later) Proxy issues: a Proxy server might stand between the client and SFS2X making it impossible to establish a direct socket connection. Again this is more typical of corporate networks and the BlueBox will come to rescue here too. Flash Player or Unity Player crossdomain policy issues: browser-based plugins such as Flash and Unity3D require special permissions for accessing resources outside of their domain. For this reason you will probably need to configure a crossdomain policy file that allows socket connections to SFS2X.

·

·

You can find other information on how to troubleshoot client connection failures in this guide.

The BlueBox

The BlueBox technology is included in SFS2X as a difference from its predecessor where it was an optional add-on. The BlueBox offers an high performance HTTP-tunnelling engine that allows users behind proxies and other restricted network conditions to enjoy multiplayer applications and games at full speed. At the moment the current release of SFS2X (RC1) does not yet include this component so we will discuss it in detials in the next release.

The Highly Resilient Connection (HRC) system

An important feature introduced in SFS2X is a low-level mechanism that allows to resume an abrupt disconnection in a completely transparent way for the developer. When the HRC is turned on in your Zone the client API will detect unexpected disconnections and attempt to reconnect to the Server within a certain amount of time that is configured at the Zone level. The whole process happens behind the scenes and the developer is notified with different events: · · CONNECTION_RETRY: this is dispatched when the API has detected an abrupt loss of connection and the reconnection to the server is carried on. CONNECTION_RESUME: this event is notified when the reconnection is successful. In case the reconnection fails a CONNECTION_LOST event will be sent instead.

On the server side an extension developer can handle this situation in the same way, by listening to the SFSEventType.USER_RECONNECTION_TRY and SFSEventType.USER_RECONNECTION_SUCCESS. In order to turn on this feature you will need to configure the User reconnection timeframe parameter in the Zone Conigurator panel from the Admin Tool.

The value is expressed in number of seconds and any values greater than zero will activate the system. Common settings range between 4 to 15 seconds depending on how your application requirements and how susceptible is your server logic to waiting for a missing player.

» Development Basics: the Login Phase

We have seen in the previous chapter how to start a connection to the Server and have mentioned that logging in a Zone is necessary before the client can start to interact with the Server API and the other users. In order to see the Zones available in the Server and create new ones you can use the SFS2X Admin Tool. In the left side bar of the tool choose Zone Configurator and a list of all Zones will be presented.

From here you can double click (or select and click the edit button) any Zone name and proceed with the configuration of the Zone settings.

Default vs Custom Login

Before we proceed to show the basic code for the login request we need to point our attention to one very important setting in the Zone called "User custom login" which is found right after the Zone name in the Zone Configurator. The value of this parameter (which is by default set to off) determines which Controller will receive and handle the Login Request: · customLogin == false: the System Controller will handle the login phase. In this case any user name will be accepted unless another user is already logged in the Zone with an identical name. The password will be ignored. If an empty string is passed as the name the Server will automatically generate a "guest" name (e.g. Guest#42) customLogin == true: the Extension Controller will handle the login phase allowing the developer's Extension to validate the User credentials and perform any other required operation.

·

Logging in a Zone

The Login phase is always initiated from the client side by sending a LoginRequest to the Server. This requires several parameters: · · · · user name: (optional) a user name password: (optional) a password zone name: the name of a Zone that exists on the Server side. extra parameters: (optional) an object containing extra custom data. Typically this is used in conjunction with an Extension-based login in order to send custom data to the server side.

Logging in a Zone requires just a few lines of code: in essence you just need to register the SmartFox class instance to receive the SFSEvent.LOGIN and LOGIN_ERROR events and proceed with the request.

? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

var sfs:SmartFox = new SmartFox(); sfs.addEventListener(SFSEvent.LOGIN, onLogin); sfs.addEventListener(SFSEvent.LOGIN_ERROR, onLoginError); ... ... // This code is executed after the connection sfs.send( new LoginRequest("", "", "SimpleChat") ); public function onLogin(evt:SFSEvent):void { trace("Login success: " + evt.params.user.name); } public function onLoginError(evt:SFSEvent):void { trace("Login failed: " + evt.params.errorMessage); }

In the example we use a default login (handled by the System Controller) and pass an empty string as the user name thus obtaining an auto-generated guest name.

NOTE: When you load the external configuration file there is no need to pass the Zone name to the LoginRequest constructor, the API will use it automatically.

Here's another LoginRequest snippet:

? 1

sfs.send( new LoginRequest("Fozzie") );

In this case we send our user name, no password, and by not specifying the Zone name the system will use the one loaded from the configuration file.

What could go wrong?

During the login phase the Server performs a number of validations that could block the process and cause the LOGIN_ERROR event to be fired. Let's see in brief which problems could be found: · · · · · Missing Zone: the requested Zone name doesn't correspond to any available Zones on the server side. Zone Full: the requested Zone has reached the maximum amount of users allowed (configurable from the Zone Configurator in the Admin Tool). Server Full: the Server has reached its maximum capacity and cannot accept any other users. Duplicate names: no two users can take the same name in a Zone. This is casesensitive so two users with names "Fozzie" and "fozzie" will not create any problems. Bad Words in user name: if a Word Filter is configured in the Zone and it is applied to user names an error could be fired if such name matches any of the swear words configured in the filter. Banned user name: if the provided user name was banned by a moderator or admin the client will not be able to join the Zone until the banishment is expired.

·

The Room List

In the first code example we have seen that when the login is successful the Server will join the User in the requested Zone. Behind the scenes the Server will also perform a few other operations that we need to know about:

1. Auto-subscribe the default Room Groups: in the Zone Configurator you can

declare a number of public Room Groups. By default there is only one Group called "default" and you can add more, if necessary. A Room Group is nothing more than a String id that it is used to organize Rooms in different categories called Room Groups. Upon login the client will be subscribed to all the Groups specified in the "Default Room Groups" setting of the Zone.

2. Populate the client with the initial Room List: behind the scenes the client

receives a Room List which is populated with all the Rooms contained in all subscribed Groups. This operation is done once, at login time, then only small updates will be sent to the client to maintain the Room List up to date with the Server.

In the example provided here the Zone contains three different Room Groups called Europe, America and Asiaeach containing a certain amount of Rooms. If we suppose that the Zone defaultGroup property is set to "Europe,America" the client Room List will be populated with the details of all Rooms in those two groups and will receive updates about any "interesting" changes (e.g. a new room is created or another one is removed etc...)

Custom Login

One very common use case in the login process is to write an Extension that handles the user credentials and checks them against your database. In order to do so you will need a few preliminary steps: · · Setup a database connection. You can follow this tutorial on how to configure SFS2X to communicate with your DB. Prepare a simple Extension that will execute the validation of the user name and password. We provide an in-depth overview of the Extensions here and a login how-to tutorial here. Turn on the "Use custom login" setting in the Zone Configurator and setup your extension.

·

» Development Basics: join and create Rooms

One of the fundamental building blocks in the SFS2X framework is the Room object. Rooms allow to arrange players so that they can "see" each others and interact together. A typical use of Rooms is to create different categories in a chatting application, different meeting locations in a Virtual World or different places where to challenge other friends in all sort of games. Rooms are created in two different ways:

·

Statically: via the Zone Configurator module in the Admin Tool. This is a useful way to create persistent Rooms in your Zone (e.g. a Lobby) that are initialized as soon as the Server is launched. · Dynamically: rooms can be created and destroyed at runtime from either the client or the server side. There is no difference between Rooms created from one or the other side, however from server side it is possible to fine tune certain aspects of Rooms that the client cannot access for security reasons. For example, in our SimpleChat Zone (that we provide by default) there is one static Room called "The Lobby" which can be joined by any client after they heve successfully logged in the Zone. Joining a Room from the client API requires one line of code: ?

1

sfs.send( new JoinRoomRequest("The Lobby") );

The server will in turn respond with one of the following events:

1.

SFSEvent.ROOM_JOIN: if the operation is

successful 2. SFSEvent.ROOM_JOIN_ERROR: if an error occurred As usual we will need to register the events in our main SmartFox class instance to be notified of the result of the join operation. This is the full code required to handle both cases: ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

var sfs:SmartFox = new SmartFox(); sfs.addEventListener(SFSEvent.ROOM_JOIN, onJoin); sfs.addEventListener(SFSEvent.ROOM_JOIN_ERROR, onJoinError); ... ... // This code is executed after a successful login sfs.send( new JoinRoomRequest("The Lobby") ); public function onJoin(evt:SFSEvent):void { trace("Joined Room: " + evt.params.room.name); } public function onJoinError(evt:SFSEvent):void { trace("Join failed: " + evt.params.errorMessage); }

What could go wrong?

There are a number of possible things that could invalidate the attempt to join a Room:

·

Room is full: each Room has a certain capacity. If the maximum number of users is reached no more clients will be able to join until a few slots are made available. · Room doesn't exist: the Room id provided in the join request is not valid. No Room was found with that name or Room id ( a unique Room number which can be used in alternative to the name ) · Wrong password: Rooms can be made private via the use of a password. If you don't know this password or you have forgotten it, you won't be able to access the Room.

Joining one or more Rooms at the same time

In most cases the client will probably move from one Room to another leaving the previous Room after the new one is joined. This is the default mode in which SFS2X and its API operate by default. There are situations, however, in which we need the client to remain joined in a Room (typically a Lobby) while entering in another Room (maybe a game or chat Room). Let's examine the JoinRoomRequestconstructor from the client API. The following arguments are available:

·

id: the Room id can be either its name (String) or numeric id (int) · pass: (optional) a password if the Room is private · roomToLeave: (optional) indicates which Room should be left when the new one is joined with success. By default the last joined Room is used. However if the User is already joined in several other Rooms, here we can specify the Room that should be left. A value of -1 will indicate that no Room should be left. · asSpect: (optional) this option is availble for game Rooms only and it allow the user to join as a "spectator" in the game (more on this later). In order to keep track of the joined rooms on the client side, the API provide a couple of useful tools:

·

The lastJoinedRoom property of the SmartFox class instance provides a reference to the last Room that has been joined. When this value is set to null no Rooms have been joined yet. · The joinedRooms property of the same class will provide a list of all Rooms that the client is currently joined in. Finally the roomManager object in the SmartFox class allows to query the local room list data.

Creating Rooms dynamically

Rooms can be created from code at any time (after the login) from both client and server side. This is a quick example in Actionscript 3: ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

smartFox.addEventListener(SFSEvent.ROOM_ADD, onRoomAdded) smartFox.addEventListener(SFSEvent.ROOM_CREATION_ERROR, onRoomCreationError) // Create a new Chat Room var settings:RoomSettings = new RoomSettings("Piggy's Chat Room") settings.maxUsers = 40 settings.groupId = "ChatGroup" smartFox.send(new CreateRoomRequest(settings)) function onRoomAdded(evt:SFSEvent):void { trace("A new Room was added: " + evt.params.room ) } function onRoomCreationError(evt:SFSEvent):void { trace("An error occurred while attempting to create the Room: " + evt.params.errorMessage) }

The RoomSetting class allows to specify a large number of parameters that allow to fine tune all major and minor aspects of a Room. If the creation is successful the ROOM_ADD event will be sent back to the client, otherwise an error message is notified in the ROOM_CREATION_ERROR event. There could be several reasons why the Room cannot be created:

·

Lack of User Permission: depending on your configuration of the Privilege Manager a User with a certain Permission profile might not be allowed to create new Rooms in the system. · Duplicate Room name: no two Rooms can have the same name in the same Zone. The check is applied in case-sensitive mode. · Bad Words in Room name: if a Word Filter is configured in the Zone and applied to Room names (seeZone Configurator in the Admin Tool) the creation could be refused because swear words are detected in the Room name. · Maximum number of User created Rooms is reached: for security reasons the Zone is configured with a configurable max. number of Rooms that can be created at once by each User. If a client has already created 3 Rooms (which are all active) and the limit is set to 3 a new attempt to create a Room will fail until one of the previous Rooms is removed.

·

Maximum number of Rooms in the Zone is reached: a Zone can be configured with a maximum number of Rooms. When this value is reached, no more Rooms can be created until some of the old Rooms are removed. You can learn all the details about the RoomSettings parameters in the API documentation.

· ·

Client side API (Actionscript) Server side API

Basic Room concepts:

In the SFS2X framework Rooms are a quite sophisticated tool that can be configured in every minor detail. This article is not intended to analyze all these aspects. If you want to learn all the details we suggest to consult this Room Guide. In this section we are going to mention two different types of Rooms: game Rooms and nongames Rooms or "regular" Rooms.

·

Regular Rooms: by default a new Room is created with the isGame flag set to false. You can use these Rooms for all purposes such as the creation of a Lobby, chat Rooms, conference Rooms etc... · Game Rooms: when the isGame flag is set to true the Room offers a couple of extra functionalities that are essential for most games o Automatic assignment of player IDs: each User is automatically assigned a unique playerId value from 1 to N, providing the developer a simple way to recognize each player. Player IDs are managed and assigned by the Server transparently. o Support for non-player users (spectators): the Room can be created with a certain number of player slots and spectator slots. (e.g. 2 players, and 8 spectators). This will enable other users to join the Room as non-players and watch the game. If one or more players leave the game the developer could allow spectators to join the game. ( See the SpectatorToPlayerRequest and PlayerToSpectatorRequest )

When creating a game Room always remember to set the isGame flag and configure the maximum number of players and spectators allowed in the Room. For a more in-depth look at the Room features we highly reccomend the Room Architecture article in this very same documentation section.

Advanced Game Rooms and the Game API

The SFS2X platform provides a new set of client and server API specifically designed for game creation and management, including public and private games, game invitations, User and Room matching and lots more. A new type of game Room was introduced via the SFSGame class which is found both on the client and server side of the API. The new class extends the Room object providing dozens of new functionalities that enable developers to create advanced player matching, challenges and game invitations in a snap. We highly recommend to take a look at the Game API article for more details.

» Development Basics: Room architecture

The Room object is one of the most important building blocks in the structure of any multiplayer applications. It is responsible for grouping and connecting users together and make them interact with each others. From a developer's perspective it provides useful events that help building the application logic and each Room can be governed by a different server side Extension. In this chapter we are going to dissect the many features provided by the Room object and the new powerful functionalities offered in the SFS2X framework.

General architecture

We have mentioned in other articles of this documentation the introduction of a new key element in SFS2X, the Room Groups:

The above diagram should clarify what these Room Groups are all about. As we can see Rooms are still all contained in the Zone and they look like they are at the "same level". This is actually the best way to represent the general structure of Room Groups: in fact a Group is just an extra ID applied to each Room which acts as a "category" thus allowing filtering and separation. This new feature allows clients to limit their local view of the entire Zone structure just to one or more groups, instead of the whole catalog. The use cases are countless: separating game Rooms of different types, creating different areas of the application, hiding certain Rooms for certain type of users etc... The advantages of Room grouping are that we can minimize the Room data that is transmitted to the clients and reduce the number of update events, making the application more efficient.

Room Types

In SmartFoxSverer 1.x there were three main types: Regular, Game and Limbo each with its own different characteristics. SFS2X simplifies this approach offering two main types only (Regular and Game) but providing a much better fine-grained level of control over each parameter. With the new ability to set every minute detail of a Room developers will be able to quickly create many different behaviors, including the old "Limbo" type, and many more that were not possible before. Among the new features introduced in SFS2X we have the ability to rename, resize and lock/unlock each Room dynamically at runtime. Additionally we can now select which events the Room will send to the clients and precisely control the Room life-cycle. Room Variables are always available to store custom data and new features have been introduced (more on this later).

Room Settings

In order to create a Room there are just a few mandatory parameters, all the others can be left to their default values if you are not interested in more advanced options. Let's see all the available settings: · Basic o o o settings name: the name of the Room, must be unique password: (optional) if a password is specified the Room becomes private maxUsers: (optional) the maximum number of Users allowed in the Room (default == 20) o maxVariablesAllowed: (optional) the maximum number of RoomVariables allowed in the Room (default == 5) o isGame: (optional) if the Room is going to be used for games this flag should be set to true o maxSpectators: (optional) if this is a Game Room you should configure how many spectators are allowed inside (default == 0)

·

Advanced settings (optional) o autoRemoveMode: decides the life-cycle of a Room (when it's going to be destroyed) o roomVariables: provide a list of RoomVariables that will be added to the Room o extension: dynamically attach a Server-Side extension to the Room, adding custom logic for your games

·

Fine tuning settings (optional) o roomSettings: allow to specify permissions and events used by the Room. o badWordsFilter: configure the bad words filtering options o customPlayerIdGeneratorClass: provide a custom Class that handles the generation of playerId(s) for Game Rooms

All these settings are availble from both client and server side although from client side a few of them are limited to prevent malicious code from harming the server with spam or other nasty attacks.

Advanced Settings

The AutoRemoveMode option allows you to choose 4 different ways in which the Room life cycle can be handled: · DEFAULT: this is applied by default if the setting is not provided. In this mode regular Rooms are removed when the Room is emty and it's creator is no longer logged in the Zone. Game Rooms are removed as soon as they are empty WHEN_EMPTY: the Room is removed when empty WHEN_EMPTY_AND_CREATOR_IS_GONE: the Room is removed when the Room is empty and its creator has logged out NEVER: the Room is never removed

· · ·

NOTE #1: in order for the AutoRemove to be active the isDynamic flag of the Room must be set to true NOTE #2: this setting is available only in the server side API. From client side the DEFAULT mode is always used.

RoomVariables are a useful tool to store custom data in a Room to maintain application state. These variables are automatically synchronized between all users in the Room, making it a powerful tool to quickly change game states, scores and other values that are propagated to all players. The SFS2X API also provide several settings to control the access, life cycle and visibility of each RoomVariable. Let's see each property of a RoomVariable: · · · name: a unique vatiable name isPrivate: (optional) if set, the RV can only be modified or deleted exclusively by its creator (default == false) isPersistent: (optional) by default a user created RV is removed from the Room when the user leaves it. If this flag is turned on the RV will persist until the user logs out or disconnects. (default == false)

·

· ·

isGlobal: (optional) by default RV updates are sent to all users in the Room. A global RV is also updated outside of the Room to all users in the same Room Group (default == false) isHidden: (optional) if set the RV will be available on the server side only and never transmitted on the client side (default == false) value: the value of the RV. Many types are supported o Boolean o Integer o Double o String o SFSObject o SFSArray o Null: this is not really a value but it is used to delete RVs. When an RV is set to null it is removed from the Room

On the server side it is possible to create Room Variables that are owned by the Server itself. This is like a "virtual user" that never leaves the system thus allowing to create RVs that are never removed. In order to do so you will need to pass a null as the owner of the variable. A server side Extension can be dynamically plugged into the Room upon creation. This enables the developer to attach custom logic to the Room and control its usage. Typically this is used for running games, enabling advanced modertation controls, filtering messages, and tons more... For a full discussion on Extensions please make sure to consult this documentation article.

Fine Tuning Settings

Room Events and Permissions can be finely tuned when creating a new Room. The following Event are available: · · · USER_ENTER_EVENT: toggles the notification of a new user joining the Room (default == true) USER_EXIT_EVENT: toggles the notification of a user leaving the Room (default == true) USER_COUNT_CHANGE_EVENT: toggles the notification of user and spectator count for all Rooms in the subscribed groups (default == true). NOTE: this event can be further finely tuned at the Zone level by specifying how often these updates should be notified. The default is real-time, so as soon as they happen. In order to save bandwidth you might want to reduce these updates to 1 time per second or less. Check the Zone Configurator in the AdminTool for more on this. USER_VARIABLES_UPDATE_EVENT: toggles the notification of UserVariable updates (default == true)

·

NOTE: the USER_ENTER and USER_EXIT events should always be used in conjunction. If you decide to turn one of them off the other should also be set in the same way as they are two sides of the same notification.

This is a list of Permissions available: · · · · ROOM_NAME_CHANGE: toggles the ability to change the Room name to at runtime by its creator PASSWORD_STATE_CHANGE: toggles the ability to to add/remove and modify the Room password by its creator PUBLIC_MESSAGES: toggles the ability to send public messages CAPACITY_CHANGE: toggles the ability to resize the Room at runtime by its creator. It is possible to "shrink" the Room capacity so that maxUsers < userCount. In this case nothing will happen to the "extra" users. As soon as clients will leave the Room the userCount will get down to the new maxUsers value.

If a Words Filter is configured at the Zone level we will be able to apply it to public messages exchanged in the Room. Finally, for very advanced game logic, we can provide an alternative PlayerId Generator class that uses custom code to handle and distribute player IDs in the Room. The class is required to implement thecom.smartfoxserver.v2.util.IPlayerIdGenerator class provided in the SFS2X server API.

» Development Basics: SFSObject and SFSArray

SmartFoxServer 2X introduces two new fundamental classes, SFSObject and SFSArray, that are central in the manipulation and transmission of data between client and server. The two classes are common across all API in all languages (server side API included) making it very easy to port code to any platform and on every side of the application. SFSObject and SFSArray represent a platform-neutral, high level objects that abstract the data transport between client and server. They are used to respectively represent data in form of a Map/Dictionary or List/Array, they can be nested to create complex data structures and they support many different data types (from bytes to integers, doubles, strings and a lot more)

These two classes provide fine-grained control over each data element sent over the network and offer high speed serialization using the default SFS2X binary protocol. Let's consider this simple example, we need to send the data relative to a combat vehicle in a multiplayer game: ?

1 2 3 4 5

ISFSObject sfso = new SFSObject(); sfso.putByte("id", 10); sfso.putShort("health", 5000); sfso.putIntArray("pos", Arrays.asList(120,150)); sfso.putUtfString("name", "Hurricane");

In the code we use a single Byte (signed 8-bit) to send any small integer value, a Short (signed 16-bit) for larger values and integers for any number that should be represented as regular (signed) 32-bit value. In this example we imagined to have a wide RTS environment and we used an array of Int to transmit the x/y position of the vehicle.

Supported data types:

This is a list of all types supported by both classes:

Type NULL BOOL BYTE SHORT INT LONG FLOAT DOUBLE UTF-STRING BOOL ARRAY

Bytes used 1 1 1 2 4 8 4 8 variable variable

Ranges and Limits N/A true/false 8 bit Int, 0 - 2^8 (Java uses a signed byte) 16 bit Int, -2^15 to 2^15 32 bit Int, -2^31 to 2^31 64 bit Int, -2^63 to 2^63 See single precision floating point format See double precision floating point format UTF-8 multibyte encoding max string len: 2^15 chars (32 KB) max array len: 2^15 items (32767)

BYTE ARRAY SHORT ARRAY INT ARRAY LONG ARRAY FLOAT ARRAY DOUBLE ARRAY UTF-STRING ARRAY SFSOBJECT SFSARRAY CLASS (*)

variable variable variable variable variable variable variable variable variable variable

max array len: 2^31 items (2147483648) max array len: 2^15 items (32767) max array len: 2^15 items (32767) max array len: 2^15 items (32767) max array len: 2^15 items (32767) max array len: 2^15 items (32767) max array len: 2^15 items (32767) max key-pair values: 2^15 items (32767) max array len: 2^15 items (32767) N/A -- Please check this advanced tutorial

* = this type is currently supported only by the Server side API and Actionscript 3 client API. It allows to transmit custom classes using a series of conventions.

Array types are particularly useful when transmitting lists of values that are all of the same type, the result is a very compact data structure. On the other hand, if you need to send a list of values with different types, the SFSArray will be the best choice.

NOTE for Actionscript 3 developers: there is a fundamental difference between AS3 Arrays and these Arrays. The latter are dense arrays, meaning they must have a value (or null) in each index whereas AS3 Arrays don't have the same restriction.

Example of usage

One example of SFSObject/SFSArray usage is in Extension development where they are employed to transmit every request and response. By expanding the example provided at the beginning of this article let's see a full use case. The client (Actionscript 3) needs to transmit the following data to a server Extension: ?

1 2 3

public function sendSomeData():void { var sfso:SFSObject = new SFSObject(); sfso.putByte("id", 10);

4 5 6 7 8 9 10 11

sfso.putShort("health", 5000); sfso.putIntArray("pos", [120,150]); sfso.putUtfString("name", "Hurricane"); // Send request to Zone level extension on server side sfs.send( new ExtensionRequest("data", sfso) ); }

The server Extension will receive the same data as the parameters object in one of its Request handlers: ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14

public class DataRequestHandler extends BaseClientRequestHandler { @Override public void handleClientRequest(User sender, ISFSObject params) { // Get the client parameters byte id = params.getByte("id"); short health = params.getShort("health"); Collection<Integer> pos = params.getIntArray("pos"); String name = params.getUtfString("Hurricane"); // Do something cool with the data ...

} }

Inspecting an SFSObject/SFSArray

Let's take a closer look at these two classes and see in detail what happens behind the scenes. Both objects provide two useful methods to dump their content in a hierarchical or hex-dump format. If we take the same example provided at the beginning of the article and call the getDump() method on the SFSObject we will see this output:

(short) health: 5000 (utf_string) name: Hurricane (byte) id: 10 (int_array) pos: [120, 150]

Each element in the object is listed with the format: (type) key-name: value

If you want a more low level view on how the object is represented in binary you can call thegetHexDump() method:

Binary size: 54 12 00 04 00 06 68 65 61 6C 74 68 03 13 88 00 04 6E 61 6D 65 08 00 09 48 75 72 72 69 63 61 6E 65 00 02 69 64 02 0A 00 03 70 6F 73 0C 00 02 00 00 00 78 00 00 00 96 .....health..... name...Hurricane ..id....pos.....

Byte Arrays

One special mention should go to the ByteArray type which provides a mean of transferring binary data to and from the server. This could be used to transfer small files, images, media files, encrypted data, etc... Flash developers could improve the security of their application by sending external SWF files via the socket which would go undetected when spying the HTTP traffic. When transferring large chunks of data we highly recommend to pre-compress them in order to optimize the size and avoid heavy strain on the Server side. In particular the dynamic protocol compression won't kick in for large bulks of data (tens/hundreds MB) in order to avoid significant performance degradation (especially under heavy concurrency). Zipping or gzipping the file(s) before the transmission is highly recommended.

SFSObject/SFSArray best practices

SFSObject and SFSArray are not thread safe therefore additional care should be used when sharing these objects in a multi-threaded environment. In 90% of the cases SFSObject/SFSArray are only used for data transport and handled as local variables, so no concurrency is involved. If you use several classes to represent the model in your games it is advisable to add an toSFSObject() andnewFromSFSObject() methods to your classes in order help converting them to an SFSObject representation and viceversa. At least you should do it for those classes that are transferred over the network more often. This is an example in Java where we suppose we have an RTS game with several vehicles that are updated often to the clients. Specifically we have a CombatQuad class (Dune2 nostalgia) representing one of the vehicles in the game.On the server side we will probably need at least a toSFSObject() method to extract the relevant properties from the instance and send them in the update. ?

1 2 3

public class CombatQuad { private int unitID;

4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

private private private private

int int int int

posx; posy; energyLevel; bulletCount;

public CombatQuad(int unitID) { this.unitID = unitID; this.energyLevel = 100; this.bulletCount = 20; } //... More getters and setters... public ISFSObject toSFSObject() { ISFSObject sfso = new SFSObject(); sfso.putByte("id", unityID); sfso.putShort("px", posx); sfso.putShort("px", posy); sfso.putByte("el", energyLevel); sfso.putShort("bc", bulletCount); return sfso;

} }

We omitted all the getters/setters to go straight to the point and show how this works. The toSFSObject() method takes care of extracting the properties we need and also format them using the smallest data type possible. Our Extension code will be greatly simplified when it is time to send these object data in an update: ?

1 2 3 4 5 6 7 8

public void sendMapUpdate(CombatQuad quad, OtherObject other, User recipient) { ISFSObject responseObj = new SFSObject(); responseObj.putSFSObject("quad", quad.toSFSObject()); responseObj.putSFSObject("other", other.toSFSObject()); send("quadUpdate", responseObj, recipient);

}

On the client side we will use a similar, although reverse, approach: we will rebuild the class instance from the properties transmitted via the SFSObject. In order to do so we will implement a static constructor called newFromSFSObject().

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

public class CombatQuad { private var unitID:int; private var posx:int; private var posy:int; private var energyLevel:int; private var bulletCount:int; public static function newFromSFSObject(sfso:ISFSObject):CombatQuad { var combatQuad:CombatQuad = new CombatQuad(sfso.getByte("id")); combatQuad.posx = sfso.getShort("px"); combatQuad.posy = sfso.getShort("py"); combatQuad.energyLevel = sfso.getByte("el"); combatQuad.bulletCount = sfso.getByte("bc"); return combatQuad; } function CombatQuad(unitID:int):void { this.unitID = unitID; } //... More getters and setters... }

Actionscript Only: Object/Array to SFSObject/SFSArray conversion

In Actionscript 3 the SFSObject and SFSArray classes provide direct conversion to and from their respective native types: Object and Array . This was introduced since version RC1b to help migration from previous code written for SmartFoxServer 1.x and to provide some extra convenience for developers that use dynamic types. It is necessary to underline that the use of generic Objects instead of Classes for the data model is in general not recommended, because it won't take advantage of the type optimizations introduced in Actionscript 3. Properties attached dynamically to an Object are not strong-typed and they cannot be optimized by the compiler. Anyways this can be useful in several occasions and it can help accelerating the developement. The new methods are: · SFSObject.newFromObject(): static constructor, generates a new SFSObject instance from an Object. Supported types are: null, Boolean, Number, String, Object, Array. The operation supports nested objects.

· ·

·

SFSObject.toObject(): converts the SFSObject instance into an Object, including nested objects. SFSArray.newFromArray() static constructor, generates a new SFSArray instance from an Array. Supported types are: null, Boolean, Number, String, Object, Array. The operation supports nested objects. SFSArray.toArray() converts the SFSArray instance into an Array, including nested objects.

The downsides of using this approach is that you loose the ability to fine tune the numeric types: all numbers will be converted to either a 32-bit integer (non decimal values) or 64bit double (decimal values).

More resources

For more details and examples on how to use SFSObject and SFSArray we highly recommend to take a look at the examples provided with SFS2X and consult the documentation found in the "API Documentation" section of this website. Also please check the Class Serialization advanced tutorial.

» Database Recipes

In this section we are going to demonstrate several examples of integration between SFS2X and an external database. We used MySQL for all recipes but you can easily use any other database if you prefer. We used standard SQL in all code so it can be ported to any other RDBMS. · · Custom login with database Querying the database and sending resultsets

Recipe #1: Custom login with database

This recipe discusses the common scenario in which you need to validate the client credentials against the user profiles stored in a database. We will also show how to execute more custom logic right after the login, for example setting UserVariables and joining a Room from server side. Implementing a custom login on the server side is a simple process. SFS2X fires two different login events:

·

·

USER_LOGIN: fired when a client requests to join a Zone. Here you can validate the client credentials and decide if the User can continue the login process. At this stage the client is represented as a Session object, not as an SFSUser yet. USER_JOIN_ZONE: notified when a client has successfully joined a Zone (and turned into an SFSUser)

The provided example comes with a MySQL dump that can be used to generate the database table used by the Extension. These are the steps to setup and test the example application: · · · We need a database called sfs2x, if you don't have one please create it Use the provided muppets-table.sql file to import the 'muppets' table into the database. This contains a few login accounts that you can use to test the login Deploy the extension by copying it in a subfolder of {your-sfs2xdir}/SFS2X/extensions/ You can choose any name for the subfolder, for example muppetsExt Launch the AdminTool, choose the Zone Configurator and edit the SimpleChat zone. Set the extension for the Zone as follows: o Name: muppetsExt (or the name you have chosen, if different) o Type: JAVA o File: sfs2x.extension.test.dblogin.DBLogin In the "General" tab of the Zone Configurator make sure to activate the custom login option. At this point you should be ready to restart SFS2X, unless you also need to setup the database connection. If you haven't done this previously we suggest to follow this HowTo tutorial.

·

· ·

You can now startup the provided client and test the extension. » The login handler: The LoginEventHandler class is where we check the credentials. In this example we access the DBManager connection and use a PreparedStatement in order to build the query with parameters and sanitize possible bad characters in the external arguments. ?

1 2 3 4 5 6 7 8 9

// Grab a connection from the DBManager connection pool connection = dbManager.getConnection();

// Build a prepared statement PreparedStatement stmt = connection.prepareStatement("SELECT pword,id FROM muppets WHER name=?"); stmt.setString(1, userName); // Execute query ResultSet res = stmt.executeQuery();

As an alternative to this approach you could use the DBManager.executeQuery(String sql, Object[] params) which works similarly without the need to work at the connection level. We didn't use it for this example because at the time of writing it wasn't available yet (requires RC1b or higher) When an errror is encountered (e.g. bad password) we raise an exception of type SFSLoginException and we provide additional error code: ?

1 2 3 4 5 6 7

if (!getApi().checkSecurePassword(session, dbPword, cryptedPass)) { SFSErrorData data = new SFSErrorData(SFSErrorCode.LOGIN_BAD_PASSWORD); data.addParameter(userName); throw new SFSLoginException("Login failed for user: " + userName, data); }

In the case of a bad username or bad password we also specify the name or password used so that this is reported back to the client. » Post login handler: When the USER_JOIN_ZONE event is notified we are ready to do more work with the User which is now fully logged in the system. The ZoneJoinEventHandler class sets the "dbID" User Variable with the user id coming from the database and finally joins the User in the main lobby room. ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

public class ZoneJoinEventHandler extends BaseServerEventHandler { @Override public void handleServerEvent(ISFSEvent event) throws SFSException { User theUser = (User) event.getParameter(SFSEventParam.USER);

// dbid is a hidden UserVariable, available only server side UserVariable uv_dbId = new SFSUserVariable("dbid", theUser.getProperty(DBLogin. uv_dbId.setHidden(true);

// The avatar UserVariable is a regular UserVariable UserVariable uv_avatar = new SFSUserVariable("avatar", "avatar_" + theUser.getN // Set the variables List<UserVariable> vars = Arrays.asList(uv_dbId, uv_avatar); getApi().setUserVariables(theUser, vars); // Join the user Room lobby = getParentExtension().getParentZone().getRoomByName("The Lobby");

19 20 21 22 23 24 25 26 27

if (lobby == null) throw new SFSException("The Lobby Room was not found! Make sure a Room call work correctly."); getApi().joinRoom(theUser, lobby);

} }

>> DOWNLOAD the source files for this recipe <<

^top

Recipe #2: Querying the database and sending resultsets

This recipe shows how to read data from a database and send it to the client in a convenient format. TheSFSDBManager.executeQuery method on the server side provides an effective way to execute a SQL query and obtain an SFSArray-based data strcuture which can be sent to the client on the fly. For more details on this we recommend to consult the SFSDBManager javadoc. In this simple example we are going to obtain a list of records from a people database and send them over to the client in a few lines of code. The sources come with a MySQL dump that can be used to generate the database table used by the Extension. These are the steps to setup and test the example application: · · · We need a database called sfs2x, if you don't have one please create it Use the provided people-table.sql file to import the 'people' table into the database. Deploy the extension by copying it in a subfolder of {your-sfs2xdir}/SFS2X/extensions/ You can choose any name for the subfolder, for example peopleExt Launch the AdminTool, choose the Zone Configurator and edit the SimpleChat zone. Set the extension for the Zone as follows: o Name: peopleExt (or the name you have chosen, if different) o Type: JAVA o File: sfs2x.extension.test.people.PeopleExtension In the "General" tab of the Zone Configurator make sure that custom login is turned off.

·

·

·

At this point you should be ready to restart SFS2X, unless you also need to setup the database connection. If you haven't done this previously we suggest to follow this HowTo tutorial.

You can now startup the provided client and test the extension. » The request handler: In the source code you will find a GetPeopleHandler class which responds to the "getPeople" request coming from the client. Let's take a look at what it does: ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

public class GetPeopleHandler extends BaseClientRequestHandler { @Override public void handleClientRequest(User sender, ISFSObject params) { IDBManager dbManager = getParentExtension().getParentZone().getDBManager(); String sql = "SELECT * FROM people"; try { // Obtain a resultset ISFSArray res = dbManager.executeQuery(sql); // Populate the response parameters ISFSObject response = new SFSObject(); response.putSFSArray("people", res); // Send back to requester send("getPeople", response, sender);

} catch (SQLException e) { trace(ExtensionLogLevel.WARN, "SQL Failed: " + e.toString()); } } }

The returned SFSArray contains all the records represented as SFSObject(s), which makes it very easy to extract the field values. This is how it is done on the client side when the EXTENSION_RESPONSE event is handled: ?

1 2

private function onExtensionResponse(evt:SFSEvent):void { var params:ISFSObject = evt.params.params as ISFSObject

3 4 5 6 7 8 9 10 11 12 13 14 15 16

var peopleArray:ISFSArray = params.getSFSArray("people") var dump:String = "PEOPLE LIST RECEIVED:\n\n"

for (var i:int = 0; i < peopleArray.size(); i++) { var item:ISFSObject = peopleArray.getSFSObject(i) dump += " > " + item.getUtfString("name") + ", " + item.getUtfString("locati item.getUtfString("occupation") + "\n" } dTrace(dump) dTrace("Total records: " + peopleArray.size())

}

All we need to do is loop through each element in the received SFSArray. Each item is an SFSObject that represents one row of the resultset which allows us to access its fields by name. In our case the field names are: name, location, occupation.

» Writing the first Extension

Extensions are a fundamental element in SmartFoxServer 2X, they open a world of limitless possibilities for developers to create their custom game logic and integrate other technologies to support their project. To draw a parallel we could think of Extensions as the equivalent of server side code for web pages, where all the "business" logic is developed. Provided that you have an intermediate understanding of Java or any other object oriented language (Actionscript 3, C#, C++ etc...), it will be very easy to get started writing you first Extension.

Use an IDE to assist the development

First thing first we need an IDE that will help us writing the code, compiling it and creating a binary package (.jar file) that we will deploy in SFS2X. There are several well known Java IDEs available, both free and commercial. We usually recommend: · · IBM's Eclipse Oracle's NetBeans

·

JetBrain's IntelliJ

In this tutorial we will be using Eclipse, however setting up a new project in another IDE should be a very similar process, especially if the IDE you use is the one you are most familiar with.

Setting up a new project

Let's start Eclipse and from the File menu choose New... > Project. Choose Java Project from the Java folder and click Next

In the new window we give the project a name, MyJavaExtension and proceed to the next screen.

Now it is time to add the libraries that are needed to compile our Extension. Click on Add External JARs...and browse your file system to {your-sfs2x-folder}/SFS2X/lib/ Select two files: sfs2x-core.jar and sfs2x.jar and click finish in the main window.

The new project will appear in the Package Explorer panel which by default is located in the top left column of your interface. Now you can create a new Java Class by right-clicking on the src folder and selectingNew > Class from the menu.

Here we proceed by entering the name of the Class, MyJavaExtension and its package which in this case will be my.test

Finally we have created our main Extension Class and we can now proceed adding some very basic code.

In this article we are not going to dive into the details of how to write the Extension code. To learn more about server side coding we already many resources that cover a lot of ground: · · · · · Extension API tutorial Server side extension in depth article Advanced Extension topics The server side Java API, in particular the SFSApi Class which represents the central point for interacting with the Server. SFS-Tris2X: this is a bundled game example coming for all supported platforms that you will find in the Server's example folder, including the server code sources.

Deploying the extension

The deployment follows a simple set of rules. Every extension must reside in a folder under the {your-sfs2x-folder}/SFS2X/extensions/ directory. The name you choose for this folder will determine the Extension name that is requested in the Admin Tool when you activate the Extension. Let's say we create a folder called MyFirstExtension under extensions/ Under this path we can deploy the jar file containing our server code classes and any other jar file that might contain other dependencies required in our Extension.

NOTE: The name of the .jar files is absolutely unimportant. The system will simply scan all the .jar files found in the folder and load the classes contained in it.

In Eclipse the compiled classes are usually stored in the bin/ folder which sits next to the src/ folder in your project. You can export the compiled code in a .jar file directly to the SFS2X deployment folder by following these steps: In the Package Explorer right-click the project folder and choose Export... In the next dialogue box open the Java folder and choose JAR file then click Next. A new window will open, click the Browse... button in the middle of it and navigate to the SFS2X deployment folder (as we explained) specifying a name for the jar file to be created.

Click Next to advance to the next screen. Here you can store the export settings so that the next time you need to re-deploys your Extension it can be done in a snap, by simply clicking a file in your project folder. Activate the check box next to the "Save the description..." and click Browse... You will be presented a list with all your Java projects, select your own and provide a name for the export configuration, click Ok to close the panel and click Finish to complete the process.

At this point you will have deployed your jar file in the SFS2X extension folder and also saved the export configuration for future redeploys. SFS2X can actively monitor all the extension folders for changes and as soon as it detects a new jar file it will restart your extensions. This is valid for all Zone-level Extensions, while it cannot be done for Rooms. In order to toggle this feature you need to launch the Server Configurator in the AdminTool and turn on the Extension file monitor.

Assigning an Extension to a Zone or Room

The most simple way to plug our Extension to a Zone or Room is via the AdminTool. Launch the Zone Configurator, select a Zone and enter the edit mode. You will find an Extension tab where you can configure a few parameters. Essentially all you need to provide is the Extension name and the main Extension class (the one extending SFSExtension)

Now you can save, restart the Server and the Extension will be activated.

Python Extension

We should also quickly mention that it is possible to write Python scripts and run them as Extensions, although this is not recommended for production. The reason is that interpreted Python code is not able to deliver the performance obtained with pure Java code and we suggest its usage only for rapidly building prototypes, proof-of-concepts, test cases etc... The process to build a Python extension is almost identical to what we have already described, but with a few small differences: · You don't need Eclipse or a similar IDE to write the code. You can deploy directly your Python scripts under the Extension folder, nothing else is required. The server will then load the code, compile it at runtime and execute it. You need to specify in the AdminTool PYTHON instead of JAVA as the Extension type and the name of the script as the Extension File in the configuration.

·

What's next

Now that you have gained the basic understanding of how to create an Extension, it is time to learn more about the features offered by the Server framework. Make sure to continue the learning process byfollowing the links we recommended in the early stages of this document.

» Extension API Tutorial

In this article we are taking a look at the most useful elements in the Extension API and how to harness the server framework and the accompanying javadoc.

BaseSFSExtension vs SFSExtension

We provide two classes that work as the base type for your main Extension class: BaseSFSExtension andSFSExtension, both found under the com.smartfoxserver.v2.extensions package. The former is provided mainly for compatibility with SmartFoxServer PRO 1.x, while SFSExtension is the recommended class to Extend in order to get the best of SFS2X. In this article we are going to assume that your main Extension class extends SFSExtension. (For more details on the differences between the two please read the advanced tutorial)

Zone level / Room level

Server extensions can be plugged into a Zone (we call it Zone-level Extension) to govern the whole application or into a single Room (Room-level Extension) in order to only manage that particular Room. The difference between the two approaches is exclusively in scope. A Zone-level Extension can listen to any event in the Zone and control all the Rooms and Users in that it manages. On the other hand a Room-level Extension can only listen to the events in that Room and manage the Users contained in it. A typical use of Room-level Extension is to manage the logic of a game running in the Room.

Working with the server API

The SFS2X framework is mainly composed of three different elements: · API classes: they provide an organized way to access the many functionalities of the server, such as creating Rooms, setting Room/User variables, handling game challenges, matching players and a lot more. Services: they manage specific elements of the server. For example the RoomManager provides access to the Rooms created in a Zone, allowing to perform searches, filtering etc... Data classes: they are the most numerous in the framework and they provide abstractions to the Server data (e.g. User, Room, Buddy, Invitation, Event...)

·

·

We will now take a closer look to each one and discuss them briefly.

API Classes

The simplest way to access the API is via the getApi() method in your Extension, which provides a reference to the Server core API. You can learn all its methods by checking the SFSApi class javadoc. Here is a quick example that shows how to create a Room: ?

1 2 3 4 5 6 7 8

void makeRoom() throws SFSCreateRoomException { CreateRoomSettings settings = new CreateRoomSettings(); settings.setName("The music Room"); settings.setMaxUsers(20); getApi().createRoom(getParentZone(), settings, null);

}

SFS2X also provides specialized API for the Buddy List and the Game Matching features. In the following paragraph we explain how you can access them.

The SmartFoxServer class

The next important element in the framework is the SmartFoxServer class which provides access to the many services running in the system. Although working with services is a quite advanced topic, we can also use this object to access the specialized API. Example: ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14

void getMoreApi(User owner) throws SFSCreateRoomException { ISFSGameApi gameApi = SmartFoxServer.getInstance().getAPIManager().getGameApi(); ISFSBuddyApi buddyApi = SmartFoxServer.getInstance().getAPIManager().getBuddyApi(); CreateSFSGameSettings settings = new CreateSFSGameSettings(); settings.setName("PongRoom"); settings.setMaxUsers(2); settings.setMaxSpectators(8); // ...more game settings here... gameApi.createGame(getParentZone(), settings, owner);

}

Data classes

There are dozens of different data classes in the framework that provide an abstraction for the various elements of the API: Zone(s), Room(s), User(s) etc... They are mainly found under thecom.smartfoxserver.v2.entities.* package. One important facet of server side development is that data classes should always be used for reading properties but rarely for writing them. Making direct changes to the values of a Zone or Room etc. will cause only a local change and no update is going to be sent to the clients.

In other words these classes expose lots of getters and setters but, for Extension development,only getters are really significant. The changes are applied via the API instead, which provide a mechanism for maintaing all clients up to date with the changes occurring in the Server.

A practical example is setting UserVariables for a client. If you consult the javadoc, you will notice that the User object exposes a setVariable() method which you might use. There is nothing wrong with it, but the change will exclusively take its effect on the server side, and no updates will be sent to the clients (which is necessary). The proper way to proceed is to use the API setUserVariables(...) method which enables to automatically notify all the clients that require an update and even fire a server event, where necessary. (For example for other Extensions listening for those changes).

More resources

Now that you have learned the basics of Extension development it is time for the fun stuff, writing your own and experimenting with the framework. Also we highly recommend the following resources to learn more: · · · · Server side extension in depth article Advanced Extension topics The server side Java API, in particular the SFSApi Class which represents the central point for interacting with the Server. SFS-Tris2X: this is a bundled game example coming for all supported platforms that you will find in the Server's example folder, including the server code sources.

» Server Side Extensions

SmartFoxServer 2X Extensions have been revisited and improved under many aspects. We have concentrated our attention on the Java extension development and have dropped support for scripting languages. The main reasons are: · Performance: extensions written in Java perform orders of magnitude better than any scripting language such as Javascript or Python. With a new server architecture oriented to enterprise-level applications the use of dynamic languages would soon become a bottleneck. We still think that scriping is a great way for quickly prototyping ideas but we don't recommend it in production. You will still be able to plugin dynamic language engines such as Rhino and Jython for this purpose. Concurrency: Java allows to take full control over concurrency which is a critical aspect of server side code. In particular the latest Java 5/6 concurrent collections and utilities provide the developers with great concurrency tools which would not be fully accessible from dynamic languages. Integration: integration with other libraries is more natural if done directly in Java and avoids unexpected consequences that may arise later in the development.

·

·

The Java Extensions framework has been vastly improved. The Server API are written to comply with the latest Java 6 standards, taking advantage of all the latest features such as concurrent collections, generics, enums, annotations etc... The API also conform with Java best practices and provide an unprecedented level of customization. The development cycle has been simplified in order to allow one-click deployment from your IDE and complicated operations such as classpath modifications or class loading matters are completely managed behind the scenes.

» One Extension to rule everything

The first noteworthy change in the Extensions architecture is that developers can plug a single Extension to their Room or Zone. This is in contrast with SmartFoxServer 1.x, where it was possible to attach multiple extensions. The rationale behind this is that the new Extensions will allow you to do more with less: · Since a Zone represents an isolated application running in the Server, we find that using a one-extension-per-application model is the most logical way to provide extendibilty. An Extension has to be seen as a Java program containing all the logic of your Server application. Using simple object OOP practices you can separate the concerns of your code just as you would if you were writing a stand-alone application. The new Extensions API will also assist you in this accomplishment with a new set of tools. Using a single Extension allows to keep state in a central place and share it with all the classes in your code.

·

·

·

Using a single Extension eliminates the need of awkward interoperability tools that just complicate the flow of your code and add class-loading and concurrency issues

» Extension overview

Java Extensions are deployed in a single .jar file to a folder which represents the name of the Extension. The image below shows the main extensions/ folder which is the root under which all Extensions are published. Our testExtension folder contains one .jar file with all our code.

There are a few more things to note: · · You can deploy multiple .jar files under the same extension folder, e.g. dependencies or other libraries. They will all be loaded under the same Class Loader. There is a special folder called __lib__ where you can put dependencies as well. This way you can choose which libraries are shared across multiple Extensions and which are local to a specific Extension.

Both approaches can be useful depending on what you need to do: · Libraries deployed under your Extension folder will be loaded in the Extension Class Loader. This means that you can change that specific library without affecting other Extensions. Libraries shared under the __lib__ folder are loaded in the parent Class Loader. If you change any of these dependencies it will affect all Extensions that use it.

·

Custom configuration: Each extension can auto-load a .properties file containing custom settings that are immediately available in your code. By default the Extension API will attempt to load a file called config.properties, but you will be able to specify any other file name. This is particularly useful when attaching the same Extension to multiple Zones or Rooms but you need to pass different settings to each one. This is an example of how an Extension is configured in the AdminTool:

· · · · ·

Name refers the folder extension name Type indicates the type of extension in use (Java is recommended) File is the fully qualified name of the main Extension class (or the python script file name) Properties file is an optional name of .properties file deployed in the extension folder containing custom extension settings. (default = config.properties) Reload Mode indicates if the extension is monitored and auto-reloaded (AUTO) or reloaded manually (MANUAL) or not reloadable (NONE)

Classpath management: The good news is that you will not have to touch the classpath anymore. SmartFoxServer 2X will scan the dependency and extension folders and load all jar files for you. Extension reloading: SmartFoxServer 2X provides Extensions hot-redeploy which can be very useful during the development phases. When this feature is turned on the server will monitor your extension folders and reload your code when a .jar file is modified. All you need to do is configure your Java IDE (e.g. Eclipse or Netbeans) to build or copy the .jar file directly under the SmartFoxServer extension folder, and you will have a oneclick deploy system. Required dependencies: In order to start creating your own extensions you will need to add a few libraries to the project in your favorite IDE: · · lib/sfs2x.jar lib/sfs2x-core.jar

The logging jar(s) are not mandatory, unless you need specific logging features:

· · ·

lib/log4j-1.2.15.jar lib/slf4j-api-1.5.10.jar lib/slf4j-log4j12.jar

Server Side javadoc: You can consult the server side API javadoc at this address The main entry point of API is the com.smartfoxserver.v2.api.SFSApi class

» A peek at the Extension API

There are two main classes that provide the base for any Extension you will create. · BaseSFSExtension: provides the basic four methods already known in SmartFoxServer 1.x: init(), destroy(), handleClientRequest(), handleServerEvent(). We have included this class mainly for compatibility with the previous Extension approach, but we think you will find the next one more convenient.

·

SFSExtension: this is the recommended new base class that you should inherit in your Extension main class. SFSExtension provides built-in services for proper separation of request and event handlers, automatic listener release upon destruction of the Extension and more...

The simplest Extension possible: Let's take a look at the most simple Extension class we can possibly write: ?

1 2 3 4 5 6 7 8

public class MyFirstExtension extends SFSExtension { @Override public void init() { trace("Hello, this is my first SFS2X Extension!"); } }

This is the bare minimum to create a fully functional Extension: one method, init(). Of course you might be already thinking that you will not be able to accomplish much with a single init() method, so let's add a request handler. We want the User to be able to send us two numbers and we will add them and send the sum back. The recommended practice when developing an Extension is that each request or event handler is a separate class in order to clearly separate each piece of Logic in your code.

There are two interfaces we provide to create a Request Handler or a ServerEventHandler. Before we create our addition request handler we have to define which parameters we expect from the client: We will expect two integers called n1 and n2 and we will send back an integer that is the sum of the two. Now we can start coding the handler: ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

public class AddReqHandler extends BaseClientRequestHandler { @Override public void handleClientRequest(User sender, ISFSObject params) { // Get the client parameters int n1 = params.getInt("n1"); int n2 = params.getInt("n2"); // Create a response object ISFSObject resObj = SFSObject.newInstance(); resObj.putInt("res", n1 + n2); // Send it back send("add", resObj, sender); } }

The handleClientRequest method receives 2 parameters: · · sender: the User who sent the request params: an object with all the parameters sent by the User. The SFSObject is the meat and potatoof all objects exchange between client and server. Both the server and client API provide the same ISFSObject interface to ensure coding consistency.

The code in the above example should be self-explanatory. We first obtain the two expected integers, prepare a new SFSObject for the response and add the result n1 + n2. Finally we invoke the send(...)method on the parent Extension to send the response back to the User. · · · "add": is the unique command name used for this request (always use the same for the same request/response pair) resObj: contains the data we send to the client sender: is the recipient of the response

In order to "connect" this handler with the main Extension all we need to do is adding one line in the init() method:

?

1 2 3 4 5 6 7 8

@Override public void init() { trace("Hello, this is my first SFS2X Extension!"); // Add a new Request Handler addRequestHandler("add", AddReqHandler.class)

}

The addRequestHandler() method allows to register an handler for a specific request id. Similarly it is possible to add any numbers of ServerEvent handlers using the addEventHandler() method. This is an example of how you can listen to the USER_LOGIN server event. ?

1 2 3 4 5 6 7 8 9 10 11 1 2 3 4 5 6 7 8 9 10 11

public class LoginEventHandler extends BaseServerEventHandler { @Override public void handleServerEvent(ISFSEvent event) throws SFSException { String name = (String) event.getParameter(SFSEventParam.LOGIN_NAME); if (name.equals("Gonzo") || name.equals("Kermit")) throw new SFSLoginException("Gonzo and Kermit are not allowed in this

Zone!"); } }

Now we go back to the main Extension class and modify the init(): ? @Override public void init() { trace("Hello, this is my first SFS2X Extension!"); // Add a new Request Handler addRequestHandler("add", AddReqHandler.class) // Add a new SFSEvent Handler addEventHandler(SFSEventType.USER_LOGIN, LoginEventHandler.class); }

Now that we have created our first basic Extension there are few things that we can note: · As a difference with the 1.x approach we don't clutter the main Extension code with infinitely long if orswitch blocks to dispatch the request or event to the proper handler

· ·

In SFS2X Extension classes subscribe to events as opposed to the old system in which all events where always fired to the Extension even if they didn't need them. The destroy() method seems to be missing. Actually it is not, it exists in the parent SFSExtension class and it is not mandatory to override it. If you don't the default behavior is that all RequestHandlers and EventHandlers are released when the destroy() is called.

If you need to customize the destroy() behaviour you can simply override it. By calling super.destroy()you will make sure that events/request handlers are autounregistered. ?

1 2 3 4 5 6 7 8 9

@Override public void destroy() { super.destroy() /* * More code here... */

}

In conclusion it is also worth mentioning some useful methods that are inherited by any command or server handlers: · · · · getParentExtensions(): returns a reference to the main Class of your extension getApi(): return a reference to the main server side API object send(cmdName, params, recipients): sends extension message/response to the client(s) trace(args...): useful logging method with various signatures (see docs for more)

» Advanced Extension features

Now that we have covered the basics of the new Extesion 2.0 architecture we can delve into the more advanced features that allow more sophisticated control over your code. Instantiation annotations: We provide several useful annotations that can be used to specify how your handler classes should be instantiated. By default when you declare a request/event handler class this will be instantiated as new on every call. You can change this behavior using the @Instantiation annotation on your classes: · · @Instantiation(NEW_INSTANCE): creates a new instance on every call @Instantiation(SINGLE_INSTANCE): uses the same instance for all calls

Multi handlers and the request dot-syntax: In order to properly organize request names in complex application we have etablished a convention similar to Java package naming that uses dot syntax. Suppose your Extension can handle a number of games and other operations such as user registration and profile editing. You can organize all these requests like this: register.submitForm register.passwordLost register.changeEmail register. ... register. ... profile.changeAvatarType profile.changeNick profile.getAvatar profile. ... profile. ... checkers.sendMove checkers.getMyScore checkers.leaveGame checkers. ... checkers. ... The Extension API provide a @MultiHandler annotation that can be added to your handler class definition. This will register the class for all requests starting with a certain prefix. Let's implement a multi-handler for the register set of requests: ?

1 2 3 4 5 6 7 8 9 10

@MultiHandler public class RegisterMultiHandler extends BaseClientRequestHandler { @Override public void handleClientRequest(User sender, ISFSObject params) { // Obtain the request id String requestId = params.getUtfString(SFSExtension.MULTIHANDLER_REQUEST_ID); } }

We register the class in the init() method of the Extension: ? 1 @Override 2 public void init() { 3 trace("Hello, this is my first multi handler test!");

4 5 6 7

// Add a new Request Handler addRequestHandler("register", RegisterMultiHandler.class) }

8

( Please note that the request id in a multi-handler is obtained from the parameters object itself. ) The only real difference in this example is that the handler class is marked as @MultiHandler. When this is done the Extension dispatcher will invoke the handler on any request Id starting with the "register" prefix. In other words it will handle register.* NOTE: You can also mix the @Instantiation annotation with @MultiHandler In conclusion it is also worth to note that you are not limited to a single "dot" in the request name. You could have multiple nested levels such as: games.spacewars.fireBullet or user.profile.avatar.getHairColor etc... The only recommendation we have is to keep these request names reasonably short since they will be transmitted with the request/response objects. Extension Filters: The last advanced feature of this tour is Extension Filters. If you are familiar with the Java Servlet API this will probably ring a bell. Extension Filters in SmartFoxServer are inspired to servlet filters and they serve a similar purpose: they are executed in a chain and they can be used to log, filter, or handle specific requests or events before they get to the Extension itself. The advantage of pluggable Filters is that they don't get in the way of your Extension code, their execution order can be altered and they can even stop the execution flow if necessary. An example of this could be a custom ban filter where User credentials are checked against a black list before the request is passed to your Login Handler. This is an example of a simple Extension filter: ?

1 2 3 4 5 6 7 8 9 10 11 12 13

public class CustomFilter extends SFSExtensionFilter { @Override public void init(SFSExtension ext) { super.init(ext); trace("Filter inited!"); } @Override public void destroy() { trace("Filter destroyed!"); }

14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

@Override public FilterAction handleClientRequest(String cmd, User sender, ISFSObject params) { // If something goes wrong you can stop the execution chain here! if (cmd.equals("BadRequest")) return FilterAction.HALT; else return FilterAction.CONTINUE; } @Override public FilterAction handleServerEvent(ISFSEvent event) { return FilterAction.CONTINUE; }

}

Filters can be easily added to any Extension at configuration time or dynamically, at runtime: ?

1 2 3 4 5 6 7 8 9 10 11 12

@Override public void init() { /* * This is your Extension main class init() */ // Add filters addFilter("customLoginFilter", new CustomLoginFilter()); addFilter("pubMessageFilter", new PubMessageFilter()); addFilter("privMessageFilter", new PrivMessageFilter());

}

When a new request or event is sent to your Extension it will first traverse the FilterChain in the order in which Filters were added. In this case it will be: customLoginFilter » pubMessageFilter »privMessageFilter » Extension.

» Advanced Extension Development

In this article we discuss advanced aspects of Extension development: · · · · · The extension thread model Tuning thread pools Maintaining state in the Extension Class loading architecture Delayed and scheduled tasks

Threading model

SmartFoxServer 2X runs all Extensions in a multithreaded environment. There are fundamentally two separate thread pools operating on an Extension: the ExtensionController and the EventManager. The former entity is responsible for processing client requests while the latter dispatches the server events such as USER_LOGIN, USER_DISCONNECT etc... Since these threads operate concurrently on the Extension code we need to make sure that access to shared state is properly synchronized. With the the help of the new Java 6 concurrent collections andlocking features this should be pretty easy to implement in most cases. The SFS2X API already take care of concurrency most of the times: all the calls done on the SFSApi class are thread safe and the same goes for the GameAPI and BuddyListAPI. There are however a few exceptions: SFSObject and SFSArray, for example, are not thread safe. Since these objects are mostly used for data transport, they are unlikely to be contended by multiple threads. In any case the javadoc specifies which object require extra care for thread safety. ^top menu

Tuning the thread pools

There are cases in which we might need to resize on or both thread pools when we have a powerful multicore CPU at our disposal or when the Extension code works with slow running processes such as HTTP calls or heavy Database access. The ExtensionController thread pool can be resized from the AdminTool in the Server Configurator. TheEventManager, at the time of writing this article (ver RC1b) , is not exposed in the AdminTool yet and should be resized from code, typically in the init() method of your extension: ?

1 2 3 4 5 6

@Override public void init() { // Resize to 5 threads SmartFoxServer.getInstance().getEventManager().setThreadPoolSize(5); }

^top menu

Maintaining state in the Extension

When we employ the SFSExtension class as the base class for our Extensions we end up with one main Extension object and a series of request and event handlers. A common question is: where to keep the application shared state? (score, leaderboards, game data etc...) Typically there are two logical answers: · · In the main Extension class exposing the game model via getter/setter(s) Using a Singleton that can be accessed from anywhere in your code

We would highly recommend the first approach over the second for a series of reasons: · · · Singletons are not easily implemented with in an environment with multiple Class loaders. (We explain all the details in the next section of this document) Singletons can play nasty tricks across multiple Extension restarts, because of their static nature. The main Extension already acts as a Singleton and it's very easy to access it from any request or event handler via the getParentExtension() method. In addition you don't get the disadvantages of the Singleton that we just mentioned.

^top menu

Class loading architecture

In the introductory article on Extensions we have mentioned that each Extension is loaded in a different ClassLoader in order to allow hot-redeploy during development or even production. We also illustrated that in order to share dependencies across multiple Extensions these ClassLoaders follow a specific hierarchy:

The diagram shows that each Extension "sees" all its deployed classes in its own Class Loader, it can access the top global Extension classes thanks to its parent ClassLoader and finally it can use any classes from the SFS2X framework thanks to the topmost element in this hierarchy. When an extension is reloaded only the bottom ClassLoader is destroyed and rebuilt. This will create new versions of the classes contained in the deployed jar file(s), while the rest of the classes in the top levels are unaffected. (This means that they cannot be reloaded) Let's examine a practical example to see how this can be useful. Suppose Extension A is our main Zone Extension, while B and C are two Room Extensions, governing two different games. We want Extensions B and C to communicate with A in order to access the game leaderboards. The first problem we encounter is that we need to deploy the same model classes in all three Extensions (A, B and C) in order to properly run the example. This means that each ClassLoader contains a different version of the same Class, which is a well known problem in Java. The infamous ClassCastException is raised by the Java Runtime when you attempt to get an object from another context even if the two classes are the same bytecode. Technically, although they really are the same bytecode, the JVM sees them as different Class definitions that happen to share the same fully qualified name although in three separate contexts (ClassLoaders). NOTE: if we didn't lost you up to this point you probably know the basics of ClassLoading in Java. If this sounds confusing we would highly recommend to check a couple of articles on this subject. · · · A look at the Java ClassLoader Java classes and class loading Java class loading

Fortunately the solution is pretty easy: all you need to do is deploy the model classes in theextensions/__lib__/ folder and you will be able to share these objects across all Extensions. Example:

Every Extension ClassLoader is now able to access the same model classes because they are reachable in their parent Loader. By providing access to the game model from the main Zone Extension we have created a singleton-like solution without worrying about static data. If you still require to create one or more Singleton class(es), that need to be shared across multiple Extensions, this approach will work too. You will need to deploy the Singleton(s) in a jar file inside the__lib__/ folder. ^top menu

Delayed and Scheduled Tasks

Often times the game logic requires to use timers for recurring client updates (e.g. the end of a turn time, the triggering of specific events, NPC actions etc...) A quick solution to this problem is using the ScheduledThreadPoolExecutor class provided in the JDK which offers a convenient task executor backed by a pool of threads. SFS2X already runs its own instance of this Executor (wrapped in a class called TaskScheduler).

The following snippet of Java code shows how to run a looping task using the Server's own TaskScheduler. ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

public class SchedulerTest extends SFSExtension { private class TaskRunner implements Runnable { private int runningCycles = 0; public void run() { runningCycles++; trace("Inside the running task. Cycle: if (runningCycles >= 10) { trace("Time to stop the task!"); taskHandle.cancel(); }

" + runningCycles);

} }

// Keeps a reference to the task execution ScheduledFuture<?> taskHandle; @Override public void init() { SmartFoxServer sfs = SmartFoxServer.getInstance();

// Schedule the task to run every second, with no initial delay taskHandle = sfs.getTaskScheduler().scheduleAtFixedRate(new TaskRunner(), 0, 1, TimeUnit.SECONDS); } }

The scheduleAtFixedRate method takes four arguments:

1. A Runnable object that will execute the Task's code

2. The initial delay before the execution starts 3. The interval at which the task will be executed 4. The time unit used to express the time values The Scheduler also exposes a schedule method that executes a Runnable task once after the specified amount of time. Finally the Scheduler's thread pool can be resized on-the-fly at runtime via theresizeThreadPool() method.

NOTE: The initial size of the system TaskScheduler's thread pool can be adjusted via the Server Configurator panel in the AdminTool.

^top menu

» The Buddy List API

SmartFoxServer 2X provides a new set of client and server API specifically designed for managing Buddies and Buddy Lists including persistence, custom states, ability to go online/offline, runtime and persistent Buddy Variables, server side events and more... The new Buddy API (version 3.0) are loosely based on the previous SFS 1.x Buddy List framework, although we attempted to provide a more simplified approach, better flexibility and more advanced features. If you are familiar with the previous system you will notice the following differences: · File based persistence: we have abandoned the old database persistence model in favor of a simpler and faster file-based system. The persistence model is in general much simpler to manage and developers will be able to provide their own custom Storage classes and use their favorite data sources. Mutual add/remove: this feauture was removed from the system. We noticed that it created confusion for Users and in general it could not satisfy the many different requirements we have encountered. We opted for a more streamlined approach that provides the developer with all the necessary tools to implement their own mutual add/remove features without forcing them into rigid, pre-built solutions. Thanks to a much richer set of server-side Buddy events, developers will be able to extend the Buddy List system in any directions. In particular we highly recommend to check the new Invitation API provided by the game API.

·

» New features

The following is a rapid overview of the new features and improvements added in the framework: · Online status: with SFS 1.x the Buddy would go online every time he logged in the Zone and would go offline any time he would log out. With SFS2X the online state is completely under the control of the developer. Any user can go online and offline in the Buddy system without having to logout etc... Additionally the online state is persistent, so the next time a User will log in again he will be in same online/offline state as when he left in the previous session.

·

Custom states: States are custom Strings that are defined in the Zone configuration and can be used to notify the current Buddy "mode". Examples could be: "Available", "Chat with me", "Play with me", "Occupied", "Be right back" etc... These states should be configured from server side. They will be sent to each client together with the initial BuddyList, after requesting an InitBuddyList from client or server.

·

Nickname: an additional nickname is supported by the Buddy List for every Buddy. This is handled via Buddy Variables and stored persistently.

·

Enhanced Buddy Variables: Buddy variables are now more flexible supporting all common data types (Bool, Integer, Double, String...) as well as complex objects (SFSObject/Array) just like the other User/Room Variable counterparts.

·

Buddy Messages: instead of using private messages (SFS 1.x) Buddies will be able to use a new specific type of message called the BuddyMessage. This is better integrated in the Buddy List system, it embeds the logic for handling blocked Buddies etc... without "polluting" the generic public/private message which are Buddy agnostic. It is strongly recommended to use BuddyMessages for any communication between Buddies.

·

Temp Buddies: Temp Buddies where introduced to handle the following common scenario. o User Gonzo adds User Piggy to his buddy list o Gonzo sends a message to Piggy o Piggy receives the message but she knows nothing about Gonzo. o Gonzo is then added as temp Buddy in Piggy's list. This means that the Buddy will be transient and it will not be saved, unless Piggy goes ahead and calls addBuddy(`Gonzo')

·

Server Side Events: the Buddy API provide many useful new events on the server side that can be used in your Extension to handle specific situations: BUDDY_ADD, BUDDY_REMOVE, BUDDY_BLOCK, BUDDY_VARIABLES_UPDATE, BUDDY_ONLINE_STATE_UPDATE, BUDDY_MESSAGE, BUDDY_LIST_INIT.

» Buddy List Persistence

The Buddy List persistence is delegated to an implementation of the BuddyStorage interface found under the com.smartfoxserver.v2.buddylist.storage package. This is the skeleton of the interface: ? 1 void init(); 2 void destroy();

3 4 5 6 7 8 9

BuddyList loadList(String ownerName) throws SFSBuddyListNotFoundException, IOException; void saveList(BuddyList buddyList) throws IOException; List<BuddyVariable> getOfflineVariables(String buddyName) throws IOException; BuddyListManager getBuddyListManager(); void setBuddyListManager(BuddyListManager buddyListManager);

The init() and destroy() method are called upon creation and destruction of the class, while the BuddyListManager getter/setter is used to maintain a reference to the BuddyListManager object that governs the Buddy Lists in the current Zone. The remaining methods represent the core of the persistence mechanism, which is very simple. · · · loadList(String ownerName): load the Buddy list for the specified Buddy name. saveList(BuddyList buddyList): save the passed Buddy list in the storage system. getOfflineVariables(String buddyName): retrieve offline Buddy Variables for a specific Buddy. This method is needed in the following scenario: o User Fozzie logs in the system and loads his Buddy List o For every Buddy in Fozzie's list that is not online we will need to load their persistent variables directly from the storage system o For every offline Buddy the Buddy List Manager will call this method It's probably reccomendable to use a small cache of offline variables in order to avoid hitting the data source every time. This is what we do with our default file-based implementation. You will be able to provide your custom implementation by simply dropping your .jar file in the SFS2X lib/folder and providing the fully qualified name of the storage class in the Admin Tool.

» The Game API

SmartFoxServer 2X provides a new set of client and server API specifically designed for game creation and management, including public and private games, game invitations, User and Room matching and lots more. The new Game API are based on three fundamental building blocks: · Match Expressions: they allow to create search criteria in a very natural way which can then be passed to the MatchingEngine to perform any type of queries on Rooms and Users Invitations: this is a generic Invitation system that allows you to manage multiple invitations to private games but it can also be employed for many other activities. SFSGame class: the SFSGame is new type of Room specifically designed for managing games and providing unique services for advanced game management

· ·

These three components are highly versatile and can be either used singularly for a specific task or orchestrated together to create advanced server behaviors, as you will learn with the Game API.

» Overview

The easiest way to introduce the new Game API is by briefly presenting each of the components in the framework. Just as a reminder, the SmartFoxServer class exposes a getAPIManager() method which in turn allows you to access all the different API in the SFS2X framework. APIManager provides: · · · getSFSApi(): the basic SFS2X API getBuddyApi(): the Buddy/BuddyList API getGameApi(): the Game API

Match Expressions

Match Expressions are built like "if" conditions in any common programming language: they work like queries in a database. Here's a Java example to get you started: ?

1

MatchExpression exp = new MatchExpression('rank', NumberMatch.GREATER_THAN, 5).and('cou StringMatch.EQUALS, 'Italy')

Expressions are made of three elements: · Variable name

· ·

Match operator Value

Additionally any number of expressions can be linked together with a logical AND / OR operator, just like in regular code. In the above example we have created an expression that will check for a rank value > 5 and a country value == "Italy". But... where is this set of conditions applied to? Normally expressions are used to match Users (via their User Variables) and Rooms (via their Room Variables). The standard SFS2X API provide two useful methods called findUsers() and findRooms() where you can execute any MatchExpression and obtain the filtered set of Users/Rooms. A small example will clarify this better: ?

1

List<User> matchingUsers = sfsApi.findUsers(zone.getUserList(), exp, 50);

The first argument provides a List of Users where to search, in this case all Users inside the current Zone. The second parameter is the expression we just created at the beginning of the article, and the last number (50) is an optional limit for the amount of returned elements. In this case we want the search for no more than 50 matching elements. (If we passed 0 the serach would return all Users that match) The search options are not just limited to User/Room Variables name. In fact the Matching engine provides two extra classes, RoomProperties and UserProperties, where you can access many specific attributes of the Room and User class. This is an example of matching specific Room properties and Variables: ?

1 2 3 4 5 6 7

// Prepare match expression MatchExpression exp = new MatchExpression(RoomProperties.IS_GAME, BoolMatch.EQUALS, true).and (RoomProperties.HAS_FREE_PLAYER_SLOTS, BoolMatch.EQUALS, true).and ("isGameStarted", BoolMatch.EQUALS, false); // Search Rooms List<Rooms> joinableRooms = sfsApi.findRooms(zone.getRoomListFromGroup("chess"), exp, 0);

The above code will match all game Rooms that have at least one free player slot and where the isStartedvariable is set to false. Additionally the search will be performed only in the Room Group called "chess". » Advanced features

The Match expression offer advanced capabilities for searching through nested data structures such asSFSObject and SFSArray. This is done via a very simple dotsyntax expressions. Here's an example of how it works: ?

1

MatchExpression exp = new MatchExpression("europe.italy.capital", StringMatch.EQUALS, "Rome")

The above example goes down deep into an SFSObject called europe, taking the italy object (another SFSObject) and finally reading its String field capital and matching it with another String. Here is one more examples using SFSObject and SFSArray: ?

1

MatchExpression exp = new MatchExpression("europe.italy.majorCities.3.name", StringMatch.EQUALS, "Milan")

From the italy object we obtain a majorCities SFSArray and we grab the third item in it (the .3 expression means 'give me the element at index == 3'). The item is again an SFSObject whose name property we finally compare to a String. The power of Match Expression doesn't end here. You can run multiple passes of matching if you need complex searches to be performed. For example you can run a first match and obtain a list of filtered Rooms and then use it to apply another expression to further refine your search, and so on and so forth. Also you will learn more interesting usages of Match Expressions in conjunction with the SFSGame class later in this very article. For more details on Match Expression we recommend to consult the javadoc, under thecom.smartfoxserver.v2.entities.match package

Invitations

The invitation system included in SFS2X provides a generic framework for sending invitations to one ore more conncted User(s) and easily manage their responses and the expiry of the invitation itself. Invitations can be used to challenge players in a game, invite buddies to the User's place, ask permissions for specific task such as adding the invited User in the Buddy List etc... Creating an invitation is very simple, four basic parameters are needed: · · · · Inviter: the User starting the invitation Invitee: the invited User/Player/Buddy Expiry time: the amount of seconds allowed for the invitee to reply Custom parameters: a generic SFSObject contatining specific invitation parameters (a message, a picture, game details...)

Invited Users can reply to the invitation with

The invited User will receive the invitation and will be able to reply within the specified amount of time. A simple ACCEPT or REFUSE code is all it takes to reply plus an optional SFSObject with response parameters, if needed. In case no response is sent to the server within the expected number of seconds, the invitation will be considered as "refused". For more details about Invitations check the javadoc, specfically the SFSGameAPI class and thecom.smartfoxserver.v2.entities.invitation package.

SFSGame

The SFSGame class extends the normal capabilities of a Room, adding the ability to set the Game as public or private and providing a list of invited people that the system will invite in the game. Additionally the system will be able to invite more people if the number of players is not sufficient to start the game. Each game can be configured to match specific types of Users by providing a Match Expression. The expression contains criteria that are checked against each User that wants to join the game and provides a mean for filtering players. Let's see an example: user Kermit has two variables set. · · Rank: 10 BestScore: 2500

He wants to play, chooses a public SFSGame and attempts to join it. Unfortunately the SFSGame expression is set as follows: (Rank > 10) OR (BestScore > 3000) Any attempt to join the Game will be refused because the player doesn't match the SFSGame criteria. Creating an SFSGame Room is similar to creating a normal Room. On the client side you will find the CreateRoomRequest class and the CreateSFSGameRequest class, both taking a settings object: specifically RoomSettings and SFSGameSettings. The following is a quick overview of the additional parameters that an SFSGame can take, compared to aregular Room: · isGamePublic: a public game can be joined by any Player whose variables match the SFSGame Player Match Expression. If no expression is used the game will be joinable by any User. Private games, instead, are based on invitations provided by the Game creator (see invitedPlayers below) so they usually don't need to specify any match expressions.

·

minPlayersToStartGame: the minimum number of players to start the game. If the game is already running and the number of players goes below this limit the game will be stopped.

·

invitedPlayers: (private games only) a list of players invited in the Game. Each player will receive an invitation event and will be able to reply within the amount of time (see invitationExpiryTime below)

·

searchableRooms: (private games only) a list of Rooms where the Game API can search for more players to invite. The API will look for more players if the number of people invited is smaller than theminPlayersToStartGame. This way you can add your friends to the game and let the system find more players to start it. This mechanism will work only when the game is started, not every time the number of Users goes below the minimum value. If you need to search and invite more players every time the game stops you can easily do it via the SFSApi.findUsers() method.

·

leaveLastJoinedRoom: auto-remove players from their previous Room after successful join

·

playerMatchExpression: an expression to match players willing to play the game, by default no expression is used

·

spectatorMatchExpression: an expression to match spectators willing to play the game, by default no expression is used

·

invitationExpiryTime: the amount of time allowed for invited players to accept / refuse

·

invitationParams: optional custom invitation parameters.These could provide details about the inviter, the game, an invitation message etc...

·

notifyGameStartedViaRoomVariable: automatically update a reserved Room Variable to signal that the game is started/stopped. The Room variable uses the global setting to be broadcast outside of the Room. This can be used on the client side to show the game state in your game list. The reserved Room Variables are found in the com.smartfoxserver.v2.entities.variables.ReservedRoomVariables class.

Finally it is important to note that the SFSGame class extends SFSRoom and can be treated as any other Room in the system. SFSGame objects can be used and passed around to any method or function that works with the base Room interface. (this is valid for both server and client side)

Quick Join Games

Another feature offered by the Game API (both client and server side) is the QuickJoinGame feature. By providing a MatchExpression and a list of Rooms (of type SFSGame) or a Room Group the system can search for matching Rooms and immediately teleport the player in the game action. As usual an example will clarify the concept: ?

1 2 3 4 5 6 7 8

// Prepare a match expresison var expr:MatchExpression = new MatchExpression("rank", NumberMatch.GREATER_THAN, 3).and( NumberMatch.LESS_THAN, 8) // An array of Room Groups where we want the search to take place var whereToSearch:Array = ["poker", "blackJack"] // Fire the request and jump into the game! sfs.send( new QuickJoinGameRequest(expr, whereToSearch) )

The above client Actionscript code attempts to quick join the User in any game from the poker or blackJackRoom Groups where the Game rank variable is > 3 and < 8. Behind the scenes the system will also make sure that the Rooms are of type "game" and that there is at least one player slot available. If no Room matching these criteria is found a join error will be fired back at the User. IMPORTANT: the QuickJoinGame feature works exclusively with Rooms of type SFSGame which support a MatchExpression, all other Rooms types will be ignored.

» User Privilege Manager

One common requirement in online applications is to provide a set of custom permissions for different types of Users. Typically online games can handle guest users, registered users and maybe premium users. It is also quite common to have a profile for moderators and/or site administrators. SmartFoxServer 2X provides a Privilege Manager with each Zone that can be customized to limit the interaction with the Server. Each permission profile can configure a list of denied API calls for each User from a specific category. For instance, we could prohibit the creation of Rooms and Room Variables for guest users and allow them only for registered users. Moderator and Administrator messages could be denied for everyone except those two privileged categories etc... Every profile in the Permission Manager is identified with a unique number. These IDs are freely assignable, however the first 4 are reserved and have a special meaning for the system: · · · · Id Id Id Id == == == == 0: 1: 2: 3: Guest user Registered user Moderator user Administrator user

There are no default settings provided for each of these profiles. The developer is free to customize these permissions to his likings. We should simply keep in mind that those four IDs are always recognized in the system as shown in the above list. For example if you are using this in your code:

? 1 2 3 4 5 6

var kermit:User = sfs.userManager.getUserByName("KermitTheFrog") if (kermit.isModerator()) { // Allow some special action here... }

Internally the API will actually check that profileId == 3 You are also free to add any number of additional profiles and completely ignore the default ones that we have mentioned. Last note: the profile IDs are transmitted as a short integer (16 bit) so this means that there is a theoretical limit of 2^16 permission profiles, for each Zone.

» How to configure permission profiles

Configuring the permission profiles is a very simple operation. You just need to run the AdminTool, start the Configuration module and choose the Zone where you want to edit the permissions. Under the Privilege manager tab you will find the four standard profiles:

You will be able to edit any of these or add new ones:

The dual lists will enable you to add and remove any request from the denied list. In the lower part of the dialogue box you will be able to assign two other special flags: · · ExtensionCalls:: activates the access to any extension in the Zone. When turned off Users in the selected profile won't be able to use Extensions. SuperUser:: when turned on it enables Users in the selected profile to use Moderator/Admin functions such as kicking, banning and sending mod/admin messages.

» How to use permission profiles

Assigning the proper Permission Profile to specific Users requires custom login logic. Usually the developer will manage the User data in a database or similar data source. At login time your extension will be able to check the User credentials and finally set the proper Permission Id once the client is finally logged in the system. The flow that we suggest is the following: · · In your init() extension method register for the USER_LOGIN server event. When USER_LOGIN is fired you can check the credentials against your data source and either allow or deny the access. If you allow the access you will also be able to store the permission profile in theSession properties. By convention there is a reserved property called $permission which is used to specify which permission ID the user will be assigned. Example:

? 1

session.setProperty("$permission", DefaultPermissionProfile.MODERATOR);

Now the User permissions are properly configured. Each time a request will be sent from the client side thePermissionManager will verify it against the User profile and determine if it can should be executed or rejected. In case the request is denied an error will be logged with the details.

» Using the UDP protocol

SmartFoxServer 2X introduces support for the UDP protocol for all those client technologies that support it. This includes Unity3D and AIR 2.0 for Flash developers. The UDP protocol is an unrealiable network protocol that does not guarantee delivery and ordering of the packets. If you want to learn more check this entry from Wikipedia. UDP can be useful when sending fast stream of packets to and from the server in real-time type games, typically for position/transformation updates etc... Under the SFS 2X platform the developer can send any Extension call using UDP, although the default protocol used is TCP.

Even if technically the UDP protocol does not use a persistent connection (like in TCP) we usually refer to a Datagram socket (or channel) as a form of connection between the two endpoints. In order for a client to send and receive UDP data it will need to initialize his Datagram channel before any transmission is possible. This is done by calling the initUDP() method available on the client side.

» Getting started with the UDP protocol

In order to get started with UDP on SmartFoxServer 2X we first need to enable a UDP port on the server that will be used for the communication. By default SmartFoxServer 2X listens exclusively onlocalhost:9933 using the TCP protocol. You can launch the AdminTool, choose the Server Configurationmodule and add a new connector that listens for UDP data. An example would be adding a new socket listener for UDP port 9933. On the client side you will just need to call the initUPD() method once and handle the response event. This can be done at any moment in your code after having logged in a Zone. In general we would highly recommend to do it exactly in your login handler.

var sfs:SmartFox = new SmartFox() sfs.addEventListener(SFSEvent.CONNECTION, onConnection) sfs.addEventListener(SFSEvent.LOGIN, onLogin) sfs.addEventListener(SFSEvent.UDP_INIT, onUdpInit) //... more initialization code here... // Load the external cfg and start the connection sfs.loadConfig() private function onConnection(evt:SFSEvent):void { // Anonymous login sfs.send(new LoginRequest()) } private function onLogin(evt:SFSEvent):void { // Initialize UDP channel sfs.initUDP(new AirUDPManager()) } private function onUdpInit(evt:SFSEvent):void { if (evt.params.success) { trace("UDP ready!")

} }

» Client side configuration

Before running the application make sure to review the settings in your sfsconfig.xml file. In particular you should specify the address and port of the UDP connection so that it matches the server side configuration. Supposing you have set SFS2X to listen on 10.20.30.40:9933 for UDP, you should have the following in your config file: ?

1 2 3 4 5

<ip>10.20.30.40</ip> <port>9933</port> <zone>SimpleChat</zone> <udpIp>10.20.30.40</udpIp> <udpPort>9933</udpPort>

» UDP restrictions for Flash

As you can notice from the above snippet of code the initUDP method takes an argument: ? 1 sfs.initUDP(new AirUDPManager()) This actually applies exclusively to Flex/Air 2.0 while in C# you would simply use this code: ? 1 sfs.InitUDP(); Adobe's support for UDP is in fact only available for standalone applications based on the AIR runtime(version 2.0 and higher) while no support is provided for Flash and Flex applications running in the browser. In order to be able to support all the Adobe technologies with a single API package we introduced the AirUDPManager class which can be instantiated (exclusively when using the AIR SDK) and passed to the initUDP() method.

» SFSObject/SFSArray: Class serialization

In this article we are going to explore an advanced feature provided by the SFSObject and SFSArrayclasses. The feature allows to exchange custom classes (POJOs) between client and server in a completely transparent fashion. As we have seen in the introduction to SFSObject/SFSArray, you might find yourself copying data to and from your model classes into SFSObject(s) quite often in your Extension code. With Class serialization you will be able to directly send and receive your model classes without any manual conversion.

NOTE: This article touches advanced topics and requires a solid knowledge of OOP (in both Java and AS3) and familiarity with the concepts of Type Reflection and Class loading mechanism in the JVM.

The RPG game example

In order to demonstrate the features of Class serialization we will use a real-life use case and provide the source files so you can review the code and experiment with it. For this study we are working on an RPG game. We will create several classes for the game model and we will start off with the characters, using the following properties: · · · · · · name: the character name (String) type:like knight, magician, priest, etc... (String) skin: an object describing the graphics (custom class) properties: a dictionary with properties and relative values such as strength, endurance, agility etc... (Map) inventory: a dictionary with physical objects (Map) spells: a dictionary with spells (Map)

In order to model our RpgCharacter class, in both Java and Actionscript 3, we will have to comply to a series of conventions for the serialization. · · Implement the SerializableSFSType interface. This interface has no methods, and it just works as a "marker" for serializable classes. Use the same exact package and class name on boths sides of the application. In other words the RpgCharacter class must reside in the same package both in Java and Actionscript. Provide an empty constructor (which is used by the system to instantiate the class dynamically) Provide public access to all serializable fields: this simply means that all properties that you want to be transported must be accessible either directly or via a public

· ·

·

getter/setter. Protected, private, package-private fields cannot be serialized. Also static fields are ignored. Mark as transient (Java) any public field that should not be serialized. Since in Actionscript 3 the transient mutator doesn't exist we emulate it by using a naming convention: public fields that should not be serialized should start with a dollar sign ($). Example: $posx, $gameId, $name...

Also we must be aware that not all classes can be transported. For example anything referencing a local resource (file, socket, database connection...) is not serializable. This is a list of the types that can be used with the Class serialization mechanism and how they are translated from one language to the other:

Java null boolean byte short int long float double String Collection<?> (List, Set, Queue...) Map<?, ?> Array[] null

Actionscript 3

Boolean int int int Number Number Number String Array Object Array

The example classes

The following diagram better illustrates our main RpgCharacter class:

There are three different interfaces for each of the above fields with the exclusion of CharacterProperty which is a class itself. Each interface has a number of implementations: · Skin o o Item o o o o o Spell o o o o o

KnightSkin SorcererSkin KnifeItem ShieldItem SledgeHammerItem SwordItem MagicWandItem DeathRaySpell RisingFlamesSpell SolarVortexSpell SpiderSwarmSpell WaterFloodSpell

·

·

Let's see how these interfaces, and the relative implementations, are modeled in Java and Actionscript. This is the Spell.java interface: ?

1 2 3 4 5 6

package sfs2x.extension.test.serialization.model; public interface Spell { String getId(); void setId(String id);

7 8 9 10 11 12 13

int getHitPoints(); void setHitPoints(int hit); int getCount(); void cast(); }

And this is one of the implementations, WaterFloodSpell.java: ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

package sfs2x.extension.test.serialization.model; import com.smartfoxserver.v2.protocol.serialization.SerializableSFSType; public class WaterFloodSpell implements Spell, SerializableSFSType { String id; int hitPoints; int count = 7; public WaterFloodSpell() { // Empty constructor } public WaterFloodSpell(String id, int hitPoints) { this.id = id; this.hitPoints = hitPoints; } public String getId() { return id; } public void setId(String id) { this.id = id; } public int getHitPoints() { return hitPoints; } public void setHitPoints(int hitPoints) { this.hitPoints = hitPoints; } public int getCount() { return count; }

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56

@Override public void cast() { if (count > 0) { System.out.println("CASTING SPELL: " + id); count--; } else { System.out.println("CAN'T CAST SPELL. BUY MORE!"); } } }

As you can notice the class implements the SerializableSFSType interface and provides an empty constructor as required by the conventions previously discussed. Also all class fields that need to be exposed publicly provide access via getters and setters. Let's now take a look at the same interface and implementation created in Actionscript 3: ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14

?

package sfs2x.extension.test.serialization.model { public interface Spell { function get id():String function set id(id:String):void function get hitPoints():int function set hitPoints(hit:int):void function get count():int function cast():void } }

1 2 3

package sfs2x.extension.test.serialization.model { import com.smartfoxserver.v2.protocol.serialization.SerializableSFSType

4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

public class WaterFloodSpell implements Spell, SerializableSFSType { private var _id:String private var _hitPoints:int private var _count:int public function WaterFloodSpell(id:String=null, hitPoints:int=70) { this._id = id this._hitPoints = hitPoints } public function get id():String { return _id } public function set id(id:String):void { this._id = id } public function get hitPoints():int { return _hitPoints } public function set hitPoints(hitPoints:int):void { this._hitPoints = hitPoints } public function get count():int { return _count } public function set count(value:int):void { _count = value } public function cast():void { trace("Casting " + _id) } } }

50 51 52

You can notice that both versions use the exact same package as requested by the Class serializationconventions. Now that we have both classes on the client and server side we are ready to send them over the network in a very convenient way. This is a short example snippet, just to give you an idea. Client Side: ?

1 2 3 4 5 6

var params:ISFSObject = new SFSObject(); params.putBool("IsActive", true); params.putInteger("TheNumber", 42); params.putClass("spell", new WaterFloodSpell()) // This is our custom class instance! sfs.send(new ExtensionRequest("test", params));

Server Side: ?

1 2 3 4 5 6 7 8 9 10

public class TestRequestHandler extends BaseClientRequestHandler { @Override public void handleClientRequest(User sender, ISFSObject params) { boolean isActive = params.getBool("IsActive"); int theNumber = params.getInt("TheNumber"); WaterFloodSpell spell = (WaterFloodSpell) params.getClass("spell"); } }

In the source code provided with this article we will create three different instances of the RpgCharacter and populate them with actual game data, which is then exchanged between client and server in both directions to demonstrate the serialization process. The output of the data structure obtained by the Extension is the following:

+------------------------------------+ RPG Character: Sigfried +------------------------------------+ Type: knight Inventory: longSword -- Price: 500, Active: true

shortSword -- Price: 200, Active: true Spells: swarm -- HitPoints: 3, Qty: 10 Properties: endurance -- value: 70/100 combatSkill -- value: 55/100 strength -- value: 60/100 +------------------------------------+ RPG Character: Tristan +------------------------------------+ Type: knight Inventory: DragonSword -- Price: 1500, Active: true IceKnife -- Price: 300, Active: true IronShield -- Price: 150, Active: true Spells: Properties: endurance -- value: 50/100 combatSkill -- value: 75/100 strength -- value: 80/100 +------------------------------------+ RPG Character: Hayden +------------------------------------+ Type: sorcerer Inventory: DiamondKnife -- Price: 600, Active: true Spells: vortex -- HitPoints: 30, Qty: 10 flames -- HitPoints: 20, Qty: 5 flood -- HitPoints: 3, Qty: 7 swarm -- HitPoints: 15, Qty: 10 Properties: endurance -- value: 50/100 combatSkill -- value: 55/100 strength -- value: 50/100

There is also a particular server warning that is logged on the server side when the data structure is sent back from the client to the server:

No public setter. Serializer skipping private field: count, from class: XYZ...

This is not really a problem as it was done on purpose: we decided to make the count property not writable in the server side version of the classes in order to avoid client values overwriting the default values.

Deploying the Extension

Class serialization requires special attention during the deployment phase because the model classes need to be "seen" by SFS2X at the level of the topmost ClassLoader. As you may recall each Extension runs in a separate ClassLoader which implies that if we deploy the model classes inside the Extension .jar file we will isolate those classes in the specific Extension's ClassLoader. From the main ClassLoader the Server won't be able to know about these classes therefore generating an error when attempting to deserialize the data. ( If you any doubts about how this works in SFS2X we recommend to consult the Extension overview ) In order to avoid this problem we need to make sure that the model classes are deployed in theextensions/__lib__/ folder. So for this example we have prepared two files, RpgModel.jar which goes in extensions/__lib__/ and RpgExtension.jar which goes in extensions/rpg/

Conclusions

One last word goes to the performance of Class serialization. In general the process is fast and efficient thanks to the SFS2X protocol compression. In our Rpg game the resulting packet size is only 860 bytes (vs. 4323 bytes uncompressed). Some of the downsides in using Class serialization is that you don't have direct control in the optimization of numeric types and there is additional overhead due to the runtime type reflection. We think that developers can take the best of both worlds by optimizing via SFSObject the critical messages (those the need maximum efficiency), and use Class serialization where appropriate. In order to complete the tutorial we highly recommend to study the provided source files and see the Class serialization in action.

» ActionScript 3 examples

In this section of the documentation we provide a series of brief tutorials on the ActionScript 3 examples distributed together with SmartFoxServer 2X. Each tutorial analyses a single example, describing its objectives, giving an insight into the SmartFoxServer features it wants to highlight and providing the direct link to download the source code, which includes all the assets required to compile and test it (both client and -- if present -- server side). If necessary, code excerpts are provided in the tutorial itself, in order to better explain the approach that was followed to implement a specific feature. At the bottom of the tutorial, additional resources are linked if available. The tutorials are ordered following the increasing complexity of the examples they refer to: we go from the simplest application we could think of, which just shows how to connect to SmartFoxServer, to a full featured realtime game which uses UDP communication and more. Additional examples can be found in the SmartFoxBits, OpenSpace and RedBox packages. There are no dedicated tutorials describing these examples, but the source code is fully commented to help developers understand what is going on.

» Flash vs Flex

Some of the examples have been developed using Adobe Flash, while others using Adobe Flash Builder(aka Flex). We are not going to provide the same example using both technologies; the rationale behind this decision is very simple: the SmartFoxServer API for both environments is exaclty the same, and the difference between the two is just a matter of User Interface development. As the UI implementation is out of the scope of these tutorials, we strongly encourage you to study the ActionScript code of the examples even if they don't match your platform of choice. You will still be able to understand how to interact with the SmartFoxServer API, and to transfer the knowledge to your environment. All Flash examples require Adobe Flash CS4 or later. All Flex examples require Adobe Flex Builder 3 or later and Flex SDK 3.5 or later. The server-side Extensions of those examples which require one have been developed using Eclipse 3.5, but you should be able to use any Java IDE of your choice, provided you use the JDK 1.6 or later.

NOTE All the examples make use of the latest version of the SmartFoxServer 2X ActionScript 3 APIavailable when they were published. As the API evolve in time (bug fixes, new features), we strongly recommend that, after downloading an

example, you overwrite the included API SWC file (SFS2X_API_AS3.swc) with the one available in the latest SmartFoxServer package or patch available on our website.

» SmartFoxBits

Some of the provided example make use of one or more user interface components belonging to theSmartFoxBits set. This approach has three main advantages: 1. we are not forced to write the same, repetitive code over and over: in fact all multiuser applications/games ­including our examples­ perform the connection to the server and make users enter their credentials to login; also, most of them allow users to chat in a dedicated panel, display a list of available Rooms and show a list of the users in the current Room; 2. the development speed is much improved because it is focused on the objectives of the example; 3. the code is much more lightweight and limited to the the specific logic of the example, makind it easier to walkthrough and understand. In order to learn how to implement the features covered by the SmartFoxBits components, you can refer to initial examples, in particular the Simple Chat and the Advanced Chat. Time to click on the first example in the sidebar... enjoy the reading!

» Avatar Chat

» Overview

The Avatar Chat example is aimed at showing the usage of the SmartFoxServer 2X User Variables. User Variables are one of the three server variable objects available in SmartFoxServer to store data to be shared among the clients of an application or game (the other two are Room Variables and Buddy Variables, which we will deal with in other examples). What's the difference between a server variable and a regular one? The main difference is that the first one (as the word says) is stored on the server and broadcasted to the other clients. In particular, a User Variable is broadcasted to all the other users in the same Room where the owner of that variable is located. This means that a change in a User Variable will be reflected on all other clients within the same Room so that, for example, they can update the scene accordingly. In this example all the users are represented by an avatar (a crash test dummy) that they can move around in a simple environment (which represents the server Room), so we need to get position updates from all the clients to keep the scene in sync. For this reason we use two User Variables, "x" and "y", that represent the position of the corresponding avatar on the screen. When we create or update these variables, all the other clients in the Room are

notified and they can put the avatar of its owner in the right place, or make it walk from one place to another. As this is a simple example, made for educational purposes, our avatars move on a straight line, without anything acting as an obstable on their path. For this reason we can largely optimize the behavior of the client by updating the User Variables just once per movement, setting them at the value of the target coordinates and leaving to each client the task of interpolating the animation from the initial position to the final one (a simple tween is enough). This reduces a lot the bandwith usage . More complex games involving path finding techniques or requiring a perfect synchronization of the clients may require constant position updates and advanced techniques for animation interpolation and position prediction. You can check the more advanced examples provided with SmartFoxServer2X for more informations on this subject. Back to the Avatar Chat example. In addition to the position variables, we use a third User Variable, called "dir", in which we store the direction faced by the avatar, among the eight possible ones. This variable allows us to display the existing avatars in the right direction as soon as the current user enters a Room. Possible improvements of this example, that we will leave up to you, could be creating different backgrounds for different Rooms (hint: use a Room Variable to store the id of the background to be used) and adding "non-walkability" areas to the environments.

>> DOWNLOAD the source files <<

» Code highlights

The timeline of the example's main FLA is divided into sort of "scenes" identified by these labels: load,connect and chat. Both connect and chat frames call a specific initialization method (initConnect andinitChat respectively) on the document class. In the initConnect method we retrieve a reference to the SmartFox client API from the SmartFoxBits Connector available on the stage (see the note below) and add the listeners to SmartFoxServer events we need for our application:

? 1 2 3 4 5 6 7 8 9

public function initConnect():void { // Stop on current frame stop() // Get the instance of the SmartFox class from the SmartFoxBits' Connector sfs = connector.connection // Add SFS2X event listeners sfs.addEventListener(SFSEvent.LOGIN, onLogin)

10 11 12 13 14 15 16 17

}

sfs.addEventListener(SFSEvent.CONNECTION_LOST, onConnectionLost) sfs.addEventListener(SFSEvent.ROOM_JOIN, onRoomJoin) sfs.addEventListener(SFSEvent.USER_ENTER_ROOM, onUserEnterRoom) sfs.addEventListener(SFSEvent.USER_EXIT_ROOM, onUserExitRoom) sfs.addEventListener(SFSEvent.USER_VARIABLES_UPDATE, onUserVarsUpdate) sfs.addEventListener(SFSEvent.PUBLIC_MESSAGE, onPublicMessage)

The onLogin handler simply moves the playhead to the main chat scene. The onConnectionLost instead freezes the interface in case the connection with SmartFoxServer is lost. In a real-world scenario this method should be improved, for example showing a message to the user, or allowing him to try to reconnect. The onRoomJoin event handler is called as soon as the current user enters a Room. Here we can remove all the avatars on the stage in case another Room was joined previously, create the avatars corresponding to users already inside the Room and set the position and direction User Variables for the current user, like this:

? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

private function onRoomJoin(evt:SFSEvent):void { ... // Create current user's avatar by setting its position user variables to a random value // This is required the first time only, because we are not clearing user position // when the room is changed if (!sfs.mySelf.containsVariable(USERVAR_X) && ! sfs.mySelf.containsVariable(USERVAR_Y)) { var px:int = Math.round(Math.random() * panel_chat.avArea.width) var py:int = Math.round(Math.random() * panel_chat.avArea.height) var dir:String = AVATAR_DIRECTIONS[2] setAvatarVariables(px, py, dir)

} }

private function setAvatarVariables(px:int, py:int, dir:String):void { var userVars:Array = []; userVars.push(new SFSUserVariable(USERVAR_X, px)); userVars.push(new SFSUserVariable(USERVAR_Y, py)); userVars.push(new SFSUserVariable(USERVAR_DIR, dir)); sfs.send(new SetUserVariablesRequest(userVars)); }

As the comment in the onRoomJoin method says, in our example we set the current user's position only the first time the user joins a Room after the login. For simplicity, in fact, on subsequent Room changes we decided to preserve the user position in the environment. Similarly, the onUserEnterRoom and onUserExitRoom handlers respectively create and remove theavatars of those users who join or leave the Room in which the current users is. Setting the User Variables makes the avatar appear on all the clients by means of the onUserVarsUpdatehandler. As the variables are set when the user clicks on the environment too (to make his avatar move -- see the onAvAreaClick method), inside the handler we have to differentiate between these two cases. If the avatar already exists, we have to make it move, otherwise we have to create it.

? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

private function onUserVarsUpdate(evt:SFSEvent):void { var changedVars:Array = evt.params.changedVars as Array; var user:User = evt.params.user as User; // Check if the user changed his position if (changedVars.indexOf(USERVAR_X) != -1 || changedVars.indexOf(USERVAR_Y) != -1) { // Check if avatar exists if (getAvatar(user.id) != null) { // Move the user avatar moveAvatar(user) } else { // Create the user avatar createAvatar(user, true) } }

}

Finally, the onPublicMessage handler is responsible of receiving the public chat messages sent using the text input field and send button available in the application, and display them in the chat bubble of the corresponding avatar.

NOTE This example makes use of some of the SmartFoxBits user interface components. Please read the corresponding paragraph in the introduction to the ActionScript 3 examples.

NOTE You should read the comments to methods and properties in the example source code for additional informations and possible code optimizations.

» More resources

You can learn more about the described feature by consulting the following resources: ·

Zones and Rooms architecture: Room Variables and User Variables

Also, as this example shows the basic steps in creating an avatar chat, you may take a look at OpenSpace, a powerful ActionScript 3 isometric engine for rapid development of multi-user virtual worlds and MMO communities.

»Buddy Messenger

» Overview

The Buddy Messenger example gives a demonstration of the client-side capabilities of the SmartFoxServer 2X Buddy List API. Using the Buddy List API, developers can add an instant messenger-like interface to any application, allowing users to see the status of friends (the so-called buddies) in their contact list and communicate with them regardless of the SmartFoxServer Room they are connected to. In fact, in order to optimize the bandwith usage, in SmartFoxServer

messaging and user presence notifications are mostly limited to the users in the same Room. Using the buddy list system, this limit can be overridden in order to offer an improved user experience. In this example users can add new buddies simply by entering their name, remove them, block them (to stop receiving status notifications and instant messages) and send them messages (double-clicking the buddy opens a new tab in the chats panel). Also, each user can change his/her details and state as buddies to other users thanks to the very flexibleBuddy Variable objects featured by SmartFoxServer. Similarly to User Variables and Room Variables, Buddy Variables are stored on the server and broadcasted to the other clients. In particular, when set or updated a Buddy Variable is broadcasted to all the users referenced in the buddy list of its owner. Some Buddy Variables are predefined and reserved to store specific informations: · · the online/offline status of the user (a user can be connected to the server but offline in the buddy list system); the personal state of the user with respect to its presence in the buddy list system (available, busy, etc. -- the list can be customized in the Admin Tool's Zone Configurator); the user nickname, which can differ from the login username.

·

Buddy Variables can be online or offline. Offline variables can be accessed even if the user is not connected to SmartFoxServer (for example to store buddy details which are independent from the current session), while the online variables require the user presence. In our example the user's age is saved as an offlineBuddy Variable, while his/her current mood as an online one.

>> DOWNLOAD the source files <<

» Code highlights

In the init method, called when the creationComplete event of the application is fired, we retrieve a reference to the SmartFox client API from the SmartFoxBits Connector defined in the MXML (see the note below) and add the listeners to SmartFoxServer events we need for our example: ?

1 2 3 4 5 6 7

private function init():void { // Get reference to SmartFoxServer connection sfs = loginPanel.connector.connection; // Add listeners sfs.addEventListener(SFSEvent.LOGIN, onLogin); sfs.addEventListener(SFSEvent.CONNECTION_LOST, onConnectionLost);

8 9 10 11 12 13 14 15 16 17 18 19

sfs.addEventListener(SFSBuddyEvent.BUDDY_LIST_INIT, onBuddyListInit); sfs.addEventListener(SFSBuddyEvent.BUDDY_ERROR, onBuddyError); sfs.addEventListener(SFSBuddyEvent.BUDDY_ONLINE_STATE_UPDATE, onBuddyListUpdate); sfs.addEventListener(SFSBuddyEvent.BUDDY_VARIABLES_UPDATE, onBuddyListUpdate); sfs.addEventListener(SFSBuddyEvent.BUDDY_ADD, onBuddyListUpdate); sfs.addEventListener(SFSBuddyEvent.BUDDY_REMOVE, onBuddyListUpdate); sfs.addEventListener(SFSBuddyEvent.BUDDY_BLOCK, onBuddyListUpdate); sfs.addEventListener(SFSBuddyEvent.BUDDY_MESSAGE, onBuddyMessage); isBuddyListInited = false;

}

Most of the buddy-related events are catched by the same listener, which causes the dataprovider of the buddies list in the application to be recreated from scratch. This has been done on purpose, to simplify the code. A more refined approach would update the specific list item to which each event refers, also discarding those events referred to the current user. In order to make the buddy list system available, the InitBuddyListRequest must be sent after a successfull login is perfomed (see the onLogin handler). This causes the onBuddyListInit event handler to be called, which takes care of populating the dataprovider of the buddies list (see the onBuddyListUpdate handler below) and synchronizing the user interface with respect to the current user details (like nickname, state, age) saved as offline Buddy Variables. The onBuddyListUpdate event handler is responsible of building (or re-building) the list of buddies displayed in the interface. A custom list item renderer takes care of showing the buddy details properly. ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

private function onBuddyListUpdate(evt:SFSBuddyEvent):void { var buddies:ArrayCollection = new ArrayCollection(); for each (var buddy:Buddy in sfs.buddyManager.buddyList) { buddies.addItem(buddy); // Refresh the buddy chat tab (if open) so that it matches the buddy state var tab:ChatTab = stn_chats.getChildByName(buddy.name) as ChatTab; if (tab != null) { tab.buddy = buddy; tab.refresh(); // If a buddy was blocked, close its tab if (buddy.isBlocked) stn_chats.removeChild(tab);

17 18 19 20 21 22 23

}

}

}

ls_buddies.dataProvider = buddies;

The buddy list is recreated each time a buddy is added, removed, blocked, unblocked, its online/offline status changes or one of its Buddy Variables is updated. All these events are triggered by the user interaction, through the respective requests sent to SmartFoxServer like the following examples shows. Add buddy: ?

1

sfs.send(new AddBuddyRequest(ti_buddyName.text));

Block/unblock buddy: ?

1 2

var isBlocked:Boolean = ls_buddies.selectedItem.isBlocked; sfs.send(new BlockBuddyRequest(ls_buddies.selectedItem.name, !isBlocked));

Set user age: ?

1 2

var age:BuddyVariable = new SFSBuddyVariable(BUDDYVAR_AGE, ns_age.value); sfs.send(new SetBuddyVariablesRequest([age]));

Finally, the onBuddyMessage handler is responsible of receiving instant messages sent by the buddies using the text input field and send button available in the respective chat tab. A buddy message is sent through the BuddyMessageRequest. Please notice that we add a custom parameter to the request, containing the name of the recipient. This is useful in the onBuddyMessage handler to determine in which chat tab the message must be displayed when the sender is the current user. ?

1 2 3 4 5 6 7 8

public function sendMessageToBuddy(message:String, buddy:Buddy):void { // Add a custom parameter containing the recipient name var params:ISFSObject = new SFSObject(); params.putUtfString("recipient", buddy.name); sfs.send(new BuddyMessageRequest(message, buddy, params));

}

NOTE 1 This example makes use of some of the SmartFoxBits user interface components. Please read the corresponding paragraph in the introduction to the ActionScript 3 examples. NOTE 2 In order to make this example work, make sure you activate the Buddy List in the SimpleChat Zone (go to the AdminTool's Zone Configurator module).

» BattleFarm

» Overview

BattleFarm is a fast-paced realtime multiplayer action game made with Adobe Flash. The game was created in 2008 as a case study to highlight the productivity and performance features of SmartFoxServer PRO and its accompanying tools: SmartFoxBits and the BlueBox. The ActionScript 2 source code of the game was never released... until today, after we decided to port it to AS3 and of course SmartFoxServer 2X.

Just like in 2008, this complete game example is aimed at showing the capabilities of SmartFoxServer 2Xwhen creating realtime multiplayer games, the stunning performance of its BlueBox 2X module and the flexibility of the SmartFoxBits 2X components in creating user interfaces. In BattleFarm two players compete with each other and against the time to collect the fruits found in the game maze, and using bombs to hit the opponents in order to make them loose what they have collected. When the timer goes to zero, an exit door (in the form of a pit) will randomly appear on the map, and the player with the highest number of collected items will be able to go out and win the round. In this last phase the other player can try to block the opponent or make him loose his fruits so that he can't leave the map.

>> DOWNLOAD the source files <<

IMPORTANT The BattleFarm game and tutorial are distributed for educational purposes only. You must retain all the copyright notices appearing in the user interface, source code and any other accompanying file. By downloading the BattleFarm source code you implicitly accept the following: 1. you are allowed to install and use the BattleFarm client and the BattleFarm Extension for learning purposes only; 2. you are not allowed to rent, lend, lease, license or distribute BattleFarm or a modified version of BattleFarm to any other person in any way; 3. you are not allowed to make BattleFarm available, even for non-commercial purposes, on a public or private website without the written consent of GotoAndPlay SNC. Contact us if you are interested in buying a commercial license which removes the above restrictions.

» Installation

Follow these instructions to setup the development environment and access the game's source code. If instead you want to run the game immediately, check the box below. The client-side assets are contained in the /client folder and they don't require a specific setup: simply open the .fla file in Adobe Flash. All the classes linked by the main document and game assets can be found under the /client/code folder. BattleFarm features a server-side Extension, as described further on. In order to access its code, create and setup a new project in your Java IDE of choice as described in the Writing a basic extension document. Copy the content of the /server/src folder to your project's source folder and link the libraries contained in the /server/lib folder. RUNNING THE GAME In order to run the game follow these steps:

1.

create the battleFarm folder in your

SFS2X installation folder, under /SFS2X/extensions; 2. copy the content of the /server/deploy folder from the game package to the battleFarmfolder you just created;

3.

copy the BattleFarm.zone.xml file

(containing the Zone configuration) from the game package to your SFS2X installation folder, under /SFS2X/zones; 4. start SmartFoxServer 2X; make sure the client will connect to

5.

the right IP address by editing the <ip> entry in the xml files available under the /client/config folder of the game package; 6. open the /client/index.html file in a browser.

» Game architecture

The architecture of the game is pretty simple: there is one main lobby Room defined statically in theBattleFarm Zone configuration. Each users is initially connected to the lobby and he can choose to join a game or create a new one. Each game is a separate Room. A server-side Java Extension is responsible for handling the game logic and coordinating the players' events using the highly efficient binary protocol of SmartFoxServer 2X.

The Lobby system was built in just a few hours with the SmartFoxBits 2X components, which allowed to quickly arrange the typical lobby elements such as the Rooms list, users list, public and private chat boxes, etc. Almost all user interaction in the lobby is handled by the SmartFoxBits, which don't require additional coding except for the more advanced skinning and styling requirements. The SmartFoxBits provide a simple and effective way to skin the visual components so that they perfectly integrate with the rest of the GUI design. In particular, each component is responsible of the following tasks:

·

the Connector performs the connection to the server, showing the connection type (socket or http... more on this later on); · the LoginBox allows the player to login and join the BattleFarm Zone by entering his name (for sake of simplicity no access control system has been added); · the RoomList takes care of joining the player in the initial lobby Room by means of the value set for the "Room to join" property in the Component Parameters panel; also, by setting the "Show chat rooms" property to false in the same panel, it is possible to make the component display only the game Rooms and hide the lobby itself; · the ChatBox allows the players to chat publicly, both in the lobby and later during the game;

·

the UserList shows the users currently in the lobby, allowing private chatting by means of a nested ChatBox component. From the code organization point of view, on the client-side the main document class takes care of instantiating the different "scenes" which compose the application: login, chat (the lobby) and the actual game. On the server-side the standard approach to Extension development is used, together with some good OOP practice.

» Features highlights

» BlueBox SmartFoxServer 2X features the BlueBox 2X, a module specifically designed to allow connections behind firewall and proxies through and advanced HTTP tunnelling technique. If a direct socket connection is not available, the client is transparently redirected to the BlueBox. The BlueBox enables players under restricted network configurations to play and enjoy fast multiplayer applications and games with little to no noticeable performance loss. The great news is that it is not necessary to write different code depending on the type of connection: SmartFoxServer and its client API handle the "tunnelled" connections behind the scenes, making them appear, from outside, just like standard socket connections. The main advantage of the BlueBox compared to other tunnelling solutions is that you can fine tune it to the requirements of your application enabling very good performance even with real time games, likeBattleFarm itself proves. In BattleFarm, in order to be able to force the BlueBox connection for demonstration purposes, two separate client configuration files have been created (see the /client/config folder in the game package): in the sfs-config_http.xml file the <port> parameter has been set to 0000, which causes the switch to the tunnelling solution. The file to be loaded is determined by an external variable passed to the game's SWF file by the container html page (see the client code snippets below). After a connection to the server is established, the Connector's led in the bottom right corner of the game shows the connection type:

·

established

> socket connection

·

established » Server-side Extension

> http connection

While most of the lobby features are automatically managed by SmartFoxServer and SmartFoxBits, the game logic must be custom developed using a server-side Extension plugged at Zone level, which allows to control the status of all the game Rooms running in the application. During a match, each client sends the following requests to the Extension:

·

ready: the player is ready to start the match. This request is sent right after the game Room is joined: as soon as the Extension receives two "ready" commands from two clients in the same Room, the match can start. · mv (move): the player's character has changed it position inside the game maze, and its coordinates are sent to the server so that the opponent's client can be updated accordingly. Additionally, the server checks if the player stumbled upon a fruit, making him collect it. · bb (bomb): the player set off a bomb; its position is sent to the server so that it will make it appear in the opponent's client too. Also, the Extension starts a timer which will cause the bomb to explode. · restart: as soon as the match finishes (when one of the players enters the exit pit), a popup panel shows the game result; if one of the players click it, this request is sent to the Extension which restarts the game. The followings are the commands or responses sent by the Extension to the clients:

·

map: after the client declares that it is ready to play (using the ready command above), the server sends the data which define the maze layout and the graphics to be used to render it, the fruits positions, the player starting positions, etc. · go: after both players sent the ready command and received the map data, the Extension sends this command which states that the match can start. When this command is received, each client creates a main thread (based on the ENTER_FRAME event) which controls the user keyboard interaction, the characters animation, etc. · mv (move): when the server receives the specular mv request sent by one of the players

(see above), the Extension validates it (checking if the player stumpled upon a collectible item or the exit pit) and sends the new position to the opponent through this command. · bb (bomb): when the server receives the specular bb request sent by one of the players (see above), the Extension sends its position to the opponent through this command. The Extension also keeps a reference to this bomb and will make it explode later. · gi (get item): when, during the movement of one of the characters, the Extension detects that a collectible fruit has been reached, this command is sent to the clients so that they can make the item disappear from the map and update the collected items counters accordingly. · xp (explode): a thread controls if a specific amount of time has passed since a bomb was placed on the map. When this occurs, this command is sent to both players so their clients can render the explosion. The Extension is also in charge of checking if the bomb explosion hits the players, making them lose the collected items, which are redistributed randomly on the map. · od (open door): a timed event in the Extension is responsible of sending this command to the players. It makes the exit pit appear on the map. · win: when one of the players steps on the exit pit, the Extension checks if the game is over (the player must have collected more items than his opponent) and, in case, sends this command which terminates the round making the client display the current score (the number of rounds won since the game was joined). · stop: if the server detects that one of the players left the game Room (voluntarily or due to a sudden disconnection), this command informs the opponent and put it in standby for another player to join the Room. In BattleFarm all the communication during the game action is performed using the efficient SFS2X binary protocol to pack the messages, which allows sending small messages very fast and with optimal bandwidth usage. The default TCP network protocol is used to transfer the messages from the client to the server and viceversa.

» Player synchronization In order to keep a perfect synchronization between players we only need to send a minimal amount of updates and use a mix of client-side and server-side validation to achieve the best user experience. Instead of validating each player movement on the server-side before actually animating the characters, we perfrom this check on the client side and notify the server about it. This way the game always feels smooth and highly responsive, regardless of the network lag. Collision checking with the collectible items is done on the server-side instead, because we need to make sure that only one player grabs an item from the map in case both of them step on the same item at the same time. The bomb explosion is controlled on the server-side: when a user drops a bomb the client notifies the server which in turn starts a countdown to the explosion. When the timer expires all clients are informed that the explosion should take place and the animation is performed on the client.

One of the most important aspects of the client-side of the game is using time-based animations, which ensure consistent execution of all game animations regardless of the computer CPU speed, memory, video card, etc. In fact one of the difficulties of client synchronization is represented by the potential differences in rendering performance of the Flash Player. In other words a player running an old computer with a few hundreds of megabytes of RAM would experience slower animation rendering than another player running a shiny new multi-core machine with several gigabytes of RAM. By ensuring that each animation takes the exact same time on any machine we eliminate the risk of additional client lag and we just need to deal with the possible delays caused by the network lag. » Handling slower connections In order to keep up with possible connection slow downs the client maintains a queue of moves sent by the server and executes them serially by grabbing the first item from the queue and processing it before moving on to next one. If the client connection becomes congested for a while, many of the incoming messages aren't received for some time until the network becomes available again: at that point all the messages suddenly arrive and populate the client queue. If those messages are too many, the client automatically skips them in order to keep up with the

latest character positions, and the player will probably notice a certain amount of frame-skip in the opponent animations. The following diagram shows a "regular" client queue state. As new messages are enqueud in the client they are processed and rendered:

In case the client can't keep up with the server message speed rate (e.g. after a network congestion) older messages are discarded to keep up with the latest game state:

This technique is very effective to avoid breaking the game synchronization even in case of clients with problematic connections.

» Client code highlights

As soon as the LoginScene is initialized, the loadConfig method is called and all the required listeners are added to the SmartFox client API, whose reference is stored in the main document class (which in turn got it from the Connector's intance). The method also takes care of loading the proper client configuration file depending on an external variable passed to the SWF by the html page. ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

private function loadConfig():void { // activate smartfox listeners refDocument.smartFox.addEventListener(SFSEvent.CONFIG_LOAD_FAILURE, onConfigLoadFailure); refDocument.smartFox.addEventListener(SFSEvent.CONFIG_LOAD_SUCCESS, onConfigLoadSuccess); refDocument.smartFox.addEventListener(SFSEvent.CONNECTION, onConnection); refDocument.smartFox.addEventListener(SFSEvent.CONNECTION_LOST, onConnectionLost); refDocument.smartFox.addEventListener(SFSEvent.LOGIN, onLogin); // // // if load smartfox XML config file a separate config file is loaded in case we need to force the BlueBox usage (a parameter passed by the html page to the swf file url forces this) (root.loaderInfo.parameters.conn == "http") refDocument.smartFox.loadConfig("config/sfs-config_http.xml", true); else refDocument.smartFox.loadConfig("config/sfs-config_socket.xml", true);

}

The onLogin handler moves the game to the ChatScene, where the player joins the initial lobby Room. In this scene the Create button allows users to create new matches based on the selected map and title. During the Room creation process, a Room Variable is also set to save the id of the selected map. ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

private function createNewGame(evt:MouseEvent):void { if (name_ti.text != "") { var roomFound:Boolean = false; for (var i:int = 0; i < refDocument.smartFox.roomList.length; i++) { if ((refDocument.smartFox.roomList[i] as Room).name == name_ti.text) { roomFound = true; break; } } if (!roomFound) // prevent create room errors { // create room variable var roomVar:SFSRoomVariable = new SFSRoomVariable("map",

17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

refDocument.availableMaps[refDocument.selectedMapIndex].id); roomVar.isPrivate = false; roomVar.isPersistent = true; // add room variable to an array var roomVars:Array = new Array(); roomVars.push(roomVar); // create room settings var roomSettings:RoomSettings = new RoomSettings(name_ti.text); roomSettings.password = password_ti.text; roomSettings.maxUsers = 2; roomSettings.maxSpectators = 0; roomSettings.isGame = true; roomSettings.variables = roomVars; // create new game room with above parameters and join it refDocument.smartFox.send(new CreateRoomRequest(roomSettings, true, refDocument.smartFox.lastJoinedRoom)); } } }

In the GameScene the onGameExtensionResponse listener takes care of handling all commands and responses sent by the server-side Extension, dispatching them to specific subhandler methods. ?

1 private function onGameExtensionResponse(evt:SFSEvent):void 2 { var extParams:SFSObject = evt.params.params; 3 4 switch (evt.params.cmd) 5 { case "map": 6 initGameMap(extParams); 7 break; 8 9 case "go": 1 startGame(); 0 break; 1 case "mv": 1 handleOpponentMove(extParams.getInt("px"), extParams.getInt("py")); 1 break; 2 1 case "bb": 3 handleOpponentBomb(extParams.getInt("bId"), extParams.getInt("bx"), extParam 1

break; 4 1 case "gi": 5 handlePickItem(extParams.getInt("px"), extParams.getInt("py"), extParams.get 1 break; 6 1 case "xp": handleExplosion(extParams.getInt("bb"), extParams.getInt("uid"), extParams.g 7 1 extParams.getUtfString("it1"), extParams.getInt("kp2"), extParams.getUtfString("it2"), e break; 8 1 case "od": 9 handleOpenDoor(extParams.getInt("px"), extParams.getInt("py")); 2 break; 0 case "win": 2 handleWinner(extParams.getInt("id")); 1 break; 2 2 case "stop": 2 handleStopGame(); 3 break; 2 default: 4 break; 2 } 5 } 2 6 2 7 2 8 2 9 3 0 3 1 3 2 3 3 3 4 3 5 3 6 3

7 3 8 3 9 4 0 4 1 4 2 4 3 4 4 4 5 4 6

» Server code highlights

Following the recommended approach in Extensions development, on the server-side each request sent by the clients is handled by a dedicated handler. Also, a list of games in progress is mantained (to save the status of each game) and a global game controller takes care of all the time-based events in all games (bombs explosions, exit pit appearance). ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

@Override public void init() { // load maps information gameMapsInfoBean = GameMapBsn.loadMaps(this); // initialize games list games = new ConcurrentHashMap<Integer,GameBean>(); // initialize game controller gameController = new GameController(this); gameController.start(); // register request handlers // get map list addRequestHandler(Commands.CMD_MAP_LIST, MapListHandler.class); // ready to start a game addRequestHandler(Commands.CMD_READY, ReadyHandler.class);

19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

// restart game addRequestHandler(Commands.CMD_RESTART, RestartHandler.class); // player movements addRequestHandler(Commands.MV, MovementHandler.class); // handle bombs requests addRequestHandler(Commands.BOMB, BombHandler.class); // register event handlers ... }

» More resources

You can learn more about the mentioned features by consulting the following resources:

· · ·

SmartFoxBits 2X The BlueBox 2X Extension API tutorial

» The Buddy List API

SmartFoxServer 2X provides a new set of client and server API specifically designed for managing Buddies and Buddy Lists including persistence, custom states, ability to go online/offline, runtime and persistent Buddy Variables, server side events and more... The new Buddy API (version 3.0) are loosely based on the previous SFS 1.x Buddy List framework, although we attempted to provide a more simplified approach, better flexibility and more advanced features. If you are familiar with the previous system you will notice the following differences: · File based persistence: we have abandoned the old database persistence model in favor of a simpler and faster file-based system. The persistence model is in general much simpler to manage and developers will be able to provide their own custom Storage classes and use their favorite data sources. Mutual add/remove: this feauture was removed from the system. We noticed that it created confusion for Users and in general it could not satisfy the many different requirements we have encountered. We opted for a more streamlined approach that provides the developer with all the necessary tools to implement their own mutual add/remove features without forcing them into rigid, pre-built solutions. Thanks to a much richer set of server-side Buddy events, developers will be able to extend the Buddy List system in any directions. In particular we highly recommend to check the new Invitation API provided by the game API.

·

» New features

The following is a rapid overview of the new features and improvements added in the framework: · Online status: with SFS 1.x the Buddy would go online every time he logged in the Zone and would go offline any time he would log out. With SFS2X the online state is completely under the control of the developer. Any user can go online and offline in the Buddy system without having to logout etc... Additionally the online state is persistent, so the next time a User will log in again he will be in same online/offline state as when he left in the previous session.

·

Custom states: States are custom Strings that are defined in the Zone configuration and can be used to notify the current Buddy "mode". Examples could be: "Available", "Chat with me", "Play with me", "Occupied", "Be right back" etc... These states should be configured from server side. They will be sent to each client

together with the initial BuddyList, after requesting an InitBuddyList from client or server.

·

Nickname: an additional nickname is supported by the Buddy List for every Buddy. This is handled via Buddy Variables and stored persistently.

·

Enhanced Buddy Variables: Buddy variables are now more flexible supporting all common data types (Bool, Integer, Double, String...) as well as complex objects (SFSObject/Array) just like the other User/Room Variable counterparts.

·

Buddy Messages: instead of using private messages (SFS 1.x) Buddies will be able to use a new specific type of message called the BuddyMessage. This is better integrated in the Buddy List system, it embeds the logic for handling blocked Buddies etc... without "polluting" the generic public/private message which are Buddy agnostic. It is strongly recommended to use BuddyMessages for any communication between Buddies.

·

Temp Buddies: Temp Buddies where introduced to handle the following common scenario. o User Gonzo adds User Piggy to his buddy list o Gonzo sends a message to Piggy o Piggy receives the message but she knows nothing about Gonzo. o Gonzo is then added as temp Buddy in Piggy's list. This means that the Buddy will be transient and it will not be saved, unless Piggy goes ahead and calls addBuddy(`Gonzo')

·

Server Side Events: the Buddy API provide many useful new events on the server side that can be used in your Extension to handle specific situations: BUDDY_ADD, BUDDY_REMOVE, BUDDY_BLOCK, BUDDY_VARIABLES_UPDATE, BUDDY_ONLINE_STATE_UPDATE, BUDDY_MESSAGE, BUDDY_LIST_INIT.

» Buddy List Persistence

The Buddy List persistence is delegated to an implementation of the BuddyStorage interface found under

the com.smartfoxserver.v2.buddylist.storage package. This is the skeleton of the interface: ? 1 void init(); 2 void destroy();

3 4 5 6 7 8 9

BuddyList loadList(String ownerName) throws SFSBuddyListNotFoundException, IOException; void saveList(BuddyList buddyList) throws IOException; List<BuddyVariable> getOfflineVariables(String buddyName) throws IOException; BuddyListManager getBuddyListManager(); void setBuddyListManager(BuddyListManager buddyListManager);

The init() and destroy() method are called upon creation and destruction of the class, while the BuddyListManager getter/setter is used to maintain a reference to the BuddyListManager object that governs the Buddy Lists in the current Zone. The remaining methods represent the core of the persistence mechanism, which is very simple. · · · loadList(String ownerName): load the Buddy list for the specified Buddy name. saveList(BuddyList buddyList): save the passed Buddy list in the storage system. getOfflineVariables(String buddyName): retrieve offline Buddy Variables for a specific Buddy. This method is needed in the following scenario: o User Fozzie logs in the system and loads his Buddy List o For every Buddy in Fozzie's list that is not online we will need to load their persistent variables directly from the storage system o For every offline Buddy the Buddy List Manager will call this method It's probably reccomendable to use a small cache of offline variables in order to avoid hitting the data source every time. This is what we do with our default file-based implementation. You will be able to provide your custom implementation by simply dropping your .jar file in the SFS2X lib/folder and providing the fully qualified name of the storage class in the Admin Tool.

免责声明:非本网注明原创的信息,皆为程序自动获取自互联网,目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责;如此页面有侵犯到您的权益,请给站长发送邮件,并提供相关证明(版权证明、身份证正反面、侵权链接),站长将在收到邮件24小时内删除。

相关阅读

最新推荐

more