SGS新社区

 : : : :甲骨文把SUN收购后,咔嚓了不少项目,其中就包括Project DarkStar。笔者研究过这个项目一段时间,后来由于一些原因停滞了,准备有时间再拾起来的,就这么没了实在可惜。
 : : : :今天据郝童鞋透露,这个项目没挂,换了新社区,项目名称也改了–RedDwarf(红色矮人?),实为Project Darkstar的分支。项目地址为:http://www.reddwarfserver.org
有兴趣的童鞋可以去那里看看。这里摘录一段简介:
Welcome to the new community portal for RedDwarf Server. RedDwarf is the official community fork of Project Darkstar, established because investment in that project has been discontinued by Oracle. The first official release of RedDwarf, version 0.10.0, is available and consists of essentially a rebranded version of the most recent codebase from the trunk of the Project Darkstar repository on java.net. Please feel free to look around, and join the community in continuing development of this great project.

实现FTP文件上传

应用中需要定时将本地文件上传到远程服务器,定时这块先不说了,说说文件上传的方法。这里用的是apache的FTP工具包

单独写一个FTP管理类,向外提供公共方法调用。

建立连接connect方法:

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
/**
 * 连接FTP服务器
 * @param host
 * @param port
 * @param userName
 * @param password
 * @return 连接成功返回TRUE,失败返回FALSE
 */
public boolean connect(String host, int port, String userName, String password) {
	boolean result = true;
	try {
		client.connect(host, port);
		int code = client.getReplyCode();
		if (FTPReply.isPositiveCompletion(code)) {
			if (client.login(userName, password)) {
				log.info("连接并登陆上FTP服务器" + host + ":" + port);
			} else {
				log.info("登录FTP服务器失败" + host + ":" + port);
				result = false;
			}
		} else {
			log.info("连接FTP服务器失败,关闭连接" + host + ":" + port);
			result = false;
			disconnect();
		}
	} catch (Exception e) {
		// TODO: handle exception
		disconnect();
		log.error("Exception:", e);
		return false;
	}
 
	return result;
}

断开连接方法disconnect:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 与FTP服务器断开连接
*
*/
public void disconnect() {
	// TODO Auto-generated method stub
	try {
		if (client.isConnected()) {
			client.logout();
			client.disconnect();
		}
		log.info("与FTP服务器连接已关闭!");
	} catch (Exception e) {
		// TODO: handle exception
		log.error("Exception:", e);
	}
}

这里的实现很简单,没有什么好说的

上传文件upload方法:

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
/**
 * 上传指定文件到服务器,服务器目录为根目录,并且本地文件与上传到服务器的文件名称相同
* @param local	本地文件相对路径,包含文件名
* @return
*/
public boolean upload(String local) {
	try {
		// 设置成服务器被动相应模式
		client.enterLocalPassiveMode();
 
		// 文件类型设为二进制类型
		client.setFileType(FTP.BINARY_FILE_TYPE);
 
		String fileName = local.substring(local.lastIndexOf("/") + 1);
 
		FTPFile[] files = client.listFiles(fileName);
 
		if (files.length > 0) {
			log.info(fileName + "文件已存在,不进行上传!");
			return false;
		}
 
		File file = new File(local);
		InputStream is = new FileInputStream(file);
 
		if (client.storeFile(fileName, is)) {
			log.info(fileName + "文件上传成功!");
			is.close();
			return true;
		} else {
			log.info(fileName + "文件上传失败!");
			is.close();
			return false;
		}
 
	} catch (Exception e) {
		// TODO: handle exception
		log.error("Exception:", e);
		return false;
	}
}

这里传进本地文件的相对路径作为参数,上传到服务器上指定用户下的根目录。有人问了,我上传到服务器后要将文件名改成指定格式的怎么办呢?很简单,“storeFile(String name, InputStream is)”方法中,第一个“name”参数就是上传到服务器后文件所用的名称。

又有人问了,那我不想上传到根目录下,我要将不同文件上传到指定目录以便管理,这要怎么办呢?工具包中并没有提供自动生成目录结构的方法,但是提供了创建目录和跳转目录的方法:makeDirectory(String arg0)、changeWorkingDirectory(String arg0)。我们可以使用这两个方法,利用递归,自己写一个生成目录的方法,以下:

重写upload方法:

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
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/**
 * 上传本地文件到服务器上的指定目录,可以修改文件名称,若服务器上目录不存在,则自动创建指定的目录结构
 * @param local 本地文件的相对路径,包含文件名
* @param remote 服务器上的文件路径,包含文件名
 * @return 上传成功返回TRUE,失败返回FALSE
 */
public boolean upload(String local, String remote) {
	try {
		client.enterLocalPassiveMode();
		client.setFileType(FTP.BINARY_FILE_TYPE);
 
		String remoteFileName = remote;
 
		if (remote.contains("/")) {
			remoteFileName = remote.substring(remote.lastIndexOf("/") + 1);
			String remoteDirectory = remote.substring(0, remote.lastIndexOf("/") + 1);
 
			// 判断远程目录是否存在,若不存在则循环创建
			if (!remoteDirectory.equalsIgnoreCase("/")
					&& !client.changeWorkingDirectory(remoteDirectory)) {
                                                   // 目录结构索引
				int start = 0;
				int end  = 0;
				String subDir = "";
 
				if (remoteDirectory.startsWith("/")) {
					start = 1;
				} else {
					start = 0;
				}
				end = remoteDirectory.indexOf("/", start);
 
				while (true) {
					subDir = remoteDirectory.substring(start, end);
 
					if (!client.changeWorkingDirectory(subDir)) {
						if (client.makeDirectory(subDir)) {
							client.changeWorkingDirectory(subDir);
						} else {
							log.info(subDir + "目录创建失败!");
							return false;
						}
					}
 
					start = end + 1;
					end = remoteDirectory.indexOf("/", start);
 
					if (end < start) {            // 到目录结尾
						break;
					}
				}
			}
		}
 
		FTPFile[] files = client.listFiles(remoteFileName);
 
		if (files.length > 0) {
			log.info(remoteFileName + "文件已存在,不进行上传!");
			return false;
		}
 
		File file = new File(local);
		InputStream is = new FileInputStream(file);
 
		if (client.storeFile(remoteFileName, is)) {
			log.info(remoteFileName + "文件上传成功!");
			is.close();
			return true;
		} else {
			log.info(remoteFileName + "文件上传失败!");
			is.close();
			return false;
		}
 
	} catch (Exception e) {
		// TODO: handle exception
		log.error("Exception", e);
		return false;
	}
 
}

测试方法:

1
2
3
4
5
6
public static void main(String[] a) {
	FtpConnection ftp = new FtpConnection();
	ftp.connect("57.27.5.44", 21, "123", "456");
	ftp.upload("upload/mysql-5.0.86-win32.zip", "/dir/text/mysql-5.0.86-win32.zip");
	ftp.disconnect();
}

UTF-8转码问题

        在与合作商进行基于http协议的消息传输时,遇到一个转码的问题。

        程序主要完成这样的工作:向对方发出一个http请求,对方在response里返回一串包含中文字符的字符串,程序将其解析,把中文字符传入下一个处理模块中。就这么简单!在编码方面,对方说并没有进行URLEncode编码,而是直接用getBytes(“UTF-8”)进行转码。

        通信自然是没有问题。问题是从response中取出的中文字符串进行转码后,得到的中文字符串包含乱码,即部分字符正常显示,部分是乱码。这种简单的http传输自然不会出现丢包的问题,于是便将问题锁定在转码上。先将对方的转码方法和我一开始使用转码方法贴出来。

对方转码方法:

1
2
3
4
5
6
7
8
9
String res = "0|";
 
if(responseMessage != null &amp;&amp; responseMessage.getStatus() == SmsMessage.STATUS_NORMAL){
    res = res + responseMessage.getContentString();
}
 
res = new String(res.getBytes("UTF-8"), "UTF-8");
response.setContentType("text/plain; charset=utf-8");
response.getWriter().write(res);

 

我的转码方法:

1
2
byte[] bs = res.get().getStringContent().getBytes();
String result = new String(bs, "UTF-8");

(其中res.get()为mina框架中的webclient提供的方法,返回的是HttpResponseMessage)

 

        看了对方的代码后,感觉很奇怪。getBytes(“UTF-8”)是将字符串转为编码为”UTF-8”的字节码,new String(new byte[], “UTF-8”)是将字节码转为编码格式是”UTF-8”的字符串。这一来一回不就等于什么都没做吗?为了一探究竟,自己写了一个servlet,用他的方法对一个中文字符“感”进行处理。单步中发现,处理前后的字节码果然没有改变。当第一次使用getBytes(“UTF-8”)进行转码时,原先的两位字节码变成了三位;第二次转码又变成了两位,和处理前一摸一样。对方这样做,想是因为他们在构造出res后,并不能确定它的编码格式,所以来个统一编码。再来看我的程序。天!得到的字符串已经面目全非,其字节码数组变成了六位!一个“感”字变成了两个乱码字加两个单字节符号“愄1�7”。后面的时间里,对这个字符做了多次转码尝试,都无济于事。

        难道程序获取的字符本身就有问题?直接获取String会不会把response中的其他隐性信息也一并获取了?先取得字节码会如何呢?试一下!在HttpResponseMessage里有getContent()方法,返回就是一个字节数组,于是调用该方法。单步查看时发现,得到的字节码与getBytes(“UTF-8”)生成的字节码一摸一样。哈,有门儿!继续调用new String(new byte[], “UTF-8”)将其转换成字符。熟悉的“感”字跃然于屏幕之上,好亲切呀,把我感动的快哭了。一个简单的http请求响应操作,竟然被编码问题折腾了大半天!

        继续做了一些测试工作,使用最基本的URLConnection,发现若是直接从response中读取String,后面的转码操作都会有异常,而使用byte来读取就能避免这种异常。看来String中确实包含了一些字符内容之外的东西。是什么呢?

Log4j每日生成一个日志文件的问题

        有个应用中需要每天生成一个记录文件sms.txt.yyyy-mm-dd.txt,并向合作方定时发送。编写程序来写文件很简单,每天生成一个文件也不难,但还是要写代码。想到log4j本身可以就有这些功能,有什么理由舍近求远,舍易求难呢?于是乎,对log4j进行配置,将需要记录并发送的文字信息直接写入单独的日志文件sms.txt当中。运行程序,发送一条请求,打印信息,写入文件。打开sms.txt文件,确认信息已经成功写入。然后,修改系统时间,将日期调为下一天。再发送一条请求,顺利生成sms.txt.yyyy-mm-dd.txt文件,并在新的sms.txt文件中写入记录。看似很顺利。接着做压力测试。重新启动程序,模拟10w条数据请求。因为快要下班,就让程序一直在服务器上运行,如果第二天生成了sms.txt.yyyy-mm-dd.txt文件,并且上传至指定FTP服务器,即算大功告成。

        第二天过来,问题就来了。服务器上只有一个sms.txt文件,打开sms.txt文件,里面的10w条记录已经正常写入,但是并没有生成sms.txt.yyyy-mm-dd.txt文件,自然也没有上传成功。然而,进行相同配置的普通日志文件log.txt却是正常生成了log.txt.yyyy-mm-dd.txt文件。这是什么原因呢?两者之间有什么区别呢?我向正在运行的程序又发了一条请求,看看是什么情况。神奇的事情出现了:服务器自动生成了sms.txt.yyyy-mm-dd.txt文件,和昨天的测试结果一摸一样!看到这,两者的区别找到了!程序从昨天运行到今天,log日志文件会不停的写入信息,而sms文件在昨天10w条数据写入完成后,就停止写入了,直到今天刚刚测了一条。

        这时,脑中出现了一个想法:log4j的日志文件生成会不会是事件驱动的?写入事件的发生,导致程序根据配置文件的内容进行相应文件的生成(或不生成)。所以,sms.txt文件在昨天停止写入后,到第二天由于没有写入事件,并不自动生成上一天的日志文件,而是等到我又测试了一条后,有了写入操作,才生成新文件。随后,我又进行几次测试,证实了这个想法。但如果真是这样,这个程序就有问题了。若第二天没有数据请求,就生成不了需要上传的sms.txt.yyyy-mm-dd.txt文件,上传动作便会失败。Log4j可不可以不用驱动,直接定时生成日志文件呢?待续。。。

ProjectDarkStar服务器端开发文档(九)

附录A:SwordWorld例子代码

Sword World

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
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/*
* Copyright 2007-2009 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package com.sun.sgs.tutorial.server.swordworld;
import java.io.Serializable;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
import com.sun.sgs.app.DataManager;
import com.sun.sgs.app.ManagedReference;
/**
* A tiny sample MUD application for the Project Darkstar Server.
*
 
* There is a Room. In the Room there is a Sword...
*/
public class SwordWorld
implements Serializable, AppListener
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
 
/** The {@link Logger} for this class. */
private static final Logger logger =
Logger.getLogger(SwordWorld.class.getName());
 
/** A reference to the one-and-only {@linkplain SwordWorldRoom room}. */
private ManagedReference roomRef = null;
 
/**
* {@inheritDoc}
*
 
* Creates the world within the MUD.
*/
public void initialize(Properties props) {
logger.info("Initializing SwordWorld");
// Create the Room
SwordWorldRoom room =
new SwordWorldRoom("Plain Room", "a nondescript room");
 
// Create the Sword
SwordWorldObject sword =
new SwordWorldObject("Shiny Sword", "a shiny sword.");
 
// Put the Sword to the Room
room.addItem(sword);
 
// Keep a reference to the Room
setRoom(room);
logger.info("SwordWorld Initialized");
}
/**
* Gets the SwordWorld's One True Room.
*
 
* @return the room for this {@code SwordWorld}
*/
public SwordWorldRoom getRoom() {
if (roomRef == null)
return null;
return roomRef.get();
}
/**
* Sets the SwordWorld's One True Room to the given room.
*
 
* @param room the room to set
*/
public void setRoom(SwordWorldRoom room) {
DataManager dataManager = AppContext.getDataManager();
if (room == null) {
roomRef = null;
return;
}
roomRef = dataManager.createReference(room);
}
/**
* {@inheritDoc}
*
 
* Obtains the {@linkplain SwordWorldPlayer player} for this
* {@linkplain ClientSession session}'s user, and puts the
* player into the One True Room for this {@code SwordWorld}.
*/
public ClientSessionListener loggedIn(ClientSession session) {
logger.log(Level.INFO,
"SwordWorld Client login: {0}", session.getName());
 
// Delegate to a factory method on SwordWorldPlayer,
// since player management really belongs in that class.
SwordWorldPlayer player = SwordWorldPlayer.loggedIn(session);
 
// Put player in room
player.enter(getRoom());
 
// return player object as listener to this client session
return player;
}
}

SwordWorldObject

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
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/*
* Copyright 2007-2009 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package com.sun.sgs.tutorial.server.swordworld;
import java.io.Serializable;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.ManagedObject;
/**
* A {@code ManagedObject} that has a name and a description.
*/
public class SwordWorldObject
implements Serializable, ManagedObject
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
 
/** The name of this object. */
private String name;
 
/** The description of this object. */
private String description;
 
/**
* Creates a new {@code SwordWorldObject} with the given {@code name}
* and {@code description}.
*
* @param name the name of this object
* @param description the description of this object
*/
public SwordWorldObject(String name, String description) {
this.name = name;
this.description = description;
}
/**
* Sets the name of this object.
*
* @param name the name of this object
*/
public void setName(String name) {
AppContext.getDataManager().markForUpdate(this);
this.name = name;
}
/**
* Returns the name of this object.
*
* @return the name of this object
*/
public String getName() {
return name;
}
/**
* Sets the description of this object.
*
* @param description the description of this object
*/
public void setDescription(String description) {
AppContext.getDataManager().markForUpdate(this);
this.description = description;
}
/**
* Returns the description of this object.
*
* @return the description of this object
*/
public String getDescription() {
return description;
}
/** {@inheritDoc} */
@Override
public String toString() {
return getName();
}
}

SwordWorldRoom

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
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/*
* Copyright 2007-2009 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package com.sun.sgs.tutorial.server.swordworld;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.DataManager;
import com.sun.sgs.app.ManagedReference;
/**
* Represents a room in the {@link SwordWorld} example MUD.
*/
public class SwordWorldRoom extends SwordWorldObject
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
 
/** The {@link Logger} for this class. */
private static final Logger logger =
Logger.getLogger(SwordWorldRoom.class.getName());
 
/** The set of items in this room. */
private final Set&gt; items =
new HashSet&gt;();
 
/** The set of players in this room. */
private final Set&gt; players =
new HashSet&gt;();
/**
* Creates a new room with the given name and description, initially
* empty of items and players.
*
* @param name the name of this room
* @param description a description of this room
 
*/
public SwordWorldRoom(String name, String description) {
super(name, description);
}
/**
* Adds an item to this room.
*
* @param item the item to add to this room.
* @return {@code true} if the item was added to the room
*/
public boolean addItem(SwordWorldObject item) {
logger.log(Level.INFO, "{0} placed in {1}",
new Object[] { item, this });
 
// NOTE: we can't directly save the item in the list, or
// we'll end up with a local copy of the item. Instead, we
// must save a ManagedReference to the item.
DataManager dataManager = AppContext.getDataManager();
dataManager.markForUpdate(this);
return items.add(dataManager.createReference(item));
}
/**
* Adds a player to this room.
*
* @param player the player to add
* @return {@code true} if the player was added to the room
*/
public boolean addPlayer(SwordWorldPlayer player) {
logger.log(Level.INFO, "{0} enters {1}",
new Object[] { player, this });
DataManager dataManager = AppContext.getDataManager();
dataManager.markForUpdate(this);
return players.add(dataManager.createReference(player));
}
/**
* Removes a player from this room.
*
* @param player the player to remove
* @return {@code true} if the player was in the room
*/
public boolean removePlayer(SwordWorldPlayer player) {
logger.log(Level.INFO, "{0} leaves {1}",
new Object[] { player, this });
DataManager dataManager = AppContext.getDataManager();
dataManager.markForUpdate(this);
return players.remove(dataManager.createReference(player));
}
/**
* Returns a description of what the given player sees in this room.
*
* @param looker the player looking in this room
* @return a description of what the given player sees in this room
*/
public String look(SwordWorldPlayer looker) {
logger.log(Level.INFO, "{0} looks at {1}",
new Object[] { looker, this });
StringBuilder output = new StringBuilder();
output.append("You are in ").append(getDescription()).append(".\n");
List otherPlayers = getPlayersExcluding(looker);
if (! otherPlayers.isEmpty()) {
output.append("Also in here are ");
appendPrettyList(output, otherPlayers);
output.append(".\n");
}
if (! items.isEmpty()) {
output.append("On the floor you see:\n");
for (ManagedReference itemRef : items) {
SwordWorldObject item = itemRef.get();
output.append(item.getDescription()).append('\n');
}
}
return output.toString();
}
/**
* Appends the names of the {@code SwordWorldObject}s in the list
* to the builder, separated by commas, with an "and" before the final
* item.
*
* @param builder the {@code StringBuilder} to append to
* @param list the list of items to format
*/
private void appendPrettyList(StringBuilder builder,
List<!--xtends SwordWorldObje--> list)
{
if (list.isEmpty()) {
return;
}
int lastIndex = list.size() - 1;
SwordWorldObject last = list.get(lastIndex);
Iterator<!--xtends SwordWorldObje--> it =
list.subList(0, lastIndex).iterator();
if (it.hasNext()) {
SwordWorldObject other = it.next();
builder.append(other.getName());
while (it.hasNext()) {
other = it.next();
builder.append(" ,");
builder.append(other.getName());
}
builder.append(" and ");
}
builder.append(last.getName());
}
/**
* Returns a list of players in this room excluding the given
* player.
*
* @param player the player to exclude
* @return the list of players
*/
private ListgetPlayersExcluding(SwordWorldPlayer player)
{
if (players.isEmpty()) {
return Collections.emptyList();
}
ArrayList otherPlayers =
new ArrayList(players.size());
for (ManagedReference playerRef : players) {
SwordWorldPlayer other = playerRef.get();
if (!player.equals(other)) {
otherPlayers.add(other);
}
}
return Collections.unmodifiableList(otherPlayers);
}
}

 

SwordWorldPlayer

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
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
/*
* Copyright 2007-2009 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package com.sun.sgs.tutorial.server.swordworld;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
import com.sun.sgs.app.DataManager;
import com.sun.sgs.app.ManagedReference;
import com.sun.sgs.app.NameNotBoundException;
/**
* Represents a player in the {@link SwordWorld} example MUD.
*/
public class SwordWorldPlayer
extends SwordWorldObject
implements ClientSessionListener
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
 
/** The {@link Logger} for this class. */
private static final Logger logger =
Logger.getLogger(SwordWorldPlayer.class.getName());
 
/** The message encoding. */
public static final String MESSAGE_CHARSET = "UTF-8";
 
/** The prefix for player bindings in the {@code DataManager}. */
protected static final String PLAYER_BIND_PREFIX = "Player.";
 
/** The {@code ClientSession} for this player, or null if logged out. */
private ManagedReference currentSessionRef = null;
 
/** The {@link SwordWorldRoom} this player is in, or null if none. */
private ManagedReference currentRoomRef = null;
 
/**
* Find or create the player object for the given session, and mark
* the player as logged in on that session.
*
* @param session which session to find or create a player for
* @return a player for the given session
*/
public static SwordWorldPlayer loggedIn(ClientSession session) {
String playerBinding = PLAYER_BIND_PREFIX + session.getName();
// try to find player object, if non existent then create
DataManager dataMgr = AppContext.getDataManager();
SwordWorldPlayer player;
try {
player = (SwordWorldPlayer) dataMgr.getBinding(playerBinding);
} catch (NameNotBoundException ex) {
// this is a new player
player = new SwordWorldPlayer(playerBinding);
logger.log(Level.INFO, "New player created: {0}", player);
dataMgr.setBinding(playerBinding, player);
}
player.setSession(session);
return player;
}
/**
* Creates a new {@code SwordWorldPlayer} with the given name.
*
* @param name the name of this player
*/
protected SwordWorldPlayer(String name) {
super(name, "Seeker of the Sword");
}
/**
* Returns the session for this listener.
*
* @return the session for this listener
*/
protected ClientSession getSession() {
if (currentSessionRef == null) {
return null;
}
return currentSessionRef.get();
}
/**
* Mark this player as logged in on the given session.
*
* @param session the session this player is logged in on
*/
protected void setSession(ClientSession session) {
DataManager dataMgr = AppContext.getDataManager();
dataMgr.markForUpdate(this);
currentSessionRef = dataMgr.createReference(session);
logger.log(Level.INFO, "Set session for {0} to {1}",
new Object[] { this, session });
}
/**
* Handles a player entering a room.
*
* @param room the room for this player to enter
*/
public void enter(SwordWorldRoom room) {
logger.log(Level.INFO, "{0} enters {1}",
new Object[] { this, room }
);
room.addPlayer(this);
setRoom(room);
}
/** {@inheritDoc} */
public void receivedMessage(ByteBuffer message) {
String command = decodeString(message);
logger.log(Level.INFO, "{0} received command: {1}",
new Object[] { this, command }
);
if (command.equalsIgnoreCase("look")) {
String reply = getRoom().look(this);
getSession().send(encodeString(reply));
} else {
logger.log(Level.WARNING, "{0} unknown command: {1}",
new Object[] { this, command }
);
// We could disconnect the rogue player at this point.
//currentSession.disconnect();
}
}
/** {@inheritDoc} */
public void disconnected(boolean graceful) {
setSession(null);
logger.log(Level.INFO, "Disconnected: {0}", this);
getRoom().removePlayer(this);
setRoom(null);
}
/**
* Returns the room this player is currently in, or {@code null} if
* this player is not in a room.
*
 
* @return the room this player is currently in, or {@code null}
*/
protected SwordWorldRoom getRoom() {
if (currentRoomRef == null) {
return null;
}
return currentRoomRef.get();
}
/**
* Sets the room this player is currently in. If the room given
* is null, marks the player as not in any room.
*
 
* @param room the room this player should be in, or {@code null}
*/
protected void setRoom(SwordWorldRoom room) {
DataManager dataManager = AppContext.getDataManager();
dataManager.markForUpdate(this);
if (room == null) {
currentRoomRef = null;
return;
}
currentRoomRef = dataManager.createReference(room);
}
/** {@inheritDoc} */
@Override
public String toString() {
StringBuilder buf = new StringBuilder(getName());
buf.append('@');
if (getSession() == null) {
buf.append("null");
} else {
buf.append(currentSessionRef.getId());
}
return buf.toString();
}
/**
* Encodes a {@code String} into a {@link ByteBuffer}.
*
* @param s the string to encode
* @return the {@code ByteBuffer} which encodes the given string
*/
protected static ByteBuffer encodeString(String s) {
try {
return ByteBuffer.wrap(s.getBytes(MESSAGE_CHARSET));
} catch (UnsupportedEncodingException e) {
throw new Error("Required character set " + MESSAGE_CHARSET +
" not found", e);
}
}
/**
* Decodes a message into a {@code String}.
*
* @param message the message to decode
* @return the decoded string
*/
protected static String decodeString(ByteBuffer message) {
try {
byte[] bytes = new byte[message.remaining()];
message.get(bytes);
return new String(bytes, MESSAGE_CHARSET);
} catch (UnsupportedEncodingException e) {
throw new Error("Required character set " + MESSAGE_CHARSET +
" not found", e);
}
}
}

本文下载地址:http://d.namipan.com/sd/1311061

ProjectDarkStar服务器端开发文档(八)

结尾部分

At this point you know all the basics of writing PDS applications. The applications you write using the PDS API on a single-node system will operate unmodified in exactly the same way on a large-scale multiple-node PDS production back end. In that environment they will scale out horizontally, handle failover, and be fault-tolerant.

       到这里,你了解了所有编写PDS应用的基础知识。你在单节点系统中用PDS的API编写的应用,不用修改,就可以以完全相同的方式在大型的,多节点的PDS产品上运行。在这种环境下,它们将是超水平的、可故障恢复的并且是可容错的。

There are, however, some best practices to follow to ensure optimal scalability for your application. Failing to follow these can seriously limit how many users your application will be able to support at once.

       然而,你可以参照一些最好的实践技巧来确保你应用的最理想的扩展性。如果不按这些来做,将会严重限制你的应用一次性可以支持的用户数量。

最好的实践经验

Do not design with bottleneck Managed Objects.

设计管理对象不要有瓶颈

You can think of each Managed Object as an independent worker who can only do one task at a time. When one task is modifying the state of a Managed Object, any other tasks that want to read or change its state must wait. A Managed Object may have many readers at once if no one is writing to it, but any writing turns it into a potential bottleneck. In general, a pattern of one writer and many readers is the best configuration, although it is not always possible.

       你可以把每个管理对象想象成一个独立的工作者,它一次只可以做一件任务。当一个任务要修改一个管理对象的状态时,其他任何想读或修改它的任务都必须等待。一个管理对象如果没有写操作时,一次可以有很多读取者,但是任何的写操作都会将它转变为一个潜在的瓶颈。通常,一个写和多个读的模式是最好的配置,虽然这不是一直可行。

In the worst case, multiple lockers of the same objects will cause potential deadlock situations that are computationally more expensive to resolve and can result in processing delays (additional latency).

       最坏的情况,对同一个对象进行大量的锁操作会导致潜在的死锁情况,那样为解决问题会消耗更多的资源来计算,可能最终导致处理的延时(额外的潜在的延时)。

Avoid contending for object access.

避免对象访问的冲突

Although the PDS will detect and resolve contentions for write access to objects between parallel events such that your code never actually stops processing, this deadlock avoidance can significantly load the CPU and slow response time to the users. In the worst case, contention can actually prevent parallel processing, seriously impacting the scalability of your code.

       虽然PDS会检测并解决并行事件之间对对象进行访问的冲突,以至于你的代码从来不会停止运行,但是,这种避免死锁的行为会导致CPU负载过高,拖慢对用户的响应时间。最坏的情况,冲突实际上可以阻止并发执行,严重影响你代码的扩展性。

A classic example of a deadlock is two combatants who have to modify both their own data and that of their enemies when resolving an attack. If all the data is on a combatant Managed Object, then both combat tasks need to lock both objects, typically in the opposite order, which is a formula for deadlock. One solution is to divide the combatants’ combat statistics into two groups, the ones they modify themselves and the ones modified by an attacker, and store them on separate Managed Objects. The combatant object would then retain Managed References to these objects, and only lock them as needed.

       一个典型的死锁的例子是,有两个争斗者,当他们要解决一次进攻时,都必须修改自己的数据和敌人的那份数据。如果所有的数据都在一个争斗者管理对象上,那么,两个竞争任务需要锁住各自的对象。死锁的惯例就是通常都处在相反的顺序。(翻的太生涩了,其实意思就是两个任务互相需要对方的资源来完成自己的任务,完成任务前都不能释放自己的资源,由此造成死锁。)一个解决方法是将争斗者的争斗统计数据分割为两组,一组它们自己修改,一组由攻击者来修改,并把它们保存在独立的管理对象中。争斗者对象会保留对这些对象的管理引用,只在需要的时候锁住它们。

Another practical solution is partial serialization of the problem. This is especially appropriate if you want the combatants attacks to be processed in a fixed order. Here you have a single “combat clock” task (generally a repeating task) that processes in sequence all the attacks from all the combatants in a given battle.

另一个实用的方法是将问题部分序列化。如果你想让争斗者的进攻在一个固定的顺序下进行,这就尤其合适了。这里你有一个独立的“争斗计时器”任务(通常是一个循环任务),它会依次处理一场战争中,来自所有战士的所有进攻。

Not all contention situations are this easy to spot and design around. The PDS gives you feedback at runtime as to what objects are deadlocking. Use this to tune your application.

       不是所有冲突的解决方法都能这样简单的点出并设计方案。PDS会在运行时给你反馈,哪个对象正处于死锁。使用这个特性来调节你的应用。

Give all serializable objects a fixed serialVersionUID.

   给所有可序列化的对象一个固定的serialVersionUID

The PDS uses Serialization internally. If you don’t give your Serializable classes a serialVersionUID, then any change to their public interface could invalidate the stored copies, leading to a need to delete the entire Object Store. Giving them a fixed serialVersionUID will allow you to make “compatible changes” without invalidating your existing store. (For what constitutes a compatible change, please see the JDK™ documents on Serialization.)

       PDS使用内部的序列化操作。如果你不给你序列化的类一个serialVersionUID,任何对它们公共接口的改变都可以使已经保存的副本无效,导致需要删除整个对象存储器。给它们一个固定的serialVersionUID可以允许你做“兼容性的改变”,而不会使你已存在的存储器无效。(至于什么构成了兼容性改变,请参看JDK™序列化文档。)

Avoid inner classes in Serializable objects.

   避免在序列化对象中使用内部类

Serialization of objects whose classes contain non-static inner classes gets complicated. It is best to avoid inner classes, including anonymous inner classes. If you must use them, the safest thing is to declare them as static inner classes. (12)

       那些包含非静态内部类的类的对象序列化会变得非常复杂。最好避免使用内部类,包括匿名内部类。如果你必须使用它们,最好将它们申明为静态的。(12)

Do not create non-final static fields on Managed Objects.

   不要在管理对象上创建non-final的静态域

The most important reason for this is that static fields exist only within the scope of a single VM, and a PDS back end floats Managed Objects between many different VMs.

       最重要的理由是:静态域仅存在于一个单独的虚拟机范围之内,一个PDS应用后端的管理对象游走在不同的虚拟机之间。

Do not use the synchronized keyword in Managed Objects.

   不要在管理对象上使用synchronized关键字

First, this is unnecessary in a PDS application. Synchronization is used to prevent contention over data between multiple parallel threads of control. The PDS programming model handles this transparently for you. Second, it won’t work, since synchronization is relative to a single VM and a full PDS back-end operates over many VM instances simultaneously. Finally, it causes interactions between tasks that can defeat the system’s deadlock-proofing feature and actually cause your code to lock up.

       首先,这在PDS应用里是没有必要的。同步化是被用在预防多线程访问数据时的冲突的。PDS的开发模式暗地里为你做了这些处理。第二,它不会工作,因为同步化和一个单独的虚拟机进行关联,而一个完整的PDS后端操作同时在多台虚拟机上执行。最后,它造成任务之间相互影响,会使检验死锁特征的操作失败,使你代码被锁住,不能运行。

Any task that locks up for too long will be forced by the PDS to yield its control of Managed Objects back to the system, but this is a last-ditch safety feature and will result in significant delays in code execution.

       任何锁住很长时间的任务会被PDS强制让出它对管理对象的控制,并交还给系统,但是这是最后一道保险,并且会导致代码执行的大量的延迟。

Do not make blocking I/O calls or stay in a loop for a long period.

   不要使用阻塞式I/O调用,或者处在一个周期很长的循环当中。

The system contains the assumption that tasks are short-lived. If a task lives too long, it will be forcibly terminated by the back end. The right way to do blocking I/O and the like is to create an extension manager, do the blocking calls in it, and submit a task to the Task Manager to handle the results when done. (Writing and installing custom managers will be covered in the Project Darkstar Server Extension Manual.)

       系统会有一个假设:任务都是短命的。如果一个任务生命太长,它会被后端应用强行终止。使用阻塞式I/O操作和其他类似操作的正确方式是创建一个扩展管理器,在里面做阻塞调用,提交一个任务到任务管理器去处理完成的结果。(编写并安装一个自定义的管理器将在Project Darkstar Server Extension Manual里描述。)

Do not catch java.lang.Exception.

不要捕捉java.lang.Exception

Instead, catch the explicit exceptions you are expecting. The PDS also uses exceptions to communicate exceptional states to the execution environment. Although the system does its best to do the right thing even if you hide these exceptions from it, it will operate more efficiently if you don’t.

       作为替代,捕捉你期望出现的明确的异常。PDS也会使用异常来与执行环境的异常状态进行交流。虽然系统会尽量做正确的事情,即使你隐藏了这些异常,但是这会造成更多的资源消耗。

Do not carry a non-transient Java reference on a Managed Object to another Managed Object.

   不要在一个管理对象上携带非瞬时的Java引用到另一个管理对象。

Instead, use a Managed Reference. Any object that is referred to by a Java reference chain that starts at the Managed Object is assumed to be part of the private state of that particular Managed Object. This means that, while you may set two Java references on two different Managed Objects to the same Java object during a task, they will each end up with their own copy of that object at the termination of the task.

       而是要用一个管理引用(Managerd Reference)。任何被一个Java引用关联的对象都会被假定为是这个引用它的管理对象的私有状态的一部分。这就意味着,在一个任务期间,你可能在两个不同的管理对象上各设置了一个Java引用,它们都指向相同的Java对象。在任务终止时,它们会得到各自对那个对象的副本。

Never try to save a Manager Instance on a non-transient field of a Managed Object.

   绝对不要尝试在一个管理对象的非瞬时域上保存一个管理器实例。

This is because Manager References are only valid for the life of the task that fetched them. Manager instances are not serializable objects. Any attempt to save a reference to one in your Managed Object will cause the Data Manager to throw a non-retriable exception and the entire task to be abandoned.

       这是因为管理引用只在那些取得它们的任务的生命周期中有效。管理器实例不是序列化的对象。任何企图保存一个管理器的引用都会导致数据管理器抛出一个non-retriable异常,整个任务都会被放弃。

Carefully manage the life cycle of your Managed Objects.

   小心管理你的管理对象的生命周期

Remember that the Object Store does no garbage collection for you. Managed Objects are “real objects” in the simulation sense. They don’t exist until explicitly created, exist in one and only one state at any given time, and persist until explicitly destroyed. These are very good properties from a simulation programming stance, but if you go wild creating Managed Objects and don’t destroy them when they are no longer useful, you can load down the Object Store with garbage and potentially impact performance.

       记住,对象存储器不会为你进行垃圾收集。管理对象在模拟环境下是“真实对象”。它们直到创建后才会存在,任何时间都只存在于一个状态下,并且是持久的,直到被消除。在模拟程序设计里有非常好的特性,但是如果你无休止的创建管理对象,并且在它们没用的时候不去销毁它们,你会使对象存储器负担过多的垃圾,从而潜在的影响性能。

Be aware that any PDS API call that accepts a Managed Object may create a Managed Reference to that object. If this is the first time the Data Manager has been made aware of the Managed Object, this will result in the Managed Object being added to the Object Store. It is still the developer’s responsibility to remove the Managed Object from the Object Store when it is no longer in use. For this reason, it is best to avoid passing Managed Objects that your application is not explicitly managing into the PDS APIs

       要知道,任何PDS的API的调用来接受一个管理对象都可能创建一个管理引用来引用这个对象。如果这是数据管理器第一次意识到这个管理对象,那么会使这个管理对象添加到对象存储器中。当管理对象不在使用时,从对象存储器中移除它,仍然是开发者的责任。基于这个原因,最好避免将那些你的应用还没有确实管理到的管理对象装入PDS的APIs。

一些指南中没有提到的

This tutorial is intended to introduce you to the fundamentals of coding a PDS application. Although we present all basic uses of the standard managers, these managers have additional functions and capabilities not covered here. Please see the Javadoc for those other functions.

       这篇指南打算为你介绍编写PDS应用的基础知识。虽然我们呈现了所有标准管理器的基本用法,但是这些管理器还有很多额外的功能没有在这里提及。请查看这些功能的Javadoc。

As discussed, the list of managers in a given PDS back end is extensible. This tutorial does not cover writing or using plug-in managers. For that, see the forthcoming document on PDS extensions.

       就像前面讨论过的,PDS后端里的一系列管理器都是可扩展的。这篇指南没有涉及编写和使用扩展的管理器。这些参看即将发出的PDS扩展文档。

Although we use PDS client programs in Lessons 5 and 6, this tutorial does not explain how those are written. For those explanations, please see the Project Darkstar Client Tutorial.

       虽然我们在第5、6课中使用到了客户端程序,但是这里并没有解释那些客户端程序是怎么编写的。这些都在文档Project Darkstar Client Tutorial中。

Finally, for the sake of clarity, this tutorial shows very simple examples. For more complex patterns of PDS usage, please see the community examples at www.projectdarkstar.com.

       最后,为了简洁起见,这篇指南展现了非常简单的例子。更复杂的PDS用法的示例,请查看www.projectdarkstar.com的社区例子。

注释:

(12) 通过代码的生成,一个静态的内部类和外部类没有区别,除了存在于它的外部类命名空间中的类名。

 

本文下载地址:http://d.namipan.com/sd/1311058

ProjectDarkStar服务器端开发文档(七)

第六课:Hello Channels

The previous lessons have introduced the Task Manager and Data Manager. The final standard manager is the Channel Manager. The core of the PDS provides us with basic client/server communications. For simple games, this may be enough. However, for games that organize players into groups, either to isolate game sessions (such as in many casual and fast action games), or to tame the n-squared user-to-user communications scaling issues inherent in massive numbers of simultaneous players, something with lower overhead and more control is required.

       之前的课程介绍了任务管理器(Task Manager)和数据管理器(Data Manager)。最后一个标准管理器是通道管理器Channel Manager)。PDS的核心代码为我们提供了基本的客户端/服务器通信。对于一个简单的游戏来说,这就足够了。然而,对于那些要将玩家组队,不是要隔离游戏会话(就像许多即时性操作游戏),就是要解决不规则的用户对用户的通信,在大量的玩家同时发生时。那就要求更低的开销和更有效的控制。

The Channel Manager provides publish/subscribe channels. The server application can create these channels and then assign users to one or more of them. Communication between users in a channel does not involve the Task or Data Manager.

       通道管理器提供“发布/订阅”通道。服务器程序可以创建这些通道,然后使用它们其中的一个或者更多。在一个通道中的用户之间的通信不会牵涉到任务或数据管理器。

1.编写HelloChannels

The HelloChannels Managed Object is similar to our previous AppListener implementations with the addition that it opens two reliable channels, Foo and Bar.

       HelloChannels这个管理对象很像我们前面的AppListener实现类,它打开了两个可靠的通道,FooBar

HelloChannels

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

The HelloChannelsSessionListener is identical to HelloEchoSessionListener except for the constructor. When we create the session listener, we also join its session to two channels. One channel is passed in, while the second is looked up by name.

       HelloChannelsSessionListenerHelloEchoSessionListener除了构造器之外,其他地方没有区别。当我们创建了会话监听器,我们也将它的会话加入到了两个通道中。一个通道传进来,第二个通过名称查找。

The first channel.join is passed null for a ChannelListener, so all communication on it is only received by clients. The second channel joined however is given a channel listener. This will be called back whenever a message from this session is posted to that channel. The listener can examine the message and sender and decide to discard the message, send a different message to the channel, or send the original, unmodified message to the channel.

        第一个channel.joinChannelListener传递空值,所以,上面所有的通信只能由客户端接收。然而,第二个则给出了一个通道监听器。任何时候,会话里只要有消息传到通道,它都会进行回调。这个监听器可以检查消息,发送消息;也可以丢弃消息,并发送一条不同的消息到通道,或者发送原始的,没有被修改的消息到通道。

Note that, with this code, each session has it own listener for messages on the second channel. This is preferable to registering a single channel-wide listener, since messages from different clients can be processed in parallel. However, if your design really requires a single listener to all messages sent by any client on a channel, you would declare the listener as the second parameter to the createChannel call.

       这个代码中要注意的是,在第二个通道上每个会话都有它自己的消息监听器。这比注册一个单独的通道监听器更好,因为从不同客户端发来的消息可以被并行处理。然而,如果你的设计真的是在一个通道上注册一个独立的监听器来处理任何客户发来的所有的信息,你要申明这个监听器作为createChannel方法调用中的第二个参数。

HelloChannelsSessionListener

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
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/*
* Copyright 2007-2009 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package com.sun.sgs.tutorial.server.lesson6;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.Channel;
import com.sun.sgs.app.ChannelManager;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
import com.sun.sgs.app.DataManager;
import com.sun.sgs.app.ManagedReference;
/**
* Simple example {@link ClientSessionListener} for the Project Darkstar
* Server.
*
 
* Logs each time a session receives data or logs out, and echoes
* any data received back to the sender.
*/
class HelloChannelsSessionListener
implements Serializable, ClientSessionListener
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
 
/** The {@link Logger} for this class. */
private static final Logger logger =
Logger.getLogger(HelloChannelsSessionListener.class.getName());
 
/** The session this {@code ClientSessionListener} is listening to. */
private final ManagedReference sessionRef;
 
/** The name of the {@code ClientSession} for this listener. */
private final String sessionName;
/**
* Creates a new {@code HelloChannelsSessionListener} for the session.
*
* @param session the session this listener is associated with
* @param channel1 a reference to a channel to join
*/
public HelloChannelsSessionListener(ClientSession session,
ManagedReference channel1)
{
if (session == null) {
throw new NullPointerException("null session");
}
DataManager dataMgr = AppContext.getDataManager();
sessionRef = dataMgr.createReference(session);
sessionName = session.getName();
 
// Join the session to all channels. We obtain the channel
// in two different ways, by reference and by name.
ChannelManager channelMgr = AppContext.getChannelManager();
 
// We were passed a reference to the first channel.
channel1.get().join(session);
 
// We look up the second channel by name.
Channel channel2 = channelMgr.getChannel(HelloChannels.CHANNEL_2_NAME);
channel2.join(session);
}
/**
* Returns the session for this listener.
*
* @return the session for this listener
*/
protected ClientSession getSession() {
// We created the ref with a non-null session, so no need to check it.
return sessionRef.get();
}
/**
* {@inheritDoc}
*
 
* Logs when data arrives from the client, and echoes the message back.
*/
public void receivedMessage(ByteBuffer message) {
ClientSession session = getSession();
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Message from {0}", sessionName);
}
session.send(message);
}
/**
* {@inheritDoc}
*
 
* Logs when the client disconnects.
*/
public void disconnected(boolean graceful) {
String grace = graceful ? "graceful" : "forced";
logger.log(Level.INFO,
"User {0} has logged out {1}",
new Object[] { sessionName, grace }
);
}
}

HelloChannelsChannelListener is a simple skeletal listener that just logs what it receives.

       HelloChannelsChannelListener是一个简单的监听器框架,它只记录接收了什么。

HelloChannelsChannelListener

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/*
* Copyright 2007-2009 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package com.sun.sgs.tutorial.server.lesson6;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.Channel;
import com.sun.sgs.app.ChannelListener;
import com.sun.sgs.app.ClientSession;
import java.nio.ByteBuffer;
/**
* Simple example {@link ChannelListener} for the Project Darkstar Server.
*
 
* Logs when a channel receives data.
*/
class HelloChannelsChannelListener
implements Serializable, ChannelListener
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
 
/** The {@link Logger} for this class. */
private static final Logger logger =
Logger.getLogger(HelloChannelsChannelListener.class.getName());
 
/**
* {@inheritDoc}
*
 
* Logs when data arrives on a channel. A typical listener would
* examine the message to decide whether it should be discarded,
* modified, or sent unchanged.
*/
public void receivedMessage(Channel channel,
ClientSession session, ByteBuffer message)
{
if (logger.isLoggable(Level.INFO)) {
logger.log(Level.INFO,
"Channel message from {0} on channel {1}",
new Object[] { session.getName(), channel.getName() }
);
}
channel.send(session, message);
}
}

2.运行HelloChannels

To try HelloChannels you need a client that will connect and allow you to talk on selected channels. One is provided in Lesson 2 of the Project Darkstar Client Tutorial

(com.sun.sgs.tutorial.client.lesson2.HelloChannelClient in tutorial-client.jar).

       运行HelloChannels你需要一个客户端,它会来连接,并且允许你和选择的通道进行对话。一个已经在Project Darkstar Client Tutorial文档中第二课提供了。

(com.sun.sgs.tutorial.client.lesson2.HelloChannelClient in tutorial-client.jar).

 

本文下载地址:http://d.namipan.com/sd/1321402

ProjectDarkStar服务器端开发文档(六)

第五课:Hello User!

Up till now the tutorial lessons have focused on getting your logic up and running in the PDS. But there is another side to the online game equation ─ the users and their computers. This lesson shows how to start communicating between clients and the PDS.

直到现在,指南里的课程都在集中研究你在PDS里的逻辑运行。但是,在网络游戏中还有另一端——用户和他们的电脑。这节课展现了如何开始在客户端与PDS服务器之间建立连接。

In this tutorial, the server side of that communication will be explained and illustrated using a simple pre-built client. For the client-side coding, please see the Project Darkstar Client Tutorial.

这篇指南会解释通信时服务器端的运行,并用一个简单的客户端来说明。关于客户端的代码,请参考Project Darkstar Client Tutorial客户端指南。

1.了解用户的登录

The first step in communicating with users is knowing who is available to communicate with. The PDS provides a callback method on the AppListener for this: loggedIn. The loggedIn method gets passed an object that describes the user; this object is called a ClientSession.(11)

与用户通信的第一步是了解连接时谁是有效的。PDS为此在AppListener里提供了一个回调方法:loggedInloggedIn方法通过一个对象来描述用户,这个对象称之为ClientSession(11)

Below is the code for HelloUser, a trivial application that logs the login of a user.

下面的HelloUser代码,记录了一个用户的登录。

HelloUser

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/*
* Copyright 2007-2009 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sun.sgs.tutorial.server.lesson5;
import java.io.Serializable;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
/**
* Simple example of listening for user {@linkplain AppListener#loggedIn login}
* in the Project Darkstar Server.
* <p>
* Logs each time a user logs in, then kicks them off immediately.
*/
public class HelloUser
implements AppListener, // to get called during startup and login.
Serializable // since all AppListeners are ManagedObjects.
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
 
/** The {@link Logger} for this class. */
private static final Logger logger =
Logger.getLogger(HelloUser.class.getName());
 
// implement AppListener
/** {@inheritDoc} */
public void initialize(Properties props) {
// empty
}
/**
* {@inheritDoc}
* <p>
* Logs a message each time a new session tries to login, then
* kicks them out by returning {@code null}.
*/
public ClientSessionListener loggedIn(ClientSession session) {
// User has logged in
logger.log(Level.INFO, "User {0} almost logged in", session.getName());
// Kick the user out immediately by returning a null listener
return null;
}
}

2.直接通信

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

你会注意到,当你运行上面的服务程序并连接到一个客户时,客户立刻就登出了。这是因为我们在loggedIn里返回null。PDS将此理解为拒绝用户。为了接受用户,允许他或她留下来,你需要返回一个有效的ClientSessionListener。为了有效,这个对象必须实现ClientSessionListenerSerializable接口。下面的HelloUser2就是这样。

HelloUser2

HelloUser2 is identical to HelloUser except for the loggedIn method:

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

HelloUserSessionListener

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

HelloUserSessionListener is a glue object that listens for either data from the user or the disconnect of the user; it allows our server code to respond to these events. So far, all we do is log some information, but in a complete PDS application, these would both be important events to which we would want to respond.

HelloUserSessionListener是一个监听用户发来的数据和用户断开连接的对象,它允许我们的服务器代码响应这些事件。到目前为止,我们所做的只是记录一些信息,但是在一个完整的PDS应用里,这些信息对于我们想要响应的事件来说都是很重要的。

There are two kinds of communication in the PDS:

● Direct Communication

● Channel Communication

在PDS里有两种通信方式:

● 直接通信

● 通道通信

Direct Communication is built into the core of the system and provides a pipe for the flow of data between a single user client and its PDS application.

直接通信被建立为系统的核心,为用户的客户端和它的PDS应用之间的数据流通提供一个管道。

Channel Communication is provided by a standard manager, the Channel Manager, and provides for publish/subscribe group communications. While there is nothing in the Channel Manager’s functionality that could not be implemented on top of the Direct Communication mechanisms, putting the channel functionality in a manager allows for a much more efficient implementation.

通道通信是一个标准管理器提供的,通道管理器,提供给发布/订阅组通信。然而,在通道管理器的函数里,并没有什么不能实现直接通信机制,但考虑到一个更加有效的实现,还是把通道功能放进一个管理器里。

The HelloEcho PDS application echoes back to the user anything the user sends to the application. Besides the name, there is only one line difference in HelloEchoSessionListener from HelloUserSessionListener: the addition of a session.send call.

HelloEcho应用向用户回送任何用户发向应用的数据。除了名字之外,HelloEchoSessionListenerHelloUserSessionListener只有一个不同:增加一个session.send的调用。

HelloEchoSessionListener

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

3.运行例子

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

为了运行这部分的所有例子,你需要一个简单的客户端来登录,以及直接的客户端/服务器通信。你可以在Project Darkstar Client Tutorial客户端指南中的第一课中找到这个客户端(com.sun.sgs.tutorial.client.lesson2.HelloChannelClient in tutorial-client.jar)。

注释:

(11) 事实上,ClientSession描述了新的连接会话,用户信息只是那些参数中的一个。这个区别在那种情况下是很重要的,就是在用户断开连接,会话结束时,你并不能保存一个ClientSession对象并让它是有效的。

本文下载地址:http://d.namipan.com/sd/1311054

ProjectDarkStar服务器端开发文档(五)

第四课:Hello Persistence!

Lesson 3 explained that tasks that are run on a delay or repeat don’t necessarily happen exactly at the time you asked for. They could happen a bit later if (for example) the system is very loaded, or a lot later if (for example) the entire data center has actually come down and had to be restarted.(8)

       第三课解释了任务的延迟或周期性运行,并不需要严格按照你要求的时间点上发生。它们可以稍微迟一点发生,如果(比如)系统负载很高;也可以过很久再发生,如果(比如)全部数据都挂掉,必须重新启动。(8)

To track the last time the run task was called and calculate the true time-delta, we need a way of storing the past time value so that it will survive the system going down. This is called persistent storage, and in real games it is very important. Imagine how your users would react if your machine went down and they all lost their characters and everything on them!

       为了追踪run最后一次被调用,并计算真正的运行时间,我们需要一种方法来保存经过的时间值,以至于它在系统挂掉时可以生存下来。这就是持久存储器,在真实游戏中,它是非常重要的。想象一下,如果你的机器挂了,你的玩家用户失去了所有的角色和装备,他们会是什么反应!

A Managed Object is an object for which the system tracks state and which the system makes persistent. We mentioned above that AppListener interface inherits the Managed Object interface and that your AppListener instance is automatically created by the system for you. The system also registers your AppListener as a Managed Object with the Data Manager. This means that its state will be preserved by the PDS for you.

       一个管理对象(Managed Object)就是一个为系统追踪状态,被系统持久化的对象。我们上面提到的AppListener接口继承了管理对象接口,系统会自动为你创建AppListener实例。系统也会用数据管理器来将AppListener作为一个管理对象注册进来。这就是说,它的状态会由系统帮你维持。

1.编写HelloPersistence

Since our HelloTimer task is a Managed Object, all we need to do is add a field to track the last time run was called. Below is the code for HelloPersistence.

       因为我们的HelloTimer任务是一个管理对象,我们需要做的就是添加一个域,来追踪run最后一次被调用。下面就是HelloPersistence的代码。

Run HelloPersistence as a PDS application. Stop the PDS, wait a minute, and then start it again. You will see that the elapsed time reported includes the down time. This is because currentTimeMillis is based on the system clock, and time kept moving forward even when the PDS wasn’t running.(9)

       作为一个PDS应用运行HelloPersistence。停止PDS,等一分钟,再启动它。你会看到运行时间报告包含了停止的时间。这是因为currentTimeMillis方法是在系统时间的基础上的,就算PDS不运行了,时间还是会一直向前走。(9)

Persistence is that simple and automatic in the PDS. Any non-transient field on a registered Managed Object will be persisted.(10)

       PDS里,持久化就那么简单,并且是自动化的。在一个注册过的管理对象中,任何只要不是瞬态的域,都会被持久化。(10)

HelloPersistence

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
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/*
* Copyright 2007-2009 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
 
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package com.sun.sgs.tutorial.server.lesson4;
import java.io.Serializable;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.Task;
import com.sun.sgs.app.TaskManager;
/**
* A simple persistence example for the Project Darkstar Server.
* As a {@link ManagedObject}, it is able to modify instance fields,
* demonstrated here by tracking the last timestamp at which a task
* was run and displaying the time delta.
*/
public class HelloPersistence
implements AppListener, // to get called during application startup.
Serializable, // since all AppListeners are ManagedObjects.
Task // to schedule future calls to our run() method.
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
 
/** The {@link Logger} for this class. */
private static final Logger logger =
Logger.getLogger(HelloPersistence.class.getName());
 
/** The delay before the first run of the task. */
public static final int DELAY_MS = 5000;
 
/** The time to wait before repeating the task. */
public static final int PERIOD_MS = 500;
 
/** The timestamp when this task was last run. */
private long lastTimestamp = System.currentTimeMillis();
 
// implement AppListener
/**
* {@inheritDoc}
*
 
* Schedules the {@code run()} method to be called periodically.
* Since SGS tasks are persistent, the scheduling only needs to
* be done the first time the application is started. When the
* server is killed and restarted, the scheduled timer task will
* continue ticking.
*
 
* Runs the task {@value #DELAY_MS} ms from now,
* repeating every {@value #PERIOD_MS} ms.
*/
public void initialize(Properties props) {
TaskManager taskManager = AppContext.getTaskManager();
taskManager.schedulePeriodicTask(this, DELAY_MS, PERIOD_MS);
}
/**
* {@inheritDoc}
*
 
* Prevents client logins by returning {@code null}.
*/
public ClientSessionListener loggedIn(ClientSession session) {
return null;
}
// implement Task
/**
* {@inheritDoc}
*
 
* Each time this {@code Task} is run, logs the current timestamp and
* the delta from the timestamp of the previous run.
*/
public void run() throws Exception {
long timestamp = System.currentTimeMillis();
long delta = timestamp - lastTimestamp;
 
// Update the field holding the most recent timestamp.
lastTimestamp = timestamp;
logger.log(Level.INFO,
"timestamp = {0,number,#}, delta = {1,number,#}",
new Object[] { timestamp, delta }
);
}
}

2.编写HelloPersistence2

While we could put all the fields of our application on the AppListener, there are many good reasons not to do this. As any Managed Object grows larger, it takes more time for the system to store and retrieve it. Also, although PDS task code is written as if it were monothreaded, many tasks are actually executing in parallel at any given time. Should the tasks conflict in what data they have to modify, then one will either have to wait for the other to finish or, in a worst-case situation, actually abandon all the work it had done up to that point and try again later.

我们可以把应用中所有的域都放在AppListener中,然而,这里有很多好理由让我们不这么做。任何管理对象增大之时,它都会花费系统更多的时间来存储和恢复。虽然PDS的任务代码编写的好像它是单线程的,但是许多任务在给定时间内实际上是并行执行的。这些任务在必须修改数据时可能产生冲突,一个任务要么就是等另一个完成之后运行,要么,最坏的情况,在那一点停下所有的工作然后稍后重新尝试。

For these reasons, an application will want to create other Managed Objects of its own. Luckily, that’s easy to do!

       因为这个原因,一个应用会想要为自己创建另一个管理对象。幸运的是,这很简单!

All Managed Objects must meet two criteria:

● They must be Serializable.

● They must implement the ManagedObject marker interface.

       所有的管理对象都必须符合两个标准:

●   它们必须是序列化的。

●   它们必须实现ManagedObject接口。

One good way to break your application up into multiple Managed Objects is by the events they handle. A Managed Object can handle only one event at a time, so you want to separate all event handlers for events that might occur in parallel into separate Managed Objects. Below is the code to HelloPersistence2. It creates a separate TrivialTimedTask Managed Object from the AppListener to handle the timed task.

       一个将你的应用分解成多个管理对象的好方式是利用它们处理的事件。一个管理对象在一刻只能处理一个事件,所以,你可以把那些处理并行发生的事件的处理器分解成独立的管理对象。下面是HelloPersistence2的代码。它从AppListener里分解并创建了一个独立的管理对象TrivialTimedTask,来处理定时任务。

HelloPersistence2

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

TrivialTimedTask

This is the Managed Object we are going to have respond to the repeating task.

       这就是我们将要响应重复任务的管理对象。

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

 

3.编写HelloPersistence3

A Managed Object does not actually become managed by the Data Manager, and thus persistent, until the Data Manager is made aware of it. The reason HelloPersistence2 works is because the Task Manager persisted the TrivialTimedTask object for us. In order to persist other Managed Objects, though, an application needs to take on the responsibility of informing the Data Manager itself. One way the Data Manager can become aware of a Managed Object is through a request for a Managed Reference.

       一个管理对象只有等到数据管理器(Data Manager)觉察到它,它才能变为是被数据管理器管理的,持久化的。HelloPersistence2能工作的原因是因为任务管理器(Task Manager)为我们持久化了TrivialTimedTask对象。为了能将其他的对象持久化,一个应用有责任自己告知对象管理器。一个让数据管理器可以觉察到管理对象的方式是请求一个管理引用(Managed Reference)。

Managed Objects often need to refer to other Managed Objects. This is done with a Managed Reference. It is very important that the only fields on one Managed Object that reference another Managed Object be Managed References. This is how the Data Manager knows that it is a reference to a separate Managed Object. If you store a simple Java reference to the second Managed Object in a field on the first Managed Object, the second object will become part of the first object’s state when the first object is stored. The result will be that, the next time the first object tries to access the second, it will get its own local copy and not the real second Managed Object

       管理对象经常需要与其他管理对象进行关联。管理引用来做这个事情。很重要的一点是,只有那些引用了其他管理对象的管理对象上的域才是管理引用。这就是数据管理器如何识别一个指向一个独立的管理对象的引用。如果你在第一个管理对象的域上保存一个指向第二个管理对象的简单的Java引用,当第一个对象被存储时,第二个对象将变成第一个对象状态的一部分。结果是,下次第一个对象尝试访问第二个对象时,它会得到自己本地的拷贝,而不是真正的第二个对象。

HelloPersistence3 below illustrates this by creating a second persistent object that is called from the TrivialTimedTask and that keeps the last-called time as part of its persistent state.

       下面的HelloPersistence3用创建第二个持久化对象来说明,这个对象被TrivialTimedTask调用,并保持最后调用时间作为它的持久化状态的一部分。

HelloPersistence3

HelloPersistence3, below, is a task that delegates to a sub-task (a TrivialTimedTask that is not scheduled to run on its own). The sub-task is stored in a Managed Reference on HelloPersistence3.

       下面的HelloPersistence3是将任务委托给一个分解任务(TrivialTimedTask,自身不能调用运行)。这个分解任务在HelloPersistence3里的一个管理引用中被存储。

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
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/*
* Copyright 2007-2009 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sun.sgs.tutorial.server.lesson4;
import java.io.Serializable;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
import com.sun.sgs.app.DataManager;
import com.sun.sgs.app.ManagedReference;
import com.sun.sgs.app.Task;
import com.sun.sgs.app.TaskManager;
/**
* A simple persistence example for the Project Darkstar Server.
*/
public class HelloPersistence3
implements AppListener, Serializable, Task
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
 
/** The {@link Logger} for this class. */
private static final Logger logger =
Logger.getLogger(HelloPersistence3.class.getName());
 
/** The delay before the first run of the task. */
public static final int DELAY_MS = 5000;
 
/** The time to wait before repeating the task. */
public static final int PERIOD_MS = 500;
 
/** A reference to our subtask, a {@link TrivialTimedTask}. */
private ManagedReference<TrivialTimedTask> subTaskRef = null;
 
/**
* Gets the subtask this task delegates to. Dereferences a
* {@link ManagedReference} in this object that holds the subtask.
* <p>
* This null-check idiom is common when getting a ManagedReference.
*
* @return the subtask this task delegates to, or null if none is set
*/
public TrivialTimedTask getSubTask() {
if (subTaskRef == null) {
return null;
}
return subTaskRef.get();
}
/**
* Sets the subtask this task delegates to. Stores the subtask
* as a {@link ManagedReference} in this object.
* <p>
* This null-check idiom is common when setting a ManagedReference,
* since {@link DataManager#createReference createReference} does
* not accept null parameters.
*
* @param subTask the subtask this task should delegate to,
* or null to clear the subtask
*/
public void setSubTask(TrivialTimedTask subTask) {
if (subTask == null) {
subTaskRef = null;
return;
}
DataManager dataManager = AppContext.getDataManager();
subTaskRef = dataManager.createReference(subTask);
}
 
// implement AppListener
/**
* {@inheritDoc}
* <p>
* Schedules the {@code run()} method of this object to be called
* periodically.
* <p>
* Since SGS tasks are persistent, the scheduling only needs to
* be done the first time the application is started. When the
* server is killed and restarted, the scheduled timer task will
* continue ticking.
* <p>
* Runs the task {@value #DELAY_MS} ms from now,
* repeating every {@value #PERIOD_MS} ms.
*/
public void initialize(Properties props) {
// Hold onto the task (as a managed reference)
setSubTask(new TrivialTimedTask());
TaskManager taskManager = AppContext.getTaskManager();
taskManager.schedulePeriodicTask(this, DELAY_MS, PERIOD_MS);
}
/**
* {@inheritDoc}
* <p>
* Prevents client logins by returning {@code null}.
*/
public ClientSessionListener loggedIn(ClientSession session) {
return null;
}
 
// implement Task
/**
* {@inheritDoc}
* <p>
* Calls the run() method of the subtask set on this object.
*/
public void run() throws Exception {
// Get the subTask (from the ManagedReference that holds it)
TrivialTimedTask subTask = getSubTask();
if (subTask == null) {
logger.log(Level.WARNING, "subTask is null");
return;
}
 
// Delegate to the subTask's run() method
subTask.run();
}
}

 

Another way to register a Managed Object to the Data Manager is with the setBinding call. This call does not return a Managed Reference, but instead binds the Managed Object to the string passed in with it to the call. Once a Managed Object has a name bound to it, the Managed Object may be retrieved by passing the same name to the getBinding call. Note that name bindings must be distinct. For each unique string used as a name binding by an application, there can be one and only one Managed Object bound.

       另一个将管理对象注册到数据管理器的方法是调用setBinding方法。这个调用不会返回一个管理引用,取而代之的是将管理对象绑定到一个字符串上。一旦管理对象有一个绑定好的名字,这个管理对象可以通过相同的名字调用getBinding方法重新得到。注意,名称绑定必须是不重复的。一个应用里,每个唯一的字符串作为一个绑定名称,有且只有一个管理对象绑定。

Retrieving a Managed Object by its binding has some additional overhead, so it’s better to keep Managed References to Managed Objects in the other Managed Objects that need to call them. There are, however, some problems that are best solved with a name-binding convention; one common example is finding the player object for a particular player at the start of his or her session.

       通过名称绑定来重新得到管理对象会有有一些额外的开销,所以,最好还是在那些需要调用管理对象的管理对像上保持指向管理对象的管理引用。然而,有一些问题最好还是用名称绑定解决,一个普通的例子,在会话开始的时候,为一个特定的玩家找到玩家对象。

There are a number of other interesting methods on the Data Manager. You might want to look at the Javadoc now, but discussion of them will be put off until required by the tutorial applications.

       在数据管理器里还有很多有趣的方法。你现在可能想看看Javadoc,但是,关于它们的讨论将会拖延,直到指南中的应用需要提到它们。

注释:

(8) 一个完整的Project Darkstar Server的生产环境提供故障恢复机制,所以一台服务器的损失不会导致游戏的挂掉。在真正的灾难中,就像整个数据中心断电了,全部的后端程序可以脱机运行。

(9) 依赖于你的操作系统,在PDS运行时,你可能会看到HelloPersistence输出的运行时间会在500ms上下浮动。这是因为currentTimeMillis方法并不需要精确到1ms。尤其是,Windows系统的currentTimeMillis方法本来就比Java SE平台精确度低。

(10) 一个瞬态的域是被transient关键字标记的。一些值在超出使用它们的任务时,是无效的,它们就应该标记为transient,比如,一个域在当前的任务期间,缓存了一个管理器。

 

本文下载地址:http://d.namipan.com/sd/1311052

ProjectDarkStar服务器端开发文档(四)

第三课:任务(Tasks),管理器(Managers)和Hello Timer!

1.任务(Tasks)

In Lessons 1 and 2, the system automatically invoked the initialize method for us in response to an event (in this case, the initial run of a newly installed PDS application). This meant executing code within the PDS environment.

       在第一、二两课中,系统自动为我们调用initialize方法来回应一个事件(在这里,一个新安装的PDS应用开始运行)。这意味着在PDS环境下执行代码。

All code run in the PDS environment must be part of a task. From the point of view of the application coder, a task is a piece of monothreaded, transactional, event-driven code. This means that the task runs as if it were the only task executing at that moment, and all actions done by the task to change data occur in an all-or-nothing manner.

       所有在PDS环境下执行的代码都必须是一个任务(task中的一部分。从程序编写者的角度来看,一个任务就是一块单线程的、事务处理、事件驱动的代码。这就意味着,任务的运行就好像在这一刻只有它在运行,任务做的所有有关修改数据的操作都以“全做或全不做”的方式发生。

The realities of task execution

任务执行的真实情况

Each individual task executes in a monothreaded manner. However, if we executed them serially, waiting for each one to finish before the next one started, it would not be possible to get the kind of scaling the PDS provides.

       每个任务都以单线程的方式执行。然而,如果我们顺序的执行它们,下一个任务的开始要等到前面每个任务完成,那就不可能得到PDS提供的好处了。

Instead, the PDS executes many of these monothreaded tasks simultaneously and watches for contention on the individual Managed Objects. If two tasks contend for control over a Managed Object, one task will be held up and will wait for the other to finish before it can proceed.

       其实,PDS在同时执行许多单线程任务,并且注意在单个管理对象上产生的冲突。如果两个任务想要支配同一个管理对象,一个任务将被挂起,等到另一个完成操作之后再运行。

Many tasks can read the state of the same Managed Object at the same time without causing contention. If any of them wants to write to it, however, that can cause contention with tasks that read from or write to the same Managed Object.

       许多任务可以同时读相同管理对象的状态而不会产生冲突。然而,如果它们中的任何一个任务想去写它,就会和那些读或写同一个管理对象的任务产生冲突。

To achieve optimal performance, it is important to design your data structures and game logic with as little potential object-contention as possible. Be especially wary of places where multiple tasks that are likely to occur simultaneously might have to write to the same Managed Object.

       为了达到最佳性能,在设计你的数据结构和游戏逻辑时,尽量不要有潜在的对象冲突时很重要的。尤其要小心的地方是,很多任务可能在同时必须要写相同的管理对象。

All tasks registered with the PDS scheduler implement the interface Task, which has one method on it — run.(6) A task may be submitted to the scheduler to be executed either as soon as possible, or after a minimum delay time. A task can be one-shot or repeating. If it is a repeating task, it is also submitted with a period of repeat.

       所有任务以PDS的调度程序来注册,都要实现Task接口,Task接口有一个方法——run。(6) 一个任务可能通过调度程序被提交去立即执行或是经过一段最小时间后执行。一个任务可以只执行一次或是反复执行。如果是个反复执行的任务,它提交时还要提交反复执行的时间段。

A repeating task is the same thing as a “timer” or “heartbeat” in traditional game systems; it lets you effectively generate an event to be handled every specified number of milliseconds.

       一个反复执行的程序和传统游戏系统中的“计时器”或“心跳程序”是一回事;它让你有效的每隔特定毫秒数产生一个事件被处理。

2.管理器(Managers)

All communication between your server application’s game logic and the world outside of it is accomplished through managers. As described above, there are three standard managers:

● Task Manager

● Data Manager

● Channel Manager

       服务器应用的游戏逻辑与外部世界之间的所有通信都是通过管理器(managers)来完成的。就像上面描述的,有三个标准的管理器:

● 任务管理器(Task Manager)

● 数据管理器(Data Manager)

● 通道管理器(Channel Manager)

There can also be installation-specific managers; these can be written by the author of the server application and deployed with the application into a PDS back end. The following static calls on the AppContext class are used by server application code to get a reference to a manager to talk to:

getTaskManager()

getDataManager()

getChannelManager()

getManager(managerClass)

       也可以有特殊安装的管理器,它们可以由服务器应用的作者来编写,用PDS后端的应用来开发。下面的AppContext类的静态调用方法被服务器端应用代码用来得到一个管理器的引用:

●  getTaskManager()

●  getDataManager()

●  getChannelManager()

●  getManager(managerClass)

The first three are covered in this tutorial. The last is a generic call to get an installation-specific manager; it will be covered in the forthcoming Project Darkstar Server Extension Manual.

       前三个方法在本文中涉及。最后一个是来调用一个特殊安装的管理器;它将在即将到来的Project Darkstar Server Extension Manual文档中进行讲解。

IMPORTANT: A Manager Reference is valid only for the life of the task within which the get manager call was invoked. Therefore, you should not try to cache manager references for use outside of that one invocation chain; get them from the AppContext instead.

       重点:一个管理引用(Manager Reference)只在那个调用了获取管理器方法的任务的生命里是有效的。所以,你不要试图将管理对象缓存起来,在那个调用之外使用;而是要从AppContext得到它们。

The Task Manager is the part of the PDS that contains the scheduler, and thus what we use to schedule tasks. In order to be scheduled with the Task Manager as a task to be run, an object must be serializable and must implement the Task interface.7 The example below turns our AppListener into a task and starts it logging “Hello Timer” messages after a five-second delay at a half-second repeat period.

       任务管理器是PDS系统的一部分,它包含调度器,我们可以用它来调度任务。为了作为一个任务让任务管理器来调度运行,一个对象必须序列化并且必须实现Task接口。(7) 下面的例子将我们的AppListener变成一个任务,并且开始运行5秒后,每隔半秒反复打印记录“Hello Timer”。

IMPORTANT: While the PDS stack makes a best effort to run tasks on schedule, it may back off execution of repeating tasks under heavy load. In that case, the task will skip execution of this period and reschedule to the next period. Additionally, contention for Managed Objects may cause a timed task to delay its execution.

       重点:当PDS在全力进行调度运行任务时,在重负载下,它可能暂缓或放弃循环任务的执行。那样的话,那个任务将会跳过这段周期的执行,重新调度到下个周期。例外,管理对象的冲突可能导致一个定时任务的延迟执行。

The requested repeat frequency of a timed task is similar to a target frame rate in a game client, where frames may be dropped if they are taking too long to compute. If your application logic is tied to elapsed time or absolute number of “beats” in a given period of time, you’ll need to check the elapsed time and handle skipped periods in your run logic.

       一个定时任务的请求的重复频率类似于一个游戏客户端中一个目标框架比率,这个客户端中,如果框架需要花太多时间来计算,那么它们可能会被清除掉。如果你的程序逻辑受困于运行时间或是给定的时间片里的若干“beats”,那么你将需要检查在run逻辑里运行时间和处理跳过的周期。(这段翻译的不好….)

3.编写HelloTimer

The HelloTimer application below uses the TaskManager to schedule a repeating task. The task will run after a delay of 5000ms at a frequency of once every 500ms.

       HelloTimer应用使用TaskManager来调度一个循环任务。这个任务会在延迟5000毫秒后以500毫秒一次的频率运行。

HelloTimer

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
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/*
* Copyright 2007-2009 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package com.sun.sgs.tutorial.server.lesson3;
import java.io.Serializable;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
import com.sun.sgs.app.Task;
import com.sun.sgs.app.TaskManager;
/**
* A simple timed-task example for the Project Darkstar Server.
* It uses the {@link TaskManager} to schedule itself as a periodic task
* that logs the current timestamp on each execution.
*/
public class HelloTimer
implements AppListener, // to get called during application startup.
Serializable, // since all AppListeners are ManagedObjects.
Task // to schedule future calls to our run() method.
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
 
/** The {@link Logger} for this class. */
private static final Logger logger =
Logger.getLogger(HelloTimer.class.getName());
 
/** The delay before the first run of the task. */
public static final int DELAY_MS = 5000;
 
/** The time to wait before repeating the task. */
public static final int PERIOD_MS = 500;
 
// implement AppListener
/**
* {@inheritDoc}
*
 
* Schedules the {@code run()} method to be called periodically.
* Since SGS tasks are persistent, the scheduling only needs to
* be done the first time the application is started. When the
* server is killed and restarted, the scheduled timer task will
* continue ticking.
*
 
* Runs the task {@value #DELAY_MS} ms from now,
* repeating every {@value #PERIOD_MS} ms.
*/
public void initialize(Properties props) {
TaskManager taskManager = AppContext.getTaskManager();
taskManager.schedulePeriodicTask(this, DELAY_MS, PERIOD_MS);
}
/**
* {@inheritDoc}
*
 
* Prevents client logins by returning {@code null}.
*/
public ClientSessionListener loggedIn(ClientSession session) {
return null;
}
// implement Task
/**
* {@inheritDoc}
*
 
* Logs the current timestamp whenever this {@code Task} gets run.
*/
public void run() throws Exception {
logger.log(Level.INFO,
"HelloTimer task: running at timestamp {0,number,#}",
System.currentTimeMillis());
}
}

Now that we have a repeating event, we have our first application that will do something when stopped and restarted. Task registration is persistent, which is to say, it survives a crash and reboot. Try stopping the server and restarting it again to see this in action.

       现在,我们有了一个重复产生的事件,有了第一个在启动和重启的时候会做些操作的应用。任务的注册时持久的,就是说,它能在程序崩溃和重启中生存。试试停止服务然后重新启动它来看看这个持久性。

The periodic task is information stored in the Object Store along with your managed objects, so if you clear the data directory and return it to its pristine, uninitialized state, the periodic tasks will also get cleared.

       周期性的任务信息和你的管理对象一起存储在对象存储器中,所以,如果你清除了数据目录,然后让它回到原来的、未初始化的状态,周期性任务也会被清除。

This behavior allows you to write your code as if the server were always up, with the caveat that you do have to check elapsed time in your periodic task’s run method if a delay between that and the last time it was run has significance to your logic.

       这个行为允许你编写、修改代码时,就像服务一直在运行一样,需要警告你的是,如果实际运行的时间和它上次运行时间之间的延迟对你的逻辑有重要的意义,你必须检查在你周期性任务的run方法中实际运算的时间。

How you keep track of the last time run was called is the subject of the next lesson.

       怎样来留意上次运行被调用是下节课的主题。

注释:

(6) 虽然像AppListener这样的事件处理接口不实现Task接口,但是这些监听器都是通过PDS的内置Task来调用。

(7) 你可能想知道为什么TaskManagedObject接口为什么不继承Serializable。答案必须用到接口上的Serialization版本控制。为了防备serialVersionUID的不匹配,任何继承了Serializable接口的类或接口都要申明一个私有的serialVersionUID。但是因为接口只能申明公共变量,所以,最好是避免制造序列化的接口,而是留给类(或抽象类)来实现Serializable接口。

本文下载地址:http://d.namipan.com/sd/1311050

←Older