/* Author: Josh Holtrop * GVSU, CS654, Project 1 * Date: 2008-03-11 */ import java.io.*; import java.net.*; import java.util.*; /* KaZaServer implements a "Mini Napster Hub" group leader peer * for the KaZa file sharing system. The listening server runs * as a thread, and each incoming connection to it is also handled * by a thread so that multple clients can be connected and upload * their file list and descriptions as well as search for files * simultaneously. */ public class KaZaServer implements Runnable { public static final int LISTEN_PORT = 3442; private ServerSocket m_serverSocket; private HashMap m_clientData; private Vector m_peerGroupLeaders; private HashMap m_currentSearches; /* create a KaZaServer */ public KaZaServer() { m_clientData = new HashMap(); m_peerGroupLeaders = new Vector(); m_currentSearches = new HashMap(); } /* begin listening for incoming connections */ public void run() { try { m_serverSocket = new ServerSocket(LISTEN_PORT); /* porcess connection requests */ for (;;) { Socket clientSocket = m_serverSocket.accept(); Thread thread = new Thread(new ClientHandler(clientSocket)); thread.start(); } } catch (IOException ioe) { System.out.println("KaZaServer: IO Exception!"); ioe.printStackTrace(); return; } } /* the MNH server administrator invokes this method to instruct * the server to connect to a peer group leader. This peer group * leader is then used to forward searches to aggregate search * results for peers connected to linked-to group leaders */ public void connectToPeerGroupLeader(String peerName, boolean sendReverseConnectMsg) { /* check if the peer group leader is already in our list */ synchronized (m_peerGroupLeaders) { for (Socket s : m_peerGroupLeaders) { if (s.getInetAddress().getHostAddress().equals(peerName)) return; } } Socket s; try { s = new Socket(peerName, LISTEN_PORT); DataOutputStream os = new DataOutputStream(s.getOutputStream()); os.writeBytes("HELO KaZaServer\n"); if (sendReverseConnectMsg) os.writeBytes("GRPL\n"); } catch (Exception e) { return; } /* add new peer group leader to the peer group leaders list */ synchronized (m_peerGroupLeaders) { m_peerGroupLeaders.add(s); } } public void close() { } /* return a list of the currently connected clients */ public Object[] getClientList() { Set s; synchronized (m_clientData) { s = m_clientData.keySet(); } return s.toArray(); } /* return a list of the linked-to peer group leaders */ public Object[] getPeerGroupLeaderList() { Vector peers = new Vector(); synchronized (m_peerGroupLeaders) { for (Socket s : m_peerGroupLeaders) { peers.add(s.getInetAddress().getHostAddress()); } } return peers.toArray(); } /* simple class to store information about a connected KaZaClient */ private class ClientInfo { String userName = "Anonymous"; int speed = 1500; HashMap files = new HashMap(); } /* this class can be run in a thread and handles communication * with a single client */ private class ClientHandler implements Runnable { private Socket m_socket; private String m_clientIP; ClientInfo m_clientInfo; public ClientHandler(Socket socket) { m_socket = socket; m_clientIP = m_socket.getInetAddress().getHostAddress(); synchronized (m_clientData) { if (!m_clientData.containsKey(m_clientIP)) { m_clientData.put(socket.getInetAddress().getHostAddress(), new ClientInfo()); } m_clientInfo = m_clientData.get(m_clientIP); } } public void run() { try { BufferedReader br = new BufferedReader( new InputStreamReader( m_socket.getInputStream())); DataOutputStream os = new DataOutputStream( m_socket.getOutputStream()); /* loop processing client messages */ boolean running = true; while (running) { String inLine = br.readLine(); StringTokenizer tokens = new StringTokenizer(inLine); if (!tokens.hasMoreTokens()) continue; String opCode = tokens.nextToken(); opCode = opCode.toUpperCase(); if (opCode.equals("HELO")) { /* user is announcing his or her username */ synchronized (m_clientData) { m_clientInfo.userName = inLine.substring(5); } // System.out.println("Got HELO from " + m_clientInfo.userName); } else if (opCode.equals("SPED")) { /* user is telling us their connection speed */ if (tokens.hasMoreTokens()) { int speed = Integer.parseInt(tokens.nextToken()); synchronized (m_clientData) { m_clientInfo.speed = speed; } // System.out.println("Got SPED of " + m_clientInfo.speed); } } else if (opCode.equals("DESC")) { /* user is giving us a description of a file */ String fileName = inLine.substring(5); String fileDesc = br.readLine(); synchronized (m_clientData) { m_clientInfo.files.put(fileName, fileDesc); } // System.out.println("Got DESC of '" + fileName + // "': '" + fileDesc + "'"); } else if (opCode.equals("SRCH")) { /* user is requesting a search */ if (tokens.hasMoreTokens()) { int depth = Integer.parseInt(tokens.nextToken()); if (tokens.hasMoreTokens()) { String query = tokens.nextToken(); String searchResults = performSearch(depth, query, m_socket); os.writeBytes(searchResults); os.writeBytes(".\n"); // System.out.println("Got SRCH for " + query + // ", results:\n" + searchResults); } } } else if (opCode.equals("QUIT")) { /* user is requesting to leave the system */ br.close(); os.close(); m_socket.close(); running = false; } else if (opCode.equals("GRPL")) { /* user is a peer group leader, initiate a connection * to it if one is not established */ connectToPeerGroupLeader(m_clientIP, false); } } } catch (Exception e) { } finally { synchronized (m_clientData) { m_clientData.remove(m_clientIP); } } } } /* this method is called when a client is asking our server to * perform a keyword search on its file database */ private String performSearch(int depth, String query, Socket clientSocket) { String results = ""; String querylc = query.toLowerCase(); /* don't perform a search that we are already performing * (avoid duplicate results) */ synchronized (m_currentSearches) { if (m_currentSearches.containsKey(querylc)) return results; m_currentSearches.put(querylc, 1); } /* if the depth > 0, then we can forward this search to our * peer group leaders to aggregate their results with ours */ if (depth > 0) { synchronized (m_peerGroupLeaders) { for (int i = 0; i < m_peerGroupLeaders.size(); i++) { if (m_peerGroupLeaders.elementAt(i).isClosed()) { m_peerGroupLeaders.removeElementAt(i); i--; } else { String peerResults = getSearchResultsFromPeerGroupLeader( m_peerGroupLeaders.elementAt(i), depth-1, query); results += peerResults; } } } } /* now search my own file list */ synchronized (m_clientData) { Set clients = m_clientData.keySet(); for (String clientIP : clients) { HashMap files = m_clientData.get(clientIP).files; Set clientFiles = files.keySet(); for (String fileName : clientFiles) { String fnamelc = fileName.toLowerCase(); if ( (fnamelc.indexOf(querylc) >= 0) || (files.get(fileName).toLowerCase().indexOf(querylc) >= 0) ) { /* clientIPToReturn will hold our MNH server's opinion * of the client IP address which has the file being * searched for *unless* it starts with "127.", in which * case it is a local connection and the client performing * the search should receive back whichever IP it is using * to talk to the MNH server */ String clientIPToReturn = clientIP.startsWith("127.") ? clientSocket.getLocalAddress().getHostAddress() : clientIP; results += clientIPToReturn + "|" + m_clientData.get(clientIP).userName + "|" + m_clientData.get(clientIP).speed + "|" + fileName + "|" + files.get(fileName) + "\n"; } } } } synchronized (m_currentSearches) { m_currentSearches.remove(querylc); } return results; } /* helper method invoked to forward keyword search to peer group leader */ private String getSearchResultsFromPeerGroupLeader(Socket s, int depth, String query) { String searchResults = ""; try { BufferedReader br = new BufferedReader( new InputStreamReader( s.getInputStream())); DataOutputStream os = new DataOutputStream(s.getOutputStream()); os.writeBytes("SRCH " + depth + " " + query + "\n"); for (;;) { String inLine = br.readLine(); if (inLine.equals(".")) break; searchResults += inLine + "\n"; } } catch (Exception e) { return ""; } return searchResults; } }