📄 732-735.html
字号:
<HTML>
<HEAD>
<META name=vsisbn content="1571690433"><META name=vstitle content="Black Art of Java Game Programming"><META name=vsauthor content="Joel Fan"><META name=vsimprint content="Sams"><META name=vspublisher content="Macmillan Computer Publishing"><META name=vspubdate content="11/01/96"><META name=vscategory content="Web and Software Development: Programming, Scripting, and Markup Languages: Java"><TITLE>Black Art of Java Game Programming:The Internet MahJong Server</TITLE>
<!-- HEADER --><STYLE type="text/css"> <!-- A:hover { color : Red; } --></STYLE><META NAME="ROBOTS" CONTENT="NOINDEX, NOFOLLOW"><script><!--function displayWindow(url, width, height) { var Win = window.open(url,"displayWindow",'width=' + width +',height=' + height + ',resizable=1,scrollbars=yes'); if (Win) { Win.focus(); }}//--></script><SCRIPT><!--function popUp(url) { var Win = window.open(url,"displayWindow",'width=400,height=300,resizable=1,scrollbars=yes'); if (Win) { Win.focus(); }}//--></SCRIPT><script language="JavaScript1.2"><!--function checkForQuery(fm) { /* get the query value */ var i = escape(fm.query.value); if (i == "") { alert('Please enter a search word or phrase'); return false; } /* query is blank, dont run the .jsp file */ else return true; /* execute the .jsp file */}//--></script></HEAD><BODY>
<TABLE border=0 cellspacing=0 cellpadding=0>
<tr>
<td width=75 valign=top>
<img src="../1571690433.gif" width=60 height=73 alt="Black Art of Java Game Programming" border="1">
</td>
<td align="left">
<font face="arial, helvetica" size="-1" color="#336633"><b>Black Art of Java Game Programming</b></font>
<br>
<font face="arial, helvetica" size="-1"><i>by Joel Fan</i>
<br>
Sams, Macmillan Computer Publishing
<br>
<b>ISBN:</b> 1571690433<b> Pub Date:</b> 11/01/96</font>
</td>
</tr>
</table>
<P>
<!--ISBN=1571690433//-->
<!--TITLE=Black Art of Java Game Programming//-->
<!--AUTHOR=Joel Fan//-->
<!--AUTHOR=Eric Ries//-->
<!--AUTHOR=Calin Tenitchi//-->
<!--PUBLISHER=Macmillan Computer Publishing//-->
<!--IMPRINT=Sams//-->
<!--CHAPTER=18//-->
<!--PAGES=732-735//-->
<!--UNASSIGNED1//-->
<!--UNASSIGNED2//-->
<CENTER>
<TABLE BORDER>
<TR>
<TD><A HREF="728-732.html">Previous</A></TD>
<TD><A HREF="../ewtoc.html">Table of Contents</A></TD>
<TD><A HREF="735-738.html">Next</A></TD>
</TR>
</TABLE>
</CENTER>
<P><BR></P>
<H4 ALIGN="LEFT"><A NAME="Heading17"></A><FONT COLOR="#000077">The Listener Thread</FONT></H4>
<P>As explained earlier, we need a listener thread for each player. This is why Player extends Thread. It is clear that its run() method should basically be a packet dispatcher:
</P>
<!-- CODE //-->
<PRE>
private Integer working = new Integer(0);
public void run () {
loop:
while (connected) {
try {
byte tag = in.readByte();
// don’t stop until we finish with this packet
synchronized (working) {
switch (tag) {
case CP_QUIT:
break loop;
case CP_MESSAGE:
hdMessage();
break;
// call other packet handlers
}
}
} catch (IOException e) {
break loop;
}
}
connected = false;
out_thread.stop();
disconnect();
}
public void stopOnWait () {
connected = false; // so that run() won't loop again
synchronized (working) { // stop only if blocked on input
stop();
}
}
</PRE>
<!-- END CODE //-->
<P>The stopOnWait() method is called when an abnormal event happens to a player, for example, when the server decides his network connection is dead and wants to clear his slot. The run() method may be in the middle of handling a packet, so we don’t want to stop it immediately and potentially leave the server in an inconsistent state. However, we do make sure that the thread dies before it reads in the next packet.
</P>
<P>I only present here a handler method for CP_MESSAGE. It first determines whether a message is directed to all players on the server or only to those on the player’s table (I chose not to allow personal messages as a countermeasure against cheating), then constructs a corresponding SP_MESSAGE packet and sends it to targeted players. Other packet handlers are similar in nature, so I will skip them.</P>
<!-- CODE //-->
<PRE>
void hdMessage throws IOException {
// CP_MESSAGE contains a target type (MSG_SERVER,
// MSG_TABLE, or MSG_ALL), and a text message.
byte type = in.readByte();
String text = in.readUTF();
if (type == Packet.MSG_SERVER)
System.out.println("Msg from " + id + ": " + text);
else if (type == Packet.MSG_TABLE && table != null)
table.outputTable(Packet.SPMessage(Packet.MSG_TABLE, id, text));
else
outputAll(Packet.SPMessage(Packet.MSG_ALL, id, text));
}
</PRE>
<!-- END CODE //-->
<H4 ALIGN="LEFT"><A NAME="Heading18"></A><FONT COLOR="#000077">The Replier Thread</FONT></H4>
<P>It remains to explain the use of PlayerOutput <I>out_thread</I>. This is the replier thread for our player. Let me first convince you that there is a need for two threads per player. Sending packets is done via the output() method. Its naive implementation is to just do an out.write() call. However, this may cause problems. Player A’s listener thread may need to send another player B’s client a packet (this happens, for example, when A sends everyone a message); thus, somewhere inside A.run() we have a call to B.output(). But as we know, network connections are far from perfect, so such a call, implemented the naive way, might take an arbitrarily long time to finish. This would prevent A.run() from handling more packets in a timely manner. Indeed, a general rule of thumb is that all packet handlers must do their job quickly.</P>
<P>To solve this problem, I let the output() method simply add the packet to a queue. The replier thread of the player pops them off the queue and sends them out. First, let me give a version of the Queue class:</P>
<!-- CODE //-->
<PRE>
package types;
class QueueLink { // a linked list node for our Queue
public QueueLink next;
public Object obj;
public QueueLink (Object o) {
obj = o;
next = null;
}
}
public class Queue { // first-in, first-out
private QueueLink head = null, tail = null;
public synchronized Object pop () {
while (head == null) {
try {
wait(); // block until we get an item
} catch (InterruptedException e) {
}
}
Object o = head.obj; // pop the first item
head = head.next;
if (head == null) {
tail = null;
}
return o;
}
public synchronized void push (Object o) {
if (head == null) {
head = tail = new QueueLink(o);
} else {
tail.next = new QueueLink(o);
tail = tail.next;
}
notify(); // wake up blocked threads
}
}
</PRE>
<!-- END CODE //-->
<P>The actual class for the replier thread is PlayerOutput:
</P>
<!-- CODE //-->
<PRE>
package server;
import java.io.*;
class PlayerOutput extends Thread {
private static Player the_player;
private DataOutputStream out;
private Queue pkt_queue = new Queue();
public PlayerOutput (Player p, DataOutputStream o) {
the_player = p;
out = o;
}
public void output (byte p[]) {
pkt_queue.push(p);
}
public void run () {
loop:
while (true) { // keep sending packets out
byte p[] = (byte[])pkt_queue.pop();
if (p != null && p.length > 0) {
try {
out.write(p, 0, p.length);
} catch (IOException e) { // bad connection
break loop;
}
}
}
the_player.stopOnWait(); // stop the listener thread
the_player.disconnect(); // make the player leave
}
}
</PRE>
<!-- END CODE //-->
<P>Note that since we keep two threads for each player, abnormal conditions like a dead network connection could occur in either thread. Some care needs to be taken to stop both threads (when they can be safely stopped) on such occasions.
</P><P><BR></P>
<CENTER>
<TABLE BORDER>
<TR>
<TD><A HREF="728-732.html">Previous</A></TD>
<TD><A HREF="../ewtoc.html">Table of Contents</A></TD>
<TD><A HREF="735-738.html">Next</A></TD>
</TR>
</TABLE>
</CENTER>
</BODY>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -