📄 chatserver.java
字号:
/* * Copyright (c) 2000 Lyrisoft Solutions, Inc. * Used by permission */package com.lyrisoft.chat.server.remote;import java.util.Collection;import java.util.Date;import java.util.Map;import java.util.HashMap;import java.util.Iterator;import java.util.ArrayList;import java.util.Properties;import java.util.StringTokenizer;import java.util.Vector;import java.util.HashSet;import java.net.ServerSocket;import java.net.Socket;import java.io.IOException;import java.io.BufferedReader;import java.io.InputStreamReader;import com.lyrisoft.chat.Translator;import com.lyrisoft.chat.ICommands;import com.lyrisoft.chat.Constants;import com.lyrisoft.chat.server.remote.IAuthenticator;import com.lyrisoft.util.io.*;import com.lyrisoft.util.properties.*;import com.lyrisoft.util.CircularQueue;import com.lyrisoft.chat.server.remote.command.*;import javax.jms.*;/** * This is the Chat Server */public class ChatServer implements ICommands, IServerCommands { public static final boolean DEBUG = getDebug(); protected static Logger logger; protected static Logger errorLogger; protected int _port; protected ServerSocket _serverSocket; protected boolean _keepGoing = true; protected long _creationTime; protected IAuthenticator _authenticator; protected HashMap _users; protected HashMap _rooms; protected String _motd; protected Vulture _vulture; protected Thread _vultureThread; protected Dispatcher _dispatcher; protected Thread _dispatchThread; protected Distributor _distributor; protected Thread _distributorThread; protected DistributedState _distributedState = new DistributedState(this); protected int _cumulativeLogins = 0; public ChatServer(String conf) throws Exception { _dispatcher = createDispatcher(); _dispatchThread = new Thread(_dispatcher, "Dispatcher"); _dispatchThread.start(); System.err.println("using " + conf); Properties p = PropertyTool.loadProperties(conf); preProcessProperties(p); String logFile = null; String errorLogFile = null; logFile = p.getProperty("log.file"); errorLogFile = p.getProperty("error.log.file"); createLoggers(logFile, errorLogFile); String authClass = PropertyTool.getString("auth.class", p); String commands = PropertyTool.getString("commands", p); Properties commandProps = PropertyTool.loadProperties(commands); initCommandProcessor(commandProps); String messages = PropertyTool.getString("messages", p); Properties messagesProps = PropertyTool.loadProperties(messages); initTranslator(messagesProps); _port = PropertyTool.getInteger("listen.port", p); _authenticator = (IAuthenticator)Class.forName(authClass).newInstance(); String sIdle = p.getProperty("idle.timeout"); double idleTimeout = 60.0; if (sIdle != null) { idleTimeout = Double.valueOf(sIdle).doubleValue(); } _vulture = new Vulture(this, idleTimeout); readMotd(); _creationTime = System.currentTimeMillis(); _users = new HashMap(); _rooms = new HashMap(); if (System.getProperty("BSD_HACK") != null) { log("FreeBSD Workaround is enabled."); } boolean useJms = false; try { useJms = PropertyTool.getBoolean("jms.enabled", p); } catch (PropertyException e) {} if (useJms) { _distributor = new Distributor(this, p); _distributorThread = new Thread(_distributor, "Distributor"); _distributorThread.start(); } else { log("Not distributing messages over JMS"); } _serverSocket = new ServerSocket(_port); _vultureThread = new Thread(_vulture, "Vulture"); _vultureThread.start(); } /** * A hook for reading in your own extra nfc.conf properties */ public void preProcessProperties(Properties p) throws PropertyException { } public int getCumulativeLogins() { return _cumulativeLogins; } public ChatServer() throws Exception { this("nfc.conf"); } protected Dispatcher createDispatcher() { return new Dispatcher(); } public Dispatcher getDispatcher() { return _dispatcher; } public void initTranslator(Properties p) { Translator.init(p); } public void initCommandProcessor(Properties p) { CommandProcessorRemote.init(p); } void distributorDisconnected(Distributor d) { } void distributorConnected(Distributor d) { announcePresence(); } protected void readMotd() throws ResourceException, IOException { BufferedReader reader = null; StringBuffer sb = new StringBuffer(); try { reader = new BufferedReader(new InputStreamReader(ResourceLoader.getResource("nfc.motd"))); String next; while ((next = reader.readLine()) != null) { sb.append(next); sb.append("\n"); } _motd = sb.toString(); } finally { if (reader != null) { reader.close(); } } } /** * Log a message (goes to the standard log) */ public static void log(String message) { logger.log(message); } /** * Log an error (goes to the error log) */ public static void logError(String message) { errorLogger.log(message); } /** * Log an exception (goes to the error log) */ public static void log(Exception e) { errorLogger.log(e); } /** * Get the RoomServer for a particular room */ public RoomServer getRoom(String roomName) { return (RoomServer)_rooms.get(roomKey(roomName)); } /** * Get a ChatClient by name */ public ChatClient getClient(String clientName) { return (ChatClient)_users.get(clientKey(clientName)); } /** * Determines if a user id is valid. Ensures that the name is made up of * alphanumerical characters and contains no spaces. */ protected void validateUserId(String user) throws AccessException { if (getName().toLowerCase().equals(user.toLowerCase())) { throw new AccessException(Translator.getMessage("reserved_name", getName())); } else if (Constants.SERVER_NAME.equalsIgnoreCase(user)) { throw new AccessException(Translator.getMessage("reserved_name", Constants.SERVER_NAME)); } char[] chars = user.toLowerCase().toCharArray(); for (int i=0; i < chars.length; i++) { if (!Character.isLetterOrDigit(chars[i]) && chars[i] != '_' && chars[i] != '-') { throw new AccessException(INVALID_CHARACTER); } } } public Map getUsers() { return _users; } public void serverSignOn(String server) { _distributedState.addServer(server); } public void serverSignOff(String server) { _distributedState.deleteServer(server); } public void setRemoteUserList(String server, String[] users) { Collection oldUsers = _distributedState.getUsersOnServer(server); HashMap newMap = new HashMap(); // copy the ORIGINAL user list for this server into a map for (Iterator i = oldUsers.iterator(); i.hasNext(); ) { String s = (String)i.next(); newMap.put(clientKey(s), s); } // go through the NEW user list. call remoteSignOn for each // new user, and remove any already-known users from // the map for (int i=0; i < users.length; i++) { String key = clientKey(users[i]); if (newMap.get(key) == null) { // user is brand new. call remoteSignOn remoteSignOn(server, users[i]); } else { // user is already known. do nothing newMap.remove(key); } } // now the map contains users which no longer exist, so we remove them // -- TL: I'm not sure if the map will ever have anything left in it, // but this doesn't hurt. for (Iterator i = oldUsers.iterator(); i.hasNext(); ) { remoteSignOff(server, (String)i.next()); } } public void remoteSignOn(String server, String userId) { // XXX check if user exists locally first, and if so we must notify _distributedState.signon(server, userId); } public void remoteSignOff(String server, String userId) { synchronized (_rooms) { for (Iterator i = _rooms.values().iterator(); i.hasNext(); ) { RoomServer room = (RoomServer)i.next(); if (_distributedState.userExistsInRoom(userId, room.getName())) { remotePartRoom(server, userId, room.getName(), true); } } } Collection deadRooms = _distributedState.signoff(server, userId); } /** * Sign on to the server * @param client the ChatClient * @param password * @exception AccessException is the login failed */ public void signOn(ChatClient client, String password) throws AccessException, AccessDenied { log(client.getUserId() + " is attempting a signon"); String userId = client.getUserId(); validateUserId(userId); Auth auth = _authenticator.authenticate(userId, password); client.setUserId(auth.getUserId()); synchronized (_users) { if (_distributedState.userExists(clientKey(client))) { throw new AccessException(ALREADY_SIGNED_ON); } else { int access = auth.getAccess(); client.setAccessLevel(access); _users.put(clientKey(client), client); log(client.getUserId() + " is authenticated. Access = " + access + (client.getTunneling() ? " (tunneling)" : "")); _cumulativeLogins++; } } client.ackSignon(auth.getUserId()); sendMotd(client); _distributedState.signon(getName(), userId); /* broadcastSignOnToUsers(userId); */ } void broadcastSignOnToUsers(String userId) { synchronized (_users) { for (Iterator i = _users.values().iterator(); i.hasNext(); ) { ChatClient cc = (ChatClient)i.next(); if (!cc.getUserId().equals(cc)) { cc.userSignOn(userId); } } } } void broadcastSignOffToUsers(String userId) { synchronized (_users) { for (Iterator i = _users.values().iterator(); i.hasNext(); ) { ChatClient cc = (ChatClient)i.next(); cc.userSignOff(userId); } } } public void broadcast(String s) { synchronized (_users) { for (Iterator i = _users.values().iterator(); i.hasNext(); ) { ChatClient client = (ChatClient)i.next(); client.sendRaw(s); } } } protected void sendMotd(ChatClient client) { StringTokenizer st = new StringTokenizer(_motd, "\r\n"); while (st.hasMoreTokens()) { client.generalMessage(st.nextToken()); } } /** * Kick a user off the system. */ public void kill(String victim, ChatClient killer, String message) { ChatClient c = getClient(victim); if (c == null) { killer.error(NO_SUCH_USER, victim); } else { if (c.getAccessLevel() >= killer.getAccessLevel()) { killer.error(ACCESS_DENIED, KILL + " " + victim); } else { c.killed(killer.getUserId(), message); signOff(c); killer.ackKill(victim); } } } /** * Sign off of the system. This is called by the ChatClient when the socket unexpectedly * closes, or when the user quits. */ public void signOff(ChatClient client) { if (client.getAccessLevel() == IAuthenticator.NONE) { // not authenticated means not logged in. // just kill the client and return log("signoff: anonymous user"); client.die(); _vulture.removeClient(client); return; } String name = client.getUserId(); log("signoff: " + name); Object o = null; synchronized (_users) { o = _users.remove(clientKey(client)); } _vulture.removeClient(client); if (o != null) { // inform all the rooms that this user is gone synchronized (_rooms) { for (Iterator i = _rooms.values().iterator(); i.hasNext(); ) { RoomServer room = (RoomServer)i.next(); room.part(name, true); if (room.isEmpty()) { log("Removing empty room: " + room.getName()); i.remove(); } } } } client.die(); Collection deadRooms = _distributedState.signoff(getName(), client.getUserId()); // notify the rest of the world (because the SignOff command processor is not // guaranteed to execute.. For instance, if the user crashed without signing out) distribute(client, SIGNOFF); } /** * Remote a user from a room */ public void partRoom(ChatClient client, String roomName) { String key = roomKey(roomName); boolean empty = false; synchronized (_rooms) { RoomServer room = (RoomServer)_rooms.get(key); if (room != null) { room.part(client, false); if (room.isEmpty()) { empty = true; log("Removing empty room: " + roomName); _rooms.remove(roomKey(room)); room = null; } } } _distributedState.part(getName(), roomName, client.getUserId()); } public void notifyClientsRoomDestroyed(String roomName) { synchronized (_users) { for (Iterator i = _users.values().iterator(); i.hasNext(); ) { ChatClient cc = (ChatClient)i.next(); cc.roomDestroyed(roomName);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -