Hey there folks,I've been takinga course in data communications, which for the most part has been completely over my head, but anyway, for a project I need to write an instant messenger program. Prof as very vague about the detais, so I think It can be as simple as possible, meaning just 2 clients contact the server and send strings back and forth. For the most part I have the client side writte, or at least I think I understand it more.

Earlier I wrote a program where the user enters data into several text fields (It's a loan calculator, so the user enters an amount, a length of time and an interest rate) which gets sent to the server, which does the calculations and sends the result back. I think this is a good place to start from, and the gui will be easier to construct, and the server doesn't need to do any calculations, but I'm not sure how to make the server sends the string it recieves to another client.

I guess it actually has to send it to both, so the sender has what they wrote in the text area.

In a nutshell, I need to know how to connect two clients to the same server, and have the server send the data it recieves from one client to the other.

I have very little talent for computer science, so laymen's terms are appreciated. Thanks in advance!

Recommended Answers

All 16 Replies

Well, I know how to use a threaded server, but I'm lost on how to make it send the data to both clients, or even to make it just send data from one client to the other, everything else I've done has been about a client and a server trading data, I've never had two clients communicating through a server. Can I do that with a threaded server? There is one article soon after that page about MulticastSocket, but judging from the sample code, I don't really see how it works. Does that mean that just changing my sockets to multicastsockets is sufficient?

If you know how to get data from a client to a server and you also know how to get data from a server to an(other) client, then you already know how to send data from one client to another via a server!

Unfortunately that's just the problem. I don't know how to send data from a server to a different client. I only know how to send it to the same client. But I know that if I could just figure that out, things would be pretty much done.

Do you know how to have 2 client/server sessions at the same time (same server, different clients)?

Well, I have a "ConcurrentServer" class from another project that uses threads. which, in theory anyway, is suppposed to let multiple clients use the same server.

I can send my code along if you think that'll be helpful.

Sorry, I don' have time to do any major work on this, but, briefly:
keep a list of the currently open client threads/server instances. Add a method to the server code that sends a message to the client ( public void sendMessage(String message); . When client 1 sends a message for client 2, get client 2's thread/server instance from the list and call its sendMessage method.
I'll try to get more time on this tomorrow. Good luck.

Der_Kaiser, if you post your code, I'll also take a look at it. But it's just as James says - once you succeed in connecting the same Server to two different Clients, it is only a matter of anytime you receive a message from the first Client, you use the Server to send that message to the second Client.

Ok, here it goes. The "IMServer" and "MyThread classes currently are not compiling due to a problem with the sendMessage method. I've been trying to implement a list for the instances of client/server interaction, but when I put them into the list it made them objcts, when I need them to be sockets. Not quite sure how I can fix that. I may be way off for all I know I've never really done anything like this before.

Here's the IMClient class

import java.awt.*;
   import java.awt.event.*;
   import java.io.*;
   import java.net.*; 
   import javax.swing.*;

    public class IMClient extends JPanel implements ActionListener{
   
   //Data Fields
      protected JTextArea textArea;
      protected JButton button;
      protected JPanel text;
      protected JTextField textField;
		public String sn;
		public int port;
      private final static String newline = "\n";
   
   
   
       public IMClient(String sName, int portNum) {
         super(new GridBagLayout());
			sn = sName;
         port = portNum;
      	textArea = new JTextArea(7, 30);
         textArea.setEditable(false);
         JScrollPane scrollPane = new JScrollPane(textArea);
      
      //Top line, Instructions and button
         text = new JPanel();
         text.setLayout(new BoxLayout(text, BoxLayout.LINE_AXIS));
         textField = new JTextField();
         button = new JButton("Send");
         text.add(textField);
         text.add(button);
      
      
      
      //Add Components to this panel.
         GridBagConstraints c = new GridBagConstraints();
         c.gridwidth = GridBagConstraints.REMAINDER;
         
         c.fill = GridBagConstraints.BOTH;
         c.weightx = 1.0;
         c.weighty = 1.0;
         add(scrollPane, c);
         
         c.fill = GridBagConstraints.HORIZONTAL;
         add(text, c);
      
      }
    
    
       public void actionPerformed(ActionEvent evt){
         String s = "";
         int length = 0;
         String str = "";
      
      
         try{
         	s = textField.getText();
         }
         
             catch (Exception e){
               textArea.append("Error: you must enter text" + newline);
            }
      
         try{
            Socket socket = new Socket("brastias.cs.geneseo.edu",port);
            PrintWriter writer = new PrintWriter(socket.getOutputStream());
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
				
            writer.println(s);
            str = sn+ ": " +str+reader.readLine();
         	
            socket.close();
         }
         
             catch (Exception e){
               textArea.append("Error: Cannot connect" + newline);
            }
      
      }		
   
   
   	
   	
   
   //Create the GUI
       public static void createAndShowGUI(String screenName, int portNumber){
      //Create and set defaults for JFrame
			int portNum = portNumber;
      	String sName = screenName;
         JFrame frame = new JFrame("Client");
         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         frame.setLocation(200,200);
      
      //Add text box to JFrame
         frame.add(new IMClient(sName, portNum));
      
      //Display the window.
         frame.pack();
         frame.setVisible(true);
      } 
   
   
       public static void main(String args[]){
		 	final String screenName = JOptionPane.showInputDialog(null,"Please Enter your name");
			final int portNumber  =8000;
      
         javax.swing.SwingUtilities.invokeLater(
                new Runnable(){
                   public void run(){
                     createAndShowGUI(screenName, portNumber);
                  }
               });
      } 
   }

The IMServer class

import java.io.*;
import java.net.*;
import java.util.*;

class IMServer{
	
	
	public static void main(String[] args) throws Exception{
	
		ServerSocket serverSocket = new ServerSocket(8000);
		LinkedList list = new LinkedList();
		
		while(true){
						
			Socket socket = serverSocket.accept();
			list.add(socket);
			
			if(list.size() == 2){
				MyThread thread = new MyThread(socket, list);
				thread.run();
			}
		}
	}
	
	public static void sendMessage(String message, Socket socket, LinkedList list){
		try{
			for(int i=0; i<2; i++){
				Socket s = list.get(i);
				PrintWriter write = new PrintWriter(s.getOutputStream(), true);
				write.println(message);
			}
		}
		catch (Exception e){}
	}
}

and the MyThread class, which gets implemented by IMServer:

import java.io.*;
import java.net.*;
import java.util.*;

class MyThread extends Thread{

	protected Socket socket;
	protected LinkedList list;
	public MyThread(Socket socket, LinkedList list){
	
		this.socket = socket;
		this.list = list;
	}
	public void run(){
	
		try{
			String received;
			
			BufferedReader read = new BufferedReader(new InputStreamReader(socket.getInputStream()));
				
			received = read.readLine();
			IMServer.sendMessage(received, socket, list);
		
		}
		catch(IOException ex){}
	}
}

I appreciate all the help guys. I just hope I'm going in the right direction here. If the code seems indecipherable, I'm probably not. Also, I'm really not caring at all about the efficiency of the code, which probably makes it harder to follow, so I hope that's not a problem.

OK, here we go
You're heading in the right direction, so don't worry.
First thing to do is to restructure the way that connections are made a bit. Basically you need to open a socket connection when the client connects, then keep it open until the client logs off. Right now you make a new connection for every message, which leaves no way for the server to send a message to the client except as a reply. It's not a very big change. At the client make the connection then sit in a loop waiting for and processing inbound messages; when the client wants to send a message just send it via the (already open) output stream.
Similarly, at the server end, when you get a connection keep it open and sit in a loop waiting for and processing messages from the client. When the server wants to send a message it can just use the already open stream.
The threading part is also very near to being right. Think of your MyThread class as being the object that handles a client (I would rename it ConnectedClient or something). When the server gets an accept() create an instance of that class and start it, just like you try to do now for a second connection, but do it for all connections. It's inside the ConnectedClient class that you have the loop waiting for messages, and where you can also send a message (because you still have the open socket and streams).
Finally, to keep track of the ConnectedClient instances I would have a static Hashtable<String, ConnectedClient> connectedClients . (static because all the instances then have shared access to it.) I would set up the Hashtable with the client's name (login ID) as key, and the instance of ConnectedClient as the value. That way you can send a message to user "Fred", provided he is connected, by calling connectedClients.get("Fred").sendMessage("Hello Fred");

Alright, I think I almost have it. Here is my modified ConnectedClient class:

import java.io.*;
   import java.net.*;
   import java.util.*;

    class ConnectedClient extends Thread{
   
      static Hashtable<String, ConnectedClient> connectedClients;
      protected Socket socket;
      
       public ConnectedClient(Socket socket){
      
         this.socket = socket;
      }
       public void run(){
      
         try{
            String received;
            String sn;
         
            BufferedReader read = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            sn = read.readLine();
            connectedClients.put(sn, this);
            while(true){	
               received = read.readLine();
               connectedClients.get(sn).sendMessage(received);
            }
         }
             catch(IOException ex){}
      }
        
       public static void sendMessage(String message){
         try{
            PrintWriter write = new PrintWriter(socket.getOutputStream(), true);
            write.println(message);
         }
             catch (Exception e){}
      }
   }

The only problems now are that I can't figure out how to get the other instance out of the hashtable, since I don't know what the second screenname will necessarily be, and "socket" is used wrong in the outpt stream but I'm not quite sure what to do there. I added another line in the client that sends the the login ID first, so that will be the first thing that this class receives.

Ok, this is probably an extremely roundabout way of doing this, but It's the only way I could think to do it, by obtaining the keySet of the hashtable, putting that into a string array, and using those to access the different login IDs. For some reason, the toArray method was still returning an "Object" type rather than strings even though the set is declared as a set of strings. Oh well, it was just one more step.

import java.io.*;
   import java.net.*;
   import java.util.*;

    class ConnectedClient extends Thread{
   
      static Hashtable<String, ConnectedClient> connectedClients;
      protected Socket socket;
      
       public ConnectedClient(Socket socket){
      
         this.socket = socket;
      }
       public void run(){
      
         try{
            String received;
            String sn;
         
            BufferedReader read = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            sn = read.readLine();
            connectedClients.put(sn, this);
            while(true){	
               received = read.readLine();
					Set<String> set = connectedClients.keySet();
					String[] names = new String[2];
					Object[] o = new Object[2];
					o = set.toArray();
					names[0] = o[0].toString();
					names[1] = o[1].toString();
               connectedClients.get(names[0]).sendMessage(received, socket);
					connectedClients.get(names[1]).sendMessage(received, socket);
            }
         }
             catch(IOException ex){}
      }
        
       public static void sendMessage(String message, Socket socket){
         try{
				Socket s = socket;
            PrintWriter write = new PrintWriter(s.getOutputStream(), true);
            write.println(message);
         }
             catch (Exception e){}
      }
   }

I think (or hope) that that should work, or at least it makes some kind of sense to me.

Sorry for not giving as much help as I'd indicated I would - its finals time right now, so I'm busier than normal. As far as the toArray method, a lot of times, methods are declared as returning Objects, but that can be casted to whatever type you're using. You have to be careful, but if what you added was a String, the return type of Object can be casted to String. Also - are you having any problems with your program now? It seems like you haven't tried it out.

Hi there. When I suggested the name/ConnectedClient Hashtable, I was thinking on to the scenario of multiple clients, and wanting to forward a message from one of them to just one of the others - hence the need to give each of them a login name by which they can be identified. If all you need now (and later?) is to send the message to all connected clients, you can simplify the Hashtable to a simple ArrayList or Vector containing the ConnectedClients, and just loop thru that calling the send method for each client.

It looks like there's a glitch in your sendMessage. When you call it you pass in the socket - which is the socket of the client who sent the message! You don't want the socket parameter; the instance implementing the sendMessage method should use it's own socket.

commented: More good points +5
public static void sendMessage(String message){
try{
PrintWriter write = new PrintWriter(socket.getOutputStream(), true);
write.println(message);
}
catch (Exception e){}
}

Der_Kaiser. . what James is suggesting would make your code look like what I've posted above (Hope I'm not stepping on your toes here James). Then you'd just need to have a loop iterating over the ConnectedClients, where each client calls the sendMessage method. That way you're using the socket of the current client when you call sendMessage.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.