I am trying to implement a multithreaded server chat application in Java.
This program created a thread and waits for a client to connect. Once a client is connected, it creates another thread and waits for another client to connect.
This is my ChatServer.java
package com.chat.server;
import java.io.InputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.InetAddress;
import java.net.ServerSocket;
/**
* <p>The chat server program</p>
* This class is a Thread that recieves connection
* from different clients and handles them is separate
* Thread.
*
* #author Aditya R.Singh
* #version 1.0.0
* #since 1.0.0
*/
class ChatServer extends Thread {
private int port; // The port number to listen at.
private String ip; // To store the IP address.
private Socket socket; // Socket connection with different clients.
private InetAddress inet; // Handling Client Address.
private ServerSocket serverSocket; // The server socket used by clients to connect.
/**
* This is solely intended for instantiation purpose.
*
* #param PORT - The port number to listen for client requests
*/
ChatServer(final int PORT) {
/* Initiallizing all instance variables to null. */
ip = null;
inet = null;
socket = null;
serverSocket = null;
/* Initiallizing the port number. */
port = PORT;
}
/**
* This method creates a connection between server and client.
*
* #throws java.io.IOException
*/
private void createConnection() throws IOException {
serverSocket = new ServerSocket(port); // Listen to the required port.
socket = serverSocket.accept(); // Accept the client connection.
}
/**
* This method sets the IP address.
*/
private void setIP() {
inet = socket.getInetAddress();
ip = new String(inet.getHostAddress());
}
/**
* This method returns the IP address.
*
* #return IP address.
*/
public String getIP() {
return ip;
}
/**
* This method checks if the socket has been connected
* with any client.
*
* #return True if the client has been connected, else false
*/
public boolean isConnected() {
if(socket == null)
return false;
return true;
}
/**
* This method returns the InputStream
* from the Socket.
*
* #return InputStream if Socket has been connected to the client, else null
* #see java.io.InputStream
*/
public InputStream getInputStream() throws IOException {
if(socket == null)
return null;
return socket.getInputStream();
}
#Override
public void run() {
try {
createConnection();
setIP();
} catch(IOException exception) {
exception.printStackTrace();
}
}
}
And this is my Server.java:
package com.chat.server;
/**
* <p>The Server app</p>
* This is the controller for accepting connections.
*
* #author Aditya R.Singh
* #version 1.0.0
* #since 1.0.0
*/
public class Server {
/**
* The port at which clients will connect.
*/
public static final int PORT = 6005;
/**
* For instantiation purpose.
*/
public Server() {
}
public static void main(String[] args) {
/* Keep accepting connections. */
while(true) {
ChatServer chat = new ChatServer(PORT); // Connecting port.
chat.start();
while(!chat.isConnected())
/* This is a false loop. Intended to keep running unless another client is not requesting to connect. */;
System.out.println("We connected to: "+chat.getIP());
}
}
}
The code compiles fine.
On running the code as:
java com.chat.server.Server
it seems that the program is listening for a client to connect. But after it connects to a client, it is expected to print the IP address of the client and then create another thread for another client. But it doesn't print the IP of the client.
This is my Client.java:
package com.chat.client;
import java.net.Socket;
import java.io.IOException;
public class Client {
public static void main(String[] args) {
Socket socket = null;
try {
socket = new Socket("127.0.0.1", 6005);
System.out.println("Socket connected.");
} catch(IOException ex) {
ex.printStackTrace();
}
}
}
The client ones connects to the server, must print Socket connected. The client does that. The client works fine:
java com.chat.client.Client
Socket connected.
But the server app doesn't print the IP address of the client. Why so?
This is not complete code
package demo;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class MultithreadedServer {
public static final int PORT = 10000;
public static void main(String[] args) {
try(ServerSocket server = new ServerSocket(PORT)) {
while(true){
Socket connection = server.accept();
Thread client = new ClientThread(connection);
client.start();
}
} catch (IOException ex) {
System.out.println("Error start server");
}
}
}
class ClientThread extends Thread {
private Socket connection;
public ClientThread(Socket connection) {
this.connection = connection;
}
#Override
public void run(){
//Do communication with client
}
}
It's a race condition. The line socket = serverSocket.accept(); causes the while(!chat.isConnected()) loop to terminate before the method 'setIP()' is been called. A quick way to verify that this is the cause of the problem is by changing this method:
public boolean isConnected() {
if(socket == null)
return false;
return true;
}
to
public boolean isConnected() {
if(socket == null || ip == null)
return false;
return true;
}
In order to fix the problem, you should make sure that the code that sets the IP and the code that checks whether it's connected use the synchronized keyword. Also, notice that the while(!chat.isConnected()) loop runs with no pauses, meaning that it'd take as much CPU as it's available... which is definitely not good.
Check out the link that #Michael Petch posted for a proper implementation of a chat server.
Related
I'm creating a chat server using Socket Servers and Thread. I have 3 Classes in my Server ChatServer.java, ThreadedServer.java, and Main.java. My Client has 2 ChatClient.java, Main.java. I need to make it so when a message is sent in from a client to server, the server sends out the message to all Clients.
ChatServer.java
package server;
import java.io.*;
import java.net.*;
public class ChatServer {
protected Socket s;
protected Socket ss;
public ChatServer() {
try {
ServerSocket ss=new ServerSocket(6969);
Runtime.getRuntime().addShutdownHook(new Thread() {
public void main(String []args) throws IOException {
ss.close();
}
});
while (true) {
Socket s =ss.accept();
new ThreadedServer(s).start();
}
}catch(Exception e) {
System.out.println(e);
}
new ThreadedServer(s).start();
}
}
ThreadedServer.java
package server;
import java.io.*;
import java.net.*;
public class ThreadedServer extends Thread{
protected Socket socket;
public ThreadedServer(Socket clientSocket) {
this.socket = clientSocket;
}
public void run() {
try {
DataInputStream dis = new DataInputStream(socket.getInputStream());
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
while (true) {
String str=(String)dis.readUTF();
String remote = socket.getInetAddress().toString();
String newmes = remote + ": " + str;
dos.writeUTF(newmes);
System.out.println(newmes);
if (str.toUpperCase() == "QUIT") {
socket.close();
break;
}else if (str.toUpperCase() == "EXIT") {
socket.close();
break;
}
}
}catch(Exception e) {
System.out.println(e);
}
}
}
Main.java (Server)
package server;
public class Main {
public static void main(String[] args) {
ChatServer chat = new ChatServer();
}
}
Here are the Client.java's
ChatClient.java
package client;
import java.io.*;
import java.net.*;
public class ChatClient {
public ChatClient() {
try {
Socket s = new Socket("10.4.27.29",6969);
Runtime.getRuntime().addShutdownHook(new Thread() {
public void main(String []args) throws IOException {
s.close();
}
});
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
DataInputStream dis = new DataInputStream(s.getInputStream());
while (true) {
String message = System.console().readLine();
dos.writeUTF(message);
System.out.println(dis.readUTF());
}
}catch(Exception e) {
System.out.println(e);
}
}
}
Main.java (Client)
package client;
public class Main {
public static void main(String[] args) {
ChatClient chat = new ChatClient();
}
}
Please any help would be amazing.
Also any ideas to make this nicer would be appreciated. Thanks!!
Try running this. This is one I made that works.
ChatServer.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashSet;
/**
* A multithreaded chat room server. When a client connects the
* server requests a screen name by sending the client the
* text "SUBMITNAME", and keeps requesting a name until
* a unique one is received. After a client submits a unique
* name, the server acknowledges with "NAMEACCEPTED". Then
* all messages from that client will be broadcast to all other
* clients that have submitted a unique screen name. The
* broadcast messages are prefixed with "MESSAGE ".
*
* Because this is just a teaching example to illustrate a simple
* chat server, there are a few features that have been left out.
* Two are very useful and belong in production code:
*
* 1. The protocol should be enhanced so that the client can
* send clean disconnect messages to the server.
*
* 2. The server should do some logging.
*/
public class ChatServer {
/**
* The port that the server listens on.
*/
private static final int PORT = 9001;
/**
* The set of all names of clients in the chat room. Maintained
* so that we can check that new clients are not registering name
* already in use.
*/
private static HashSet<String> names = new HashSet<String>();
/**
* The set of all the print writers for all the clients. This
* set is kept so we can easily broadcast messages.
*/
private static HashSet<PrintWriter> writers = new HashSet<PrintWriter>();
/**
* The appplication main method, which just listens on a port and
* spawns handler threads.
*/
public static void main(String[] args) throws Exception {
System.out.println("The chat server is running.");
ServerSocket listener = new ServerSocket(PORT);
try {
while (true) {
new Handler(listener.accept()).start();
}
} finally {
listener.close();
}
}
/**
* A handler thread class. Handlers are spawned from the listening
* loop and are responsible for a dealing with a single client
* and broadcasting its messages.
*/
private static class Handler extends Thread {
private String name;
private Socket socket;
private BufferedReader in;
private PrintWriter out;
/**
* Constructs a handler thread, squirreling away the socket.
* All the interesting work is done in the run method.
*/
public Handler(Socket socket) {
this.socket = socket;
}
/**
* Services this thread's client by repeatedly requesting a
* screen name until a unique one has been submitted, then
* acknowledges the name and registers the output stream for
* the client in a global set, then repeatedly gets inputs and
* broadcasts them.
*/
public void run() {
try {
// Create character streams for the socket.
in = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
// Request a name from this client. Keep requesting until
// a name is submitted that is not already used. Note that
// checking for the existence of a name and adding the name
// must be done while locking the set of names.
while (true) {
out.println("SUBMITNAME");
name = in.readLine();
if (name == null) {
return;
}
synchronized (names) {
if (!names.contains(name)) {
names.add(name);
break;
}
}
}
// Now that a successful name has been chosen, add the
// socket's print writer to the set of all writers so
// this client can receive broadcast messages.
out.println("NAMEACCEPTED");
writers.add(out);
// Accept messages from this client and broadcast them.
// Ignore other clients that cannot be broadcasted to.
while (true) {
String input = in.readLine();
if (input == null) {
return;
}
for (PrintWriter writer : writers) {
writer.println("MESSAGE " + name + ": " + input);
}
}
} catch (IOException e) {
System.out.println(e);
} finally {
// This client is going down! Remove its name and its print
// writer from the sets, and close its socket.
if (name != null) {
names.remove(name);
}
if (out != null) {
writers.remove(out);
}
try {
socket.close();
} catch (IOException e) {
}
}
}
}
}
ChatClient.java
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
/**
* A simple Swing-based client for the chat server. Graphically
* it is a frame with a text field for entering messages and a
* textarea to see the whole dialog.
*
* The client follows the Chat Protocol which is as follows.
* When the server sends "SUBMITNAME" the client replies with the
* desired screen name. The server will keep sending "SUBMITNAME"
* requests as long as the client submits screen names that are
* already in use. When the server sends a line beginning
* with "NAMEACCEPTED" the client is now allowed to start
* sending the server arbitrary strings to be broadcast to all
* chatters connected to the server. When the server sends a
* line beginning with "MESSAGE " then all characters following
* this string should be displayed in its message area.
*/
public class ChatClient {
BufferedReader in;
PrintWriter out;
JFrame frame = new JFrame("Chatter");
JTextField textField = new JTextField(40);
JTextArea messageArea = new JTextArea(8, 40);
/**
* Constructs the client by laying out the GUI and registering a
* listener with the textfield so that pressing Return in the
* listener sends the textfield contents to the server. Note
* however that the textfield is initially NOT editable, and
* only becomes editable AFTER the client receives the NAMEACCEPTED
* message from the server.
*/
public ChatClient() {
// Layout GUI
textField.setEditable(false);
messageArea.setEditable(false);
frame.getContentPane().add(textField, "North");
frame.getContentPane().add(new JScrollPane(messageArea), "Center");
frame.pack();
// Add Listeners
textField.addActionListener(new ActionListener() {
/**
* Responds to pressing the enter key in the textfield by sending
* the contents of the text field to the server. Then clear
* the text area in preparation for the next message.
*/
public void actionPerformed(ActionEvent e) {
out.println(textField.getText());
textField.setText("");
}
});
}
/**
* Prompt for and return the address of the server.
*/
private String getServerAddress() {
return JOptionPane.showInputDialog(
frame,
"Enter IP Address of the Server:",
"Welcome to the Chatter",
JOptionPane.QUESTION_MESSAGE);
}
/**
* Prompt for and return the desired screen name.
*/
private String getName() {
return JOptionPane.showInputDialog(
frame,
"Choose a screen name:",
"Screen name selection",
JOptionPane.PLAIN_MESSAGE);
}
/**
* Connects to the server then enters the processing loop.
*/
private void run() throws IOException {
// Make connection and initialize streams
String serverAddress = getServerAddress();
Socket socket = new Socket(serverAddress, 9001);
in = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
// Process all messages from server, according to the protocol.
while (true) {
String line = in.readLine();
if (line.startsWith("SUBMITNAME")) {
out.println(getName());
} else if (line.startsWith("NAMEACCEPTED")) {
textField.setEditable(true);
} else if (line.startsWith("MESSAGE")) {
messageArea.append(line.substring(8) + "\n");
}
}
}
/**
* Runs the client as an application with a closeable frame.
*/
public static void main(String[] args) throws Exception {
ChatClient client = new ChatClient();
client.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
client.frame.setVisible(true);
client.run();
}
}
I'm facing a strange problem doing java sockets project. Here's my code:
Server:
package second.sockets;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class Server implements Runnable {
public static String HOST = "localhost";
public static int PORT = 1234;
private static final int MAX_USERS = 2;
private static final Server serverInstance = new Server();
private ServerSocket serverSocket;
private List<User> users = new ArrayList<>();
private Server() {
try {
if( this.serverSocket == null ) {
this.serverSocket = new ServerSocket(Server.PORT);
}
} catch(IOException e) {
e.printStackTrace();
System.err.println("could not initialize ServerSocket on port="+Server.PORT +
"["+ e.getMessage() +"]");
}
}
public static Server getInstance() {
return Server.serverInstance;
}
#Override
public void run() {
System.out.println("waiting for incoming connections...");
try {
while( !Thread.interrupted() ) {
this.waitForFreeSlots();
Socket newSocket=null;
try {
newSocket = this.serverSocket.accept();
System.out.println("new connection " + newSocket);
} catch(IOException e) {
System.err.println("could not connect");
}
}
} catch(InterruptedException e) {
System.err.println("server interrupted");
}
}
private synchronized void waitForFreeSlots() throws InterruptedException {
while( this.users.size() >= Server.MAX_USERS ) {
this.wait();
}
}
public static int getPORT() {
return PORT;
}
public static void main(String[] args) {
Server server = Server.getInstance();
Thread serverThread = new Thread(server);
serverThread.start();
}
}
Client:
package second.sockets;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
public class Client {
public static void main(String[] args) {
Socket socket=null;
try {
socket = new Socket(Server.HOST, Server.PORT);
} catch(UnknownHostException uhe) {
System.err.println("could not connect to "+Server.HOST+" on port "+Server.PORT+", no such host");
} catch(IOException ioe) {
System.err.println("could not connect to "+Server.HOST + " on port "+Server.PORT);
}
System.out.println(socket);
}
}
I run the server and it works, but when I run a Client I get could not initialize ServerSocket on port=1234[Address already in use: JVM_Bind] and it is from Server code from line 25. It's odd as Server is a singleton so there's no place for more than one instance of the class. What is more I don't even touch it in Client main function. I don't get it, any ideas where the problem is? Thanks.
EDIT:
Here's the stack trace. It comes from Server's constructor although it is private and it shows after running the Client.
java.net.BindException: Address already in use: JVM_Bind
at java.net.DualStackPlainSocketImpl.bind0(Native Method)
at java.net.DualStackPlainSocketImpl.socketBind(DualStackPlainSocketImpl.java:106)
at java.net.AbstractPlainSocketImpl.bind(AbstractPlainSocketImpl.java:382)
at java.net.PlainSocketImpl.bind(PlainSocketImpl.java:190)
at java.net.ServerSocket.bind(ServerSocket.java:375)
at java.net.ServerSocket.<init>(ServerSocket.java:237)
at java.net.ServerSocket.<init>(ServerSocket.java:128)
at second.sockets.Server.<init>(Server.java:22)
at second.sockets.Server.<clinit>(Server.java:15)
at second.sockets.Client.main(Client.java:12)
could not initialize ServerSocket on port=21234[Address already in use: JVM_Bind]
Socket[addr=localhost/127.0.0.1,port=21234,localport=53054]
I don't get this transition:
at second.sockets.Client.main(Client.java:12)->at second.sockets.Server.<clinit>(Server.java:15)
The stuff you see in the exception comes from the fact that you are using Server.HOST and Server.PORT in your client code. This calls the class initializer for the Server class. In this initializer all the static stuff in the Server class is created. And I see you have a static Server instance there...so you have in fact several Server instances created.
So avoid using Server.HOST and Server.PORT in the client code and use the real valus directly and it will work.
Normally when you want to share stuff between 2 code entities, move them to a third entity to decouple the server and client. Then let server and client use that new entity.
Okay, I figured out what was wrong. In Client in this line socket = new Socket(Server.HOST, Server.PORT); I was using 2 variables from class Server. When I deleted it and pasted raw values or cahnge the variables to be final, it started to work. This is some wild magic.
I trying to create a TCP server using NIO(Selector based) which can check whether any client is idle for more than 5 minutes.
I had been using time out on read operations using blocking IO, but there is no such option is provided in NIO.
Is there any efficient way to achieve this?
I achieved this by periodically checking idle clients.
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.log4j.Logger;
/**
* {#link NIOServer} class is used to create a Non-Blocking TCP server listening
* on the port specified in the constructor parameter and listens for clients
* sending data.
*
* #author AchuthaRanga.Chenna
*
*/
public class NIOServer implements Runnable {
private Logger logger = Logger.getLogger(this.getClass().getName());
private InetAddress hostAddress;
private int port;
private ServerSocketChannel serverSocketChannel;
private Selector selector;
private boolean runServer = true;
private ConcurrentHashMap<SocketChannel, Long> clients = new ConcurrentHashMap<SocketChannel, Long>();
/**
* Constructor to pass the host and port of server.
*
* #param hostAddress
* address on which server has to be run.
* #param port
* port of the server.
**/
public NIOServer(InetAddress hostAddress, int port) throws IOException {
this.hostAddress = hostAddress;
this.port = port;
this.selector = initSelector();
IdleSocketChecker isc = new IdleSocketChecker();
new Thread(isc).start();
}
/**
* Method to create a ServerSocket and register to selector with option
* OP_ACCEPT to accept connections from clients.
*
* #return Selector registered with a serverSocket listening on a port to
* accept connections.
* #throws IOException
* on fail to create a selector or bind the server to the
* address.
*/
private Selector initSelector() throws IOException {
Selector socketSelector = SelectorProvider.provider().openSelector();
this.serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
InetSocketAddress isa = new InetSocketAddress(this.hostAddress, this.port);
serverSocketChannel.socket().setReuseAddress(true);
serverSocketChannel.socket().bind(isa);
serverSocketChannel.register(socketSelector, SelectionKey.OP_ACCEPT);
return socketSelector;
}
/**
* Method with a infinite loop to iterate over the selected keys of the
* selector to know the option interested of the client sockets connected.
* Loop breaks when on IOException occurs on the selector which is listening
* for IOOperations of the client.
**/
#Override
public void run() {
while (runServer) {
try {
/*
* ".select" is a blocking call which invokes when any channel
* registered with the selector has an I/O operation to be done.
*/
this.selector.select();
/*
* Get a Iterator of the channels having I/O event to be
* handled.
*/
Iterator<SelectionKey> selectedKeys = this.selector.selectedKeys().iterator();
/* Iterate over the selected keys having I/O event to be done. */
while (selectedKeys.hasNext()) {
SelectionKey key = (SelectionKey) selectedKeys.next();
/* Remove the key to avoid infinite loop. */
selectedKeys.remove();
try {
if (!key.isValid()) {
continue;
}
if (key.isAcceptable()) {
this.accept(key);
} else if (key.isReadable()) {
this.read(key);
}
} catch (CancelledKeyException e) {// key has been canceled
}
}
/* Exception is generated when the Selector fails. */
/*
* Close the server and return from the while loop when Selector
* fails.
*/
} catch (IOException e) {
logger.error("[run] Exception on generation of client event", e);
try {
serverSocketChannel.close();
selector.close();
} catch (IOException e1) {
logger.error("[run] Exception on shutting down NIOSERVER due to selector is closed", e1);
}
break;
}
}
logger.info("[NIOSERVER thread closed normally]");
/* Clean up the resources */
this.releaseResources();
}
/**
* Method to release the resources used to create NIOSERVER SOCKET.
*/
public void releaseResources() {
try {
this.serverSocketChannel.close();
} catch (IOException e) {
logger.warn("[run]", e);
}
try {
this.selector.close();
} catch (IOException e) {
logger.warn("[run]", e);
}
}
/**
* Method to return socket status.
*
* #return
*/
public boolean isConencted() {
boolean status = false;
try {
status = serverSocketChannel.isOpen();
} catch (Exception e) {
}
return status;
}
/**
* Utility method to stop the server thread.
*
* #param runServer
* Flag decides to stop Server
*/
public void shutDown() {
this.runServer = false;
logger.info("[shutDown] Server is stopped");
}
/**
* Method to accept connections from clients and registering for reading
* data from clients.Set's a KeepAlive option of the socket true and
* register the connected socket for READ option.
*
* #param key
* which is ready to acceptable
*/
private void accept(SelectionKey key) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
try {
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel != null) {
/* Set the KeepAlive flag to avoid continuous open of files */
socketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
socketChannel.configureBlocking(false);
/* Register the client connected with our interested Option Read */
socketChannel.register(this.selector, SelectionKey.OP_READ);
// key.attach(System.currentTimeMillis());
clients.put(socketChannel, System.currentTimeMillis());
logger.debug("[accept] New Client connected from " + socketChannel.getRemoteAddress());
logger.info("[accept] Total connected : " + clients.size());
// System.out.println(socketChannel.hashCode());
} else {
key.cancel();
}
} catch (IOException e) {
key.cancel();
logger.error("[accept] Error while accepting new connectins", e);
}
}
/**
* * Method to read data from key having read event pending.
*
* #param key
* SelectionKey having read event.
*/
private void read(SelectionKey key) {
SocketChannel socketChannel = (SocketChannel) key.channel();
synchronized (socketChannel) {
if (socketChannel.isOpen()) {
try {
ByteBuffer readBuffer = ByteBuffer.allocate(150);
readBuffer.clear();
int numRead = 0;
try {
/* ".read" is nonblocking */
numRead = socketChannel.read(readBuffer);
/*
* Some other IO error on reading socketChannel.
*/
} catch (IOException e) {
logger.debug("[run] Connection abruptly terminated from client", e);
key.channel().close();
clients.remove(socketChannel);
return;
}
if (numRead == -1) {// socket closed cleanly
key.channel().close();
clients.remove(socketChannel);
return;
}
String data = null;
data = new String(readBuffer.array(), Charset.forName("ASCII"));
logger.info(data);
/* Send the read data to the DataDispatcher Actor */
clients.put(socketChannel, System.currentTimeMillis());
} catch (IOException e) {
logger.debug("[run] ", e);
return;
}
} else {// socketChannel is closed
try {
key.channel().close();// Sanitary close operation
clients.remove(key);
return;
} catch (IOException e) {
}
}
}
logger.info("[checkIdleSockets] Total connected : " + clients.size());
}
/**
* Method to check the sockets idle for 15 minutes and close the socket.
*/
private void checkIdleSockets() {
// synchronized (clients) {
Iterator<Entry<SocketChannel, Long>> iter = clients.entrySet().iterator();
while (iter.hasNext()) {
try {
Map.Entry<SocketChannel, Long> entry = iter.next();
SocketChannel client = entry.getKey();
long mills = entry.getValue();
double minutes = (System.currentTimeMillis() - mills) / (double) (1000 * 60);
if (minutes > 5) {
/* key is idle for */
logger.info("[IdleSocketChecker] Socket is idle for " + Math.round(minutes) + ", closing......");
try {
client.close();
} catch (IOException e) {
} finally {
iter.remove();
}
}
} catch (Exception e) {
logger.info("[IdleSocketChecker] ", e);
}
}
// }
logger.info("[checkIdleSockets] Total connected : " + clients.size());
}
/**
* {#link IdleSocketChecker} is a thread to check for any idle sockets in
* the selector.
*
* #author AchuthaRanga.Chenna
*
*/
private class IdleSocketChecker implements Runnable {
private boolean RUN = true;
#Override
public void run() {
try {
while (RUN) {
/* Wait for 5 Minutes */
Thread.sleep(5 * 60 * 1000);
checkIdleSockets();
}
} catch (InterruptedException e) {
logger.warn("[IdleSocketChecker]<run> IdleSocketChecker thread stopped", e);
}
}
}
}
I watched a tutorial on simple Java networking, and the tutorial showed the server and client application running on the same computer and it worked, I was wondering if there's a way to make it work on different computers in different homes using port forwarding or something else; Here is my code:
Server.java:
package com.cloud.server;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
/**
* The Server class extends JFrame and contains all of the code pertaining to the GUI and the server
*
* #author mcjcloud
*
*/
public class Server extends JFrame
{
private JTextField userInput;
private JTextArea convo;
private ObjectOutputStream output;
private ObjectInputStream input;
private ServerSocket server; // establishes server
private Socket connection; // establishes connection with other computer
/**
* Constructor (basically just sets up the GUI and actionListener(s)
*/
public Server()
{
super("Cloud Messenger");
userInput = new JTextField();
userInput.setEditable(false);
userInput.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
sendMessage(e.getActionCommand());
userInput.setText("");
}
});
add(userInput, BorderLayout.SOUTH);
// set up convo (JTextArea)
convo = new JTextArea();
add(new JScrollPane(convo));
setSize(500, 700);
setLocationRelativeTo(null);
setVisible(true);
}
/**
* startServer() method waits for connection, sets up connection and manages chat
*/
public void startServer()
{
try
{
server = new ServerSocket(6789, 100, InetAddress.getByName("0.0.0.0")); // (port number, backlog) backlog (aka qlength) = "how many people can connect at a time"
while(true) // infinite loop
{
try
{
waitForConnection(); // first set up the connection
setupStreams(); // set up the streams
chat(); // enable the chat and things
}
catch(EOFException eofe) // EOF = EndOfStream (meaning the input/output stream ended)
{
showMessage("Connection terminated.");
}
finally
{
cleanUpConnection();
}
}
}
catch(IOException io)
{
io.printStackTrace();
}
}
/**
* waitForConnection() method will wait for the connection, then display connection info
*
* #throws IOException
*/
private void waitForConnection() throws IOException
{
showMessage("Waiting for connection...");
connection = server.accept(); // listens for a connection
showMessage("Now connected to " + connection.getInetAddress().getHostName());
}
/**
* setupStream() method gets a stream to send/recieve data
*/
private void setupStreams() throws IOException
{
// setup output stream
output = new ObjectOutputStream(connection.getOutputStream()); // create pathway to allow us to connect to the computer the socket is connected to
output.flush();
// setup input stream
input = new ObjectInputStream(connection.getInputStream()); // create pathway to receive messages
showMessage("Stream setup success.");
}
/**
* chat() method code runs during conversation
*/
private void chat() throws IOException
{
String message = "Chatting enabled";
showMessage(message);
setCanType(true);
do
{
try
{
message = (String) input.readObject();
showMessage(message);
}
catch(ClassNotFoundException cnfe)
{
showMessage("Message recieve failed (Other person's problem)");
}
}
while(!message.equals("CLIENT - /terminate"));
}
/**
* cleanUpConnection() method cleans up the stream and things after the chat has ended
*/
private void cleanUpConnection()
{
showMessage("Closing connection...");
setCanType(false);
try
{
output.close();
input.close();
connection.close();
}
catch(IOException io)
{
io.printStackTrace();
}
}
/**
* sendMessage(String) method sends whatever message you type
*
* #param message is what is going to be shown
*/
private void sendMessage(String message)
{
try
{
output.writeObject("SERVER - " + message); // write the message to the outputstream
output.flush();
showMessage("SERVER - " + message);
}
catch(IOException io)
{
convo.append("ERROR: Message can't be sent.");
}
}
/**
* showMessage(String)shows whatever needs to be shown on the JTextArea
*/
private void showMessage(String message)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
convo.append(" " + message + "\n");
}
});
}
/**
* setCanType() method decides whether or not a user can type
*
* #param canType
*/
private void setCanType(boolean canType)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
userInput.setEditable(canType);
}
});
}
}
InvokeServer.java:
package com.cloud.server;
import javax.swing.JFrame;
public class InvokeServer
{
public static void main(String[] args)
{
Server server = new Server();
server.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
server.startServer();
}
}
Client.java:
package com.cloud.client;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.Socket;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
/**
* Client class is the sister of the Server class in the server application, contains all the code to build GUI and send info to server and receive
*
* #author mcjcloud
*
*/
public class Client extends JFrame
{
private JTextField userInput;
private JTextArea convo;
private ObjectOutputStream output;
private ObjectInputStream input;
private String message = "";
private String serverIP; // connecting to a specific server
private Socket connection;
public Client(String host)
{
super("Cloud Messenger");
serverIP = host;
userInput = new JTextField();
userInput.setEditable(false);
userInput.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
sendMessage(e.getActionCommand());
userInput.setText("");
}
});
add(userInput, BorderLayout.SOUTH);
convo = new JTextArea();
add(new JScrollPane(convo));
setSize(500, 700);
setLocationRelativeTo(null);
setVisible(true);
}
/**
* startClient() method invokes the whole conversation
*/
public void startClient()
{
try
{
boolean connected = false;
showMessage("Connecting to server...");
while(!connected)
{
try
{
connected = connectToServer();
}
catch(ConnectException ce)
{
// do nothing
}
}
setupStreams();
chat();
}
catch(EOFException eofe)
{
sendMessage("Connection terminated.");
}
catch(IOException ioe)
{
ioe.printStackTrace();
}
finally
{
cleanUpConnection();
}
}
/**
* connectToServer() establishes connection with the server application
*
* #throws IOException
*/
private boolean connectToServer() throws IOException
{
connection = new Socket(InetAddress.getByName(serverIP), 6789);
showMessage("Now connected to " + connection.getInetAddress().getHostName());
return true;
}
/**
* setupStream() method gets a stream to send/recieve data
*/
private void setupStreams() throws IOException
{
// setup output stream
output = new ObjectOutputStream(connection.getOutputStream()); // create pathway to allow us to connect to the computer the socket is connected to
output.flush();
// setup input stream
input = new ObjectInputStream(connection.getInputStream()); // create pathway to receive messages
showMessage("Stream setup success.");
}
/**
* chat() method code runs during conversation
*/
private void chat() throws IOException
{
String message = "Chatting enabled";
showMessage(message);
setCanType(true);
do
{
try
{
message = (String) input.readObject();
showMessage(message);
}
catch(ClassNotFoundException cnfe)
{
showMessage("Message recieve failed (Other person's problem)");
}
}
while(!message.equals("SERVER - /terminate"));
}
/**
* cleanUpConnection() method cleans up the stream and things after the chat has ended
*/
private void cleanUpConnection()
{
showMessage("Closing connection...");
setCanType(false);
try
{
output.close();
input.close();
connection.close();
}
catch(IOException io)
{
io.printStackTrace();
}
}
/**
* sendMessage(String) method sends whatever message you type
*
* #param message is what is going to be shown
*/
private void sendMessage(String message)
{
try
{
output.writeObject("CLIENT - " + message); // write the message to the outputstream
output.flush();
showMessage("CLIENT - " + message);
}
catch(IOException io)
{
convo.append("ERROR: Message can't be sent.");
}
}
/**
* showMessage(String) shows whatever needs to be shown on the JTextArea
*/
private void showMessage(String message)
{
SwingUtilities.invokeLater(new Runnable() // USE THIS RUNNABLE TO UPDATE GUI
{
public void run()
{
convo.append(" " + message + "\n");
}
});
}
/**
* setCanType() method decides whether or not a user can type
*
* #param canType
*/
private void setCanType(boolean canType)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
userInput.setEditable(canType);
}
});
}
}
InvokeClient.java:
package com.cloud.client;
import javax.swing.JFrame;
public class InvokeClient
{
public static void main(String[] args)
{
Client client = new Client("99.25.233.116");
client.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
client.startClient();
}
}
Note:
I tried setting up port forwarding on my home network with the port 6789 to one of my laptops, and that's the laptop I run the server application on.
I solved it. I just undid and redid the port forwarding again and it worked with the public IP address. Thank you all for your help
I have a client that starts a long running process on the server. At regular intervals, I'd like to show the user what's happening in the background. The most simple approach is to poll the server but I'm wondering if there wasn't a way to implement the Observer pattern for this. Unfortunately, I'm using RMI to talk to the server and I fear that I have to turn my client into an RMI server for this.
Is there another way that I'm missing?
http://sites.google.com/site/jamespandavan/Home/java/sample-remote-observer-based-on-rmi
RMI can in general support two way communication. (And yeah, RMI is a PITA to set up, and do anything else with.)
However, the HTTP transport that works over a CGI script(!) does not support it.
Consolidating all the answers here, I implemented 2 way RMI between client and server with server exposing its stub using Registry
The client gets a stub of the server from rmi registry
Then the client puts its stub as Observer to the server's addObserver method
The server notifies the clients using this stub
The following code will gives a better idea
import java.rmi.*;
import java.rmi.registry.*;
import java.rmi.server.*;
import java.util.Observable;
import java.util.Observer;
import java.net.*;
import javax.rmi.ssl.SslRMIClientSocketFactory;
import javax.rmi.ssl.SslRMIServerSocketFactory;
interface ReceiveMessageInterface extends Remote
{
/**
* #param x
* #throws RemoteException
*/
void receiveMessage(String x) throws RemoteException;
/**
* #param observer
* #throws RemoteException
*/
void addObserver(Remote observer) throws RemoteException;
}
/**
*
*/
class RmiClient extends UnicastRemoteObject
{
/**
* #param args
*/
static public void main(String args[])
{
ReceiveMessageInterface rmiServer;
Registry registry;
String serverAddress = args[0];
String serverPort = args[1];
String text = args[2];
System.out.println("sending " + text + " to " + serverAddress + ":" + serverPort);
try
{ // Get the server's stub
registry = LocateRegistry.getRegistry(serverAddress, (new Integer(serverPort)).intValue());
rmiServer = (ReceiveMessageInterface) (registry.lookup("rmiServer"));
// RMI client will give a stub of itself to the server
Remote aRemoteObj = (Remote) UnicastRemoteObject.exportObject(new RmiClient(), 0);
rmiServer.addObserver(aRemoteObj);
// call the remote method
rmiServer.receiveMessage(text);
// update method will be notified
}
catch (RemoteException e)
{
e.printStackTrace();
}
catch (NotBoundException e)
{
System.err.println(e);
}
}
public void update(String a) throws RemoteException
{
// update should take some serializable object as param NOT Observable
// and Object
// Server callsbacks here
}
}
/**
*
*/
class RmiServer extends Observable implements ReceiveMessageInterface
{
String address;
Registry registry;
/**
* {#inheritDoc}
*/
public void receiveMessage(String x) throws RemoteException
{
System.out.println(x);
setChanged();
notifyObservers(x + "invoked me");
}
/**
* {#inheritDoc}
*/
public void addObserver(final Remote observer) throws RemoteException
{
// This is where you plug in client's stub
super.addObserver(new Observer()
{
#Override
public void update(Observable o,
Object arg)
{
try
{
((RmiClient) observer).update((String) arg);
}
catch (RemoteException e)
{
}
}
});
}
/**
* #throws RemoteException
*/
public RmiServer() throws RemoteException
{
try
{
address = (InetAddress.getLocalHost()).toString();
}
catch (Exception e)
{
System.out.println("can't get inet address.");
}
int port = 3232;
System.out.println("this address=" + address + ",port=" + port);
try
{
registry = LocateRegistry.createRegistry(port);
registry.rebind("rmiServer", this);
}
catch (RemoteException e)
{
System.out.println("remote exception" + e);
}
}
/**
*
* #param args
*/
static public void main(String args[])
{
try
{
RmiServer server = new RmiServer();
}
catch (Exception e)
{
e.printStackTrace();
System.exit(1);
}
}
}
I don't think you're missing anything. The only two ways are to either periodically call the server and check the status (polling) or register a callback which the server periodically calls (your client must expose a method). IMO, polling is a perfectly reasonable way to handle this.