📄 netserver.java
字号:
/*
* Light And Shadow. A Persistent Universe based on Robert Jordan's Wheel of Time Books.
* Copyright (C) 2001-2002 WOTLAS Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package wotlas.libs.net;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.net.BindException;
import java.io.IOException;
import java.io.InterruptedIOException;
import wotlas.utils.Debug;
import wotlas.utils.Tools;
import wotlas.libs.net.utils.NetInterface;
import wotlas.libs.net.connection.AsynchronousNetConnection;
import wotlas.libs.net.message.ServerErrorMessage;
import wotlas.libs.net.message.ServerWelcomeMessage;
import wotlas.libs.net.message.ServerErrorMsgBehaviour;
import wotlas.libs.net.message.ServerWelcomeMsgBehaviour;
/** A NetServer awaits client connections. There are many types of Server depending on what
* you want to do :
*<br>
* - servers that have no predefined user connection list, i.e. we
* don't know the clients that are going to connect (this is our case here).
*<br>
* - server that maintains client accounts. To create this type of server
* extend this class and override the accessControl() method.
*<br>
*<p>
* This server creates and uses AsynchronousNetConnection. If you want to use another
* connection type, override the getNewConnection() method. Note also that when we create
* our new connection we don't assign any "context" object. To assign one do it in
* the accessControl() method with the "connection.setContext()" call.
*</p>
* @author Aldiss
* @see wotlas.libs.net.NetConnection
*/
public class NetServer extends Thread implements NetConnectionListener, NetErrorCodeList {
/*------------------------------------------------------------------------------------*/
/** Period between two bind() tries if the network interface is not ready.
*/
public static final long INTERFACE_BIND_PERIOD = 1000*60*3; // 3 min
/** To prevent servers from accessing to netwok info at the same time
*/
private static final byte systemNetLock[] = new byte[0];
/*------------------------------------------------------------------------------------*/
/** Server Socket
*/
protected ServerSocket server;
/** Server Net Interface. We accept three format : an IP address, a DNS name or a network interface
* name followed by a ',' with an integer indicating the IP index for that network interface.
* If you are not sure just set the index to 0, it will point out the first available IP for
* the given interface.<br>
* Example : "wotlas.dynds.org", "192.168.0.2", "lan1,0".
*/
protected String serverInterface;
/** Server Port.
*/
protected int serverPort;
/** Maximum number of opened sockets for this server.
*/
private int maxOpenedSockets;
/** Our listeners (objects that will be informed of our state ).
*/
private NetServerListener listeners[];
/** Our connections with clients
*/
private NetConnection connections[];
/** Stop server ?
*/
protected boolean stopServer;
/** User lock to temporarily forbid new connections
*/
private boolean serverLock;
/*------------------------------------------------------------------------------------*/
/** Constructs a NetServer on the specified host/port, but does not starts it.
* Call the start() method to start the server. You have to give the name of
* the packages where we'll be able to find the NetMessageBehaviour classes to use.<p>
*
* <p>Server Host Name. We accept three format : an IP address, a DNS name or a network interface
* name followed by a ',' with an integer indicating the IP index for that network interface.
* If you are not sure just set the index to 0, it will point out the first available IP for
* the given interface.<br>
*
* Example : "wotlas.dynds.org", "192.168.0.2", "lan1,0".</p>
*
* <p>By default we accept a maximum of 200 opened socket connections for
* this server. This number can be changed with setMaximumOpenedSockets().</p>
*
* @param host the interface to monitor and to bind to.
* @param serverPort port on which the server listens to clients.
* @param msgPackages a list of packages where we can find NetMsgBehaviour Classes.
*/
public NetServer( String serverInterface, int serverPort, String msgPackages[] ) {
super("Server");
this.serverInterface = serverInterface;
this.serverPort = serverPort;
stopServer = false;
serverLock = false;
maxOpenedSockets = 200; // default maximum number of opened sockets
listeners = new NetServerListener[0];
connections = new NetConnection[maxOpenedSockets];
// we add the new message packages to the message factory
int nb = NetMessageFactory.getMessageFactory().addMessagePackages( msgPackages );
Debug.signal(Debug.NOTICE,null,"Loaded "+nb+" network message behaviours...");
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
/** To get a valid ServerSocket. If the interface is not ready we will display a warning
* message and wait INTERFACE_BIND_PERIOD milliseconds before retrying.
* @return a valid server socket
*/
private ServerSocket getServerSocket() {
// We get the ip address of the specified host
InetAddress hostIP = null;
boolean updateServerSocket = false;
// We check the format of the "host" field
if( serverInterface.indexOf(',')<0 ) {
// ok this is an IP or DNS Name
do{
try{
hostIP = InetAddress.getByName( serverInterface );
}
catch(UnknownHostException ue) {
// Interface is not ready
for( int i=0; i<listeners.length; i++ )
listeners[i].serverInterfaceIsDown( serverInterface );
// we wait some time before retrying...
synchronized( this ) {
try{
wait( INTERFACE_BIND_PERIOD );
}catch( Exception e ) {}
}
}
if(mustStop())
return server;
}
while( hostIP ==null );
}
else {
// ok, we have an interface name
int separatorIndex = serverInterface.indexOf(',');
int ipIndex = -1;
try{
ipIndex = Integer.parseInt( serverInterface.substring( separatorIndex+1, serverInterface.length() ) );
}catch(Exception e) {
Debug.signal(Debug.FAILURE,this,"Invalid network interface format : "+serverInterface+" should be <itf>,<ip-index> !");
Debug.exit();
}
// We wait for the interface to be up
do{
String itfIP[] = null;
synchronized( systemNetLock ){ // <-- to prevent servers from accessing net info at the same time
itfIP = NetInterface.getInterfaceAddresses( serverInterface.substring(0,separatorIndex) );
}
if(itfIP==null || itfIP.length<=ipIndex) {
// Interface is not ready
if(server==null) {
Debug.signal(Debug.FAILURE,this,"Network Interface MUST be enabled at start-up so that we load appropriate libraries !");
Debug.exit();
}
for( int i=0; i<listeners.length; i++ )
listeners[i].serverInterfaceIsDown( serverInterface.substring(0,separatorIndex)+" - ip "+ipIndex );
// we wait some time before retrying...
synchronized( this ) {
try{
wait( INTERFACE_BIND_PERIOD );
}catch( Exception e ) {}
}
}
else {
// Interface is up !
try{
hostIP = InetAddress.getByName( itfIP[ipIndex] );
}catch( UnknownHostException uhe ) {
Debug.signal( Debug.FAILURE, this, "Could not use IP given by NetworkInterface ! "+uhe ); // FATAL !
Debug.exit();
}
if(server!=null && !server.getInetAddress().equals(hostIP) )
updateServerSocket = true;
}
if(mustStop())
return server;
}
while( hostIP ==null );
}
// 2 - Has the state of the network interface changed ?
if( !updateServerSocket && server!=null ) {
for( int i=0; i<listeners.length; i++ )
listeners[i].serverInterfaceIsUp( hostIP.getHostAddress(), false ); // state not changed
return server;
}
// 3 - ServerSocket creation is needed here.
if(server!=null)
try{
server.close();
}catch( IOException ioe ) {
Debug.signal(Debug.ERROR,this,"Error while closing old server socket : "+ioe);
}
try{
server = new ServerSocket(serverPort, 50, hostIP ); // new server socket
server.setSoTimeout(5000);
}
catch (Exception e){
Debug.signal( Debug.FAILURE, this, "Could not create server socket ! "+e ); // FATAL !
Debug.exit();
}
for( int i=0; i<listeners.length; i++ )
listeners[i].serverInterfaceIsUp( hostIP.getHostAddress(), true ); // state changed
return server;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
/** This method is called automatically when a new client establishes a connection
* with this server ( the client sends a ClientRegisterMessage ). We are supposed
* to provide here some basic access control.<br>
*
* The default implementation here contains the STRICT MINIMUM : we accept every
* client connection without considering the content of the key they provide.<br>
*
* You can redefine this method (recommended) to :
*<p>
* 1) consider if the key ( key parameter ) provided by the client is correct for your
* application. For example for a chat Server a key could be a chat channel name. In
* the case of a repository server, a key could be "login:password".
*</p><p>
* 2) initialize the client session context ( connection.setContext() ). The context can be
* any type of object and should be client dependent. It will be given to the messages
* coming from the client. For example, in a ChatServer the context could be the Chat
* chanel object the client wants to register to. This way message behaviours would have
* a direct access to their right chat channel.
*</p><p>
* 3) MANDATORY : if you decide to accept this client, call the acceptClient() method.
* it will validate the client connection. If you decide to refuse the client, call
* the refuseClient() method with an appropriate error message. It will immediately
* close the connection.
*</p>
* @param connection a previously created connection for this connection.
* @param key a string given by the client to identify itself.
*/
public void accessControl( NetConnection connection, String key ) {
// we accept every client
acceptClient( connection );
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
/** Creates a connection object for this new connection.
* Override this method if you don't want to use the AsynchronousNetConnection.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -