📄 ch19.htm
字号:
By switching between the Web browsers, you can simulate a network
game between the two players. Go ahead and outwit yourself so
that you can see what happens when one of the players wins. This
situation is shown in Figure 19.3.
<P>
<A HREF="f19-3.gif" ><B>Figure 19.3 : </B><I>The NetConnect4 game with two client players in a finished game.</I></A>
<P>
For another game to start between the same two players, each player
just needs to click once in the applet window. You can see now
how two players interact together in a game of NetConnect4. Now,
if you really want to test the game, try loading two more instances
of the Web browser and starting another game between two new players.
In this scenario, you have a total of four players involved in
two separate games, all running off the same server. The game
server supports an unlimited number of players and games, although
at some point it might be wise to impose a limit so that performance
doesn't start dragging. A couple of hundred players banging away
at your game server might tend to slow things down!
<P>
You now understand how the game plays, along with the roles of
the client and server, so you're ready to actually dig into the
source code and really see how things work. You've come to the
right place.
<H3><A NAME="DevelopingNetConnect4"><B>Developing NetConnect4</B></A>
</H3>
<P>
The client/server nature of NetConnect4 doesn't just apply at
the conceptual level, it also plays a role in how the code is
laid out for the game. Because the client and server components
function as separate programs, it makes sense to develop the code
for them as two different efforts. With that in mind, let's tackle
each part separately.
<H4><B>The Server</B></H4>
<P>
The NetConnect4 server is composed of four classes:
<UL>
<LI><TT><FONT FACE="Courier">Connect4Server</FONT></TT>
<LI><TT><FONT FACE="Courier">Connect4Daemon</FONT></TT>
<LI><TT><FONT FACE="Courier">Connect4Player</FONT></TT>
<LI><TT><FONT FACE="Courier">Game</FONT></TT>
</UL>
<P>
The <TT><FONT FACE="Courier">Connect4Server</FONT></TT> class
serves as a stub program to get the server started. Check out
the source code for it:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">class Connect4Server {<BR>
public static void main(String args[]) {<BR>
System.out.println("NetConnect4 server
up and running...");<BR>
new Connect4Daemon().start();<BR>
}<BR>
}</FONT></TT>
</BLOCKQUOTE>
<P>
As you can see, the <TT><FONT FACE="Courier">Connect4Server</FONT></TT>
class contains only one method, <TT><FONT FACE="Courier">main</FONT></TT>,
which prints a message and creates a <TT><FONT FACE="Courier">Connect4Daemon</FONT></TT>
object. The <TT><FONT FACE="Courier">Connect4Daemon</FONT></TT>
class is where the server is actually created and initialized.
The <TT><FONT FACE="Courier">Connect4Daemon</FONT></TT> class
is responsible for creating the server socket and handling client
connections. Take a look at the member variables defined in the
<TT><FONT FACE="Courier">Connect4Daemon</FONT></TT> class:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">public static final int PORTNUM = 1234;
<BR>
private ServerSocket port;<BR>
private Connect4Player playerWaiting = null;<BR>
private Game thisGame
= null;</FONT></TT>
</BLOCKQUOTE>
<P>
Other than the constant port number, <TT><FONT FACE="Courier">Connect4Daemon</FONT></TT>
defines three member variables consisting of a <TT><FONT FACE="Courier">ServerSocket</FONT></TT>
object, a <TT><FONT FACE="Courier">Connect4Player</FONT></TT>
object, and a <TT><FONT FACE="Courier">Game</FONT></TT> object.
The <TT><FONT FACE="Courier">Connect4Player</FONT></TT> and <TT><FONT FACE="Courier">Game</FONT></TT>
classes are covered a little later in the lesson. The <TT><FONT FACE="Courier">ServerSocket</FONT></TT>
member object, <TT><FONT FACE="Courier">port</FONT></TT>, is created
using an arbitrary port number above 1024. If you recall from
yesterday's lesson, all ports below 1024 are reserved for standard
system services, so you must use one above 1024. More specifically,
I chose 1234 as the port number, which is represented by the <TT><FONT FACE="Courier">PORTNUM</FONT></TT>
constant.
<P>
<CENTER><TABLE BORDERCOLOR=#000000 BORDER=1 WIDTH=80%>
<TR><TD><B>Warning</B></TD></TR>
<TR><TD>
<BLOCKQUOTE>
Using a port number greater than 1024 doesn't guarantee that the port will be available. It does guarantee, however, that the port isn't already assigned to a common service. Nevertheless, any other extended services, such as game servers, could
potentially conflict with your port number. If your port number conflicts with another server, just try a different one.</BLOCKQUOTE>
</TD></TR>
</TABLE></CENTER>
<P>
<P>
The <TT><FONT FACE="Courier">run</FONT></TT> method in <TT><FONT FACE="Courier">Connect4Daemon</FONT></TT>
is where the details of connecting clients are handled:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">public void run() {<BR>
Socket clientSocket;<BR>
while (true) {<BR>
if (port == null) {<BR>
System.out.println("Sorry,
the port disappeared.");<BR>
System.exit(1);<BR>
}<BR>
try {<BR>
clientSocket = port.accept();
<BR>
new Connect4Player(this, clientSocket).start();
<BR>
}<BR>
catch (IOException e) {<BR>
System.out.println("Couldn't
connect player: " + e);<BR>
System.exit(1);<BR>
}<BR>
}<BR>
}</FONT></TT>
</BLOCKQUOTE>
<P>
The <TT><FONT FACE="Courier">run</FONT></TT> method first retrieves
the socket for a connecting client via a call to the <TT><FONT FACE="Courier">ServerSocket</FONT></TT>
class's <TT><FONT FACE="Courier">accept</FONT></TT> method. If
you recall from yesterday's lesson, the <TT><FONT FACE="Courier">accept</FONT></TT>
method waits until a client connects and then returns a socket
for the client. After a client connects, a <TT><FONT FACE="Courier">Connect4Player</FONT></TT>
object is created using the client socket.
<P>
<CENTER><TABLE BORDERCOLOR=#000000 BORDER=1 WIDTH=80%>
<TR><TD><B>Note</B></TD></TR>
<TR><TD>
<BLOCKQUOTE>
Even though the <TT><FONT FACE="Courier">Connect4Daemon</FONT></TT> class functions very much like a daemon thread, you don't specify it as a Java daemon thread because you don't want it to be destroyed by the runtime system. You might be wondering why the
Java runtime system would go around killing innocent threads. Because daemon threads always run as support for other non-daemon threads or programs, the Java runtime system kills them if there are no non-daemon threads executing.
</BLOCKQUOTE>
</TD></TR>
</TABLE></CENTER>
<P>
<P>
The <TT><FONT FACE="Courier">waitForGame</FONT></TT> method is
where players are paired up with each other. Listing 19.1 contains
the source code for the <TT><FONT FACE="Courier">waitForGame</FONT></TT>
method.
<HR>
<BLOCKQUOTE>
<B>Listing 19.1. The </B><TT><B><FONT FACE="Courier">Connect4Daemon</FONT></B></TT><B>
class's </B><TT><B><FONT FACE="Courier">waitForGame</FONT></B></TT><B>
method.<BR>
</B>
</BLOCKQUOTE>
<BLOCKQUOTE>
<TT><FONT FACE="Courier">public synchronized Game waitForGame(Connect4Player
p) {<BR>
Game retval = null;<BR>
if (playerWaiting == null) {<BR>
playerWaiting = p;<BR>
thisGame = null; //
just in case!<BR>
p.send("PLSWAIT");<BR>
while (playerWaiting != null) {<BR>
try {<BR>
wait();<BR>
}<BR>
catch (InterruptedException
e) {<BR>
System.out.println("Error:
" + e);<BR>
}<BR>
}<BR>
return thisGame;<BR>
}<BR>
else {<BR>
thisGame = new Game(playerWaiting, p);
<BR>
retval = thisGame;<BR>
playerWaiting = null;<BR>
notify();<BR>
return retval;<BR>
}<BR>
}</FONT></TT>
</BLOCKQUOTE>
<HR>
<P>
The <TT><FONT FACE="Courier">waitForGame</FONT></TT> method is
called from within the <TT><FONT FACE="Courier">Connect4Player</FONT></TT>
class, which you'll learn about in a moment. <TT><FONT FACE="Courier">waitForGame</FONT></TT>
is passed a <TT><FONT FACE="Courier">Connect4Player</FONT></TT>
object as its only parameter. If no player is waiting to play,
this player is flagged as a waiting player, and a loop is entered
that waits until another player connects. A null <TT><FONT FACE="Courier">Game</FONT></TT>
object is then returned to indicate that only one player is present.
When another player connects and <TT><FONT FACE="Courier">waitForGame</FONT></TT>
is called, things happen a little differently. Because a player
is now waiting, a <TT><FONT FACE="Courier">Game</FONT></TT> object
is created using the two players. This <TT><FONT FACE="Courier">Game</FONT></TT>
object is then returned to indicate that the game is ready to
begin.
<P>
The <TT><FONT FACE="Courier">finalize</FONT></TT> method in <TT><FONT FACE="Courier">Connect4Daemon</FONT></TT>
is simply an added measure to help clean up the server socket
when the daemon dies:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">protected void finalize() {<BR>
if (port != null) {<BR>
try { <BR>
port.close(); <BR>
}<BR>
catch (IOException e) {<BR>
System.out.println("Error
closing port: " + e);<BR>
}<BR>
port = null;<BR>
}<BR>
}</FONT></TT>
</BLOCKQUOTE>
<P>
To clean up the server socket, <TT><FONT FACE="Courier">finalize</FONT></TT>
simply calls the <TT><FONT FACE="Courier">close</FONT></TT> method
on the port.
<P>
The <TT><FONT FACE="Courier">Connect4Daemon</FONT></TT> class
made a few references to the <TT><FONT FACE="Courier">Connect4Player</FONT></TT>
class, which logically represents a player in the game. Listing
19.2 contains the source code for the <TT><FONT FACE="Courier">Connect4Player</FONT></TT>
class.
<HR>
<BLOCKQUOTE>
<B>Listing 19.2. The </B><TT><B><FONT FACE="Courier">Connect4Player</FONT></B></TT><B>
class.<BR>
</B>
</BLOCKQUOTE>
<BLOCKQUOTE>
<TT><FONT FACE="Courier">class Connect4Player extends SocketAction
{<BR>
private Connect4Daemon daemon = null;<BR>
<BR>
public Connect4Player(Connect4Daemon server, Socket
sock) {<BR>
super(sock);<BR>
daemon = server;<BR>
}<BR>
<BR>
public void run() {<BR>
daemon.waitForGame(this).playGame(this);
<BR>
}<BR>
<BR>
public void closeConnections() {<BR>
super.closeConnections();<BR>
if (outStream != null) {<BR>
send("GAMEOVER");
<BR>
}<BR>
}<BR>
}</FONT></TT>
</BLOCKQUOTE>
<HR>
<P>
The <TT><FONT FACE="Courier">Connect4Player</FONT></TT> class
represents a player from the server's perspective. <TT><FONT FACE="Courier">Connect4Player</FONT></TT>
is derived from <TT><FONT FACE="Courier">SocketAction</FONT></TT>,
which is the generic socket class you developed yesterday. I told
you it would come in handy. The only member variable defined in
<TT><FONT FACE="Courier">Connect4Player</FONT></TT> is <TT><FONT FACE="Courier">daemon</FONT></TT>,
which holds the <TT><FONT FACE="Courier">Connect4Daemon</FONT></TT>
object associated with the player.
<P>
The constructor for <TT><FONT FACE="Courier">Connect4Player</FONT></TT>
takes <TT><FONT FACE="Courier">Connect4Daemon</FONT></TT> and
<TT><FONT FACE="Courier">Socket</FONT></TT> objects as its two
parameters. The <TT><FONT FACE="Courier">Connect4Daemon</FONT></TT>
object is used to initialize the <TT><FONT FACE="Courier">daemon</FONT></TT>
member variable, and the <TT><FONT FACE="Courier">Socket</FONT></TT>
object is passed on to the parent constructor in <TT><FONT FACE="Courier">SocketAction</FONT></TT>.
<P>
The <TT><FONT FACE="Courier">run</FONT></TT> method for <TT><FONT FACE="Courier">Connect4Player</FONT></TT>
calls back to the daemon's <TT><FONT FACE="Courier">waitForGame</FONT></TT>
method to get a <TT><FONT FACE="Courier">Game</FONT></TT> object
for the player. The <TT><FONT FACE="Courier">playGame</FONT></TT>
method is then called on the <TT><FONT FACE="Courier">Game</FONT></TT>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -