Updating the JavaFx Gui with threads and or Tasks - java

I am creating a chat program that contains a GUI that I have created in the new version of the JavaFx Scene builder. I have a main method that extends application and i have a simpleController (that controls all the buttons, labels, anchorPanes, etc) in my GUI.
Other than that, I have a server application that can receive and send messages. For this purpose, I have created the following simple protocol:
Commands / Description:
1 - Ask for permission to connect and at the same time, ask for a user
ID (server finds out how many users are online and adds the id+1)
2 - Chat, the client sends an ID and a String message (Example: 21
Hello (note all of these are on a separate line))
3 - Disconnects the client.
4 - Shows a list of all the clients online.
5 - Ask who else is online (this is only used when a user is
connecting and he needs to know how many users are online in order to
update the GUI).
10 - Error of all sorts if the server returns the 10 message it means
that the call the client just did was an error or that it could not
be completed!
Using this simple logic, it should be fairly easy for me to make users connect, chat and disconnect. However, it turned out that what should have been a simple task has turned out to be my worst nightmare.
So far my users has no problem connecting to the program and more users can connect at the same time.
Where things start to get tricky is when I want to send and receive messages between server and client.
I do not know how I can update my GUI while using a thread. I have tried to read up on the Task class, but I am unable to see whether this should be used instead of a thread or the thread should have this as a parameter.
Should I create a new class that listens for input and make that class extend thread? OR
Should the thread be running in my simpleController class?
Main
public class Main extends Application{
public static void main(String[] args) throws IOException{
Application.launch(Main.class, (java.lang.String[]) null);
}
#Override
public void start(Stage primaryStage) throws Exception {
try {
AnchorPane page = (AnchorPane) FXMLLoader.load(Main.class.getResource("testingBackground.fxml"));
Scene scene = new Scene(page);
primaryStage.setScene(scene);
primaryStage.setTitle("Chatten");
primaryStage.show();
} catch (Exception ex) {
java.util.logging.Logger.getLogger(Main.class.getName()).log(
java.util.logging.Level.SEVERE, null, ex);
}
}
}
simpleController
import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Scanner;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import com.sun.glass.ui.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.text.Text;
/*
* evt ret array listen med commands da jeg selv kan styre hvilke commands der kommer ind og ud! og brugeren faktisk
* aldrig selv kan vælge!
*/
public class SimpleController extends Thread implements Initializable{
public Button btn_Connect;
public AnchorPane pictureFrame;
public Socket socket = new Socket();
public PrintWriter pw;
public Scanner input;
public int clientId = 1;
public Client client = new Client(socket, pw, input, clientId);
// options!
public TextField txt_userName;
public TextField textField_chat;
// send button
public Button Send;
/*
* current client that the user i connected with, this client is used to send commands and tell other clients who is connected on
* what "ingame chat persons"
*/
public static int currentClientId;
// chatperson username
public Label lbl_userName2;
public Label lbl_userName3;
public Label lbl_chatPerson2;
public Label lbl_Chatperson1_userName;
//Pictures of chat person
public Label chatPerson3;
public Label chatPerson1;
// chat persons textfield
public TextArea txt_ChatPerson1;
//public TextField txt_ChatPerson1;
public TextField txt_ChatPerson2;
public TextField txt_ChatPerson3;
#Override
public void initialize(URL location, ResourceBundle resources) throws NullPointerException {
try {
client.connect();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
pictureFrame.setMaxSize(409, 373);
txt_ChatPerson1.setMinWidth(50);
txt_ChatPerson1.setPrefWidth(50);
txt_ChatPerson1.setMaxWidth(300);
txt_ChatPerson1.setText(" ");
btn_Connect.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) throws NullPointerException {
connectMeWithOthers(1);
}
});
Send.setOnAction(new EventHandler<ActionEvent>() {
// WORK IN PROGReSS!!
#Override
public void handle(ActionEvent event) {
/*
* new line code:
*/
String x = textField_chat.getText();
txt_ChatPerson1.setText(x);
txt_ChatPerson1.setVisible(true);
System.out.println("x" +x);
txt_ChatPerson1.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(
ObservableValue<? extends String> observable,
String oldValue, String newValue) {
// txt_ChatPerson1.setPrefRowCount(5);
txt_ChatPerson1.setPrefWidth(txt_ChatPerson1.getText().length()*7);
//txt_ChatPerson1.setPrefHeight(txt_ChatPerson1.getText().length()*3);
}
});
txt_ChatPerson1.autosize();
client.SendChat(x);
}
});
}
/**
* this method connect the client to the other clients who are online on the server!
* the method calls it self after the user has established connection in order to load the other chat persons online
* if the client is the online user online then it will only load the user
* #param id
*/
protected void connectMeWithOthers(int id) {
try {
int responseId = client.sendCommando(id);
System.out.println(" response id "+responseId);
// finds whom is connected and tries to connect to a spot that is avalibul!
//Response is the ID of the chat persons
switch (responseId) {
case 1:
currentClientId = client.reciveCommando();
client.setClientId(currentClientId);
client.sendString(txt_userName.getText());
connectMeWithOthers(5);
break;
case 5:
int times = client.reciveCommando();
int o = 0;
System.out.println("times: "+times);
while (o != times) {
int j = client.reciveCommando();
System.out.println("j"+ j);
String name = client.reciveString();
System.out.println("Name " +name);
createUser(j, name);
o++;
}
start();
break;
case 10:
System.out.println("Connection fail chat room is full! Please try again later!");
case 8:
start();
break;
default:
break;
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void createUser(int j, String reciveChat) {
if (j == 1) {
chatPerson1.setVisible(true);
lbl_Chatperson1_userName.setVisible(true);
lbl_Chatperson1_userName.setText(reciveChat);
}else if (j == 2) {
lbl_chatPerson2.setVisible(true);
lbl_userName2.setVisible(true);
lbl_userName2.setText(reciveChat);
}else if (j == 3){
chatPerson3.setVisible(true);
lbl_userName3.setVisible(true);
lbl_userName3.setText(reciveChat);
}else {
Image img = new Image(getClass().getResourceAsStream("Figur.png"));
Label test2 = new Label("", new ImageView(img));
test2.setLayoutX(50);
test2.setLayoutY(30);
test2.setPrefSize(1000, 1000);
pictureFrame.getChildren().addAll(test2);
test2.setVisible(true);
}
}
/*
* denne metode er en rewrite af run metoden.
*/
public void StartClient(){
ClientListner cl = new ClientListner(client);
Task task = new Task<String>() {
#Override
protected String call() throws Exception {
// TODO Auto-generated method stub
return null;
}
};
Thread t = new Thread(task);
cl.start();
while (true) {
if (cl.recived) {
}
}
}
/*
* Run metoden er brugt til at recive data fra andre users og update GUI'en skal muligvis rewrites!?
*
*/
public void run(){
System.out.println("Thread started");
System.out.println(client.getSocket().isConnected());
ClientListner cl = new ClientListner(client);
while (client.getSocket().isConnected()) {
int key = 10;
if (cl.recived) {
try {
key = client.reciveCommando();
System.out.println("jeg er her");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("Key "+key);
switch (key) {
// case 2 er recive chat:
case 2:
// først find ud af hvilket ID der har sendt chatten:
int y = 0;
try {
y = client.reciveCommando();
System.out.println("y" + y);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// derefter få beskeden og send den så ud til resten.
String says = client.reciveChat().toString();
if (y == 1) {
txt_ChatPerson1.setText(client.reciveChat());
}else if (y == 2) {
}else {
chatPerson3.setVisible(true);
txt_ChatPerson3.setVisible(true);
txt_ChatPerson3.setText(client.reciveChat());
}
break;
default:
break;
}
}
}
}
Client
import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
public class Client {
// disse var static
public final static int portNumber = 6040;
public Socket socket;
private PrintWriter pw;
private Scanner input;
private int clientId;
/**
* #param args
* #throws IOException
*/
public Client(Socket socket, PrintWriter pw, Scanner input, int clientId){
this.socket = socket;
this.pw = pw;
this.input = input;
this.clientId = clientId;
}
public void connect() throws IOException{
// du kan vælge at bruge inetadressen til at connecte i socketet.
InetAddress adr = InetAddress.getByName("localhost");
socket = new Socket("localhost", portNumber);
input=new Scanner(socket.getInputStream());
pw = new PrintWriter(socket.getOutputStream());
}
/**
* This method sends the message (that the client(chat person) writes to the user)
* #param x
* #throws NullPointerException
* #throws IOException
*/
public void SendChat(String x) throws NullPointerException{
pw.println(2);
pw.flush();
pw.println(SimpleController.currentClientId);
pw.flush();
pw.println(x);
pw.flush();
}
public int sendCommando(int id) throws IOException{
System.out.println("Jeg sender"+ id);
pw.println(id);
pw.flush();
/*
* this part of the program sends a command to the server if the command is 1 then 1 is = Connect.
* the program then ask the server is the server is full or is it ok to connect?
* if the response is not 10 then the program will allow a connection to happen the return type will be the Id of which
* the chat person becomes!
*/
// should the method return 0 the Application will do NOTHING!
switch (id) {
case 1:
int k = reciveCommando();
if (k== 10) {
return 10;
}else if (k < 3) {
System.out.println("returned k" + k);
return k;
}else {
return 10;
}
/*
* Closes the connection with the server!
*/
case 3:
socket.close();
return 0;
case 5:
int y = reciveCommando();
return y;
default:
return 0;
}
}
/*
* this method recives a command from the server! the comands can be found in the ChatCommands.txt
* returns the command as an integer!
*/
public int reciveCommando() throws IOException{
Integer i = input.nextInt();
return i;
}
/**
* Gets a String response from the server. This method i used to create other users and give them the correct username.
*
* #param i
* #return
* #throws IOException
*/
public String getStringResponse(int i) throws IOException {
pw.print(i);
pw.flush();
String x = input.nextLine();
return x;
}
/*
* Work in progress - client getter og setter methoder!
*/
public Socket getSocket(){
return socket;
}
public Scanner getInput(){
return input;
}
public PrintWriter getPw(){
return pw;
}
public int getClientId(){
return clientId;
}
public void setClientId(int i ){
clientId = i;
}
public String reciveChat(){
String x = getInput().next();
return x;
}
public String reciveString(){
String x =input.next();
return x;
}
public void sendString(String x){
pw.println(x);
pw.flush();
}
}*
I am really sorry about the code being kind of messy. The run() method in the simple controller was an attempt to make a thread of the simpleController. However, this did not work as I expected. :(
The main goal of this is basically to make sure that the two people in the chat room can chat together. So, all it has to do is update 1 or 2 textareas.

Please check this out if you have not done that already
http://docs.oracle.com/javafx/2/threads/jfxpub-threads.htm

This took me a little time to find so I thought I would document it here.
Platform.runLater(new Runnable(){
#Override
public void run() {
// Update your GUI here.
}
});

This is old but since it was up there in google I would like to point out your best bet is to use
FXObservableList
or
Bean Properties like
DoubleProperty
Which are thread safe.

If you're using Java 8 :
Platform.runLater(() -> {
// Update your GUI here.;
});

Related

Java File Transfer Program Hangs before Executing SwingWorker

I've written a program which uses multicast to discover peers on a local network and allows file transfer between them. It works, except some process of acquiring files/initializing the transfer thread is ridiculously slow. It hangs for about 10-15 seconds, then begins transferring and completes normally:
Transfer.java
My JFrame GUI class. This was done with Netbeans for convenience, so any generated code won't be posted here.
package transfer;
import java.beans.PropertyChangeEvent;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFileChooser;
import static javax.swing.JFileChooser.FILES_AND_DIRECTORIES;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Transfer extends javax.swing.JFrame {
private final FileDrop fileDrop;
private Client client;
private final Server server;
private final ClientMulticast clientMulticast;
private final ServerMulticast serverMulticast;
public Transfer() throws IOException {
initComponents();
this.setTitle("Transfer");
peerBox.setEditable(false);
peerBox.removeAllItems();
fileDrop = new FileDrop(backgroundPanel, (java.io.File[] files) -> {
System.out.println(files[0].isDirectory());
if (peerBox.getSelectedIndex() != -1) {
sendLabel.setText("Hello");
try {
client = new Client(sendLabel, files, (Peer) peerBox.getSelectedItem());
//Client property change listener - listens for updates to progress bar
client.addPropertyChangeListener((PropertyChangeEvent evt1) -> {
if (null != evt1.getPropertyName()) switch (evt1.getPropertyName()) {
case "progress":
sendProgressBar.setValue((Integer) evt1.getNewValue());
break;
}
});
client.execute();
} catch (Exception ex) {
System.out.println("Unable to send! IOException in FileDrop call.");
ex.printStackTrace(System.out);
}
} else {
sendLabel.setText("Host not found");
}
});
sendProgressBar.setMaximum(100);
sendProgressBar.setMinimum(0);
receiveProgressBar.setMaximum(100);
receiveProgressBar.setMinimum(0);
server = new Server(receiveLabel);
//Server property change listener - listens for updates to progress bar
server.addPropertyChangeListener((PropertyChangeEvent evt1) -> {
if ("progress".equals(evt1.getPropertyName())) {
receiveProgressBar.setValue((Integer) evt1.getNewValue());
}
});
server.execute();
serverMulticast = new ServerMulticast();
serverMulticast.execute();
clientMulticast = new ClientMulticast(peerBox);
clientMulticast.execute();
}
...GENERATED CODE...
private void openButtonActionPerformed(java.awt.event.ActionEvent evt) {
Transfer guiObject = this;
SwingWorker openFile = new SwingWorker<Void, String>() {
#Override
protected Void doInBackground() throws Exception {
openButton.setEnabled(false);
fileChooser.setFileSelectionMode(FILES_AND_DIRECTORIES);
int returnVal = fileChooser.showOpenDialog(guiObject);
if (returnVal == JFileChooser.APPROVE_OPTION && peerBox.getSelectedIndex() != -1) {
File[] fileArray = fileChooser.getSelectedFiles();
client = new Client(sendLabel, fileArray, (Peer) peerBox.getSelectedItem());
//Client property change listener - listens for updates to progress bar
client.addPropertyChangeListener((PropertyChangeEvent evt1) -> {
if ("progress".equals(evt1.getPropertyName())) {
sendProgressBar.setValue((Integer) evt1.getNewValue());
}
});
client.execute();
//block this swingworker until client worker is done sending
while(!client.isDone()) { }
}
openButton.setEnabled(true);
return null;
}
};
openFile.execute();
}
/**
* #param args the command line arguments
* #throws java.lang.ClassNotFoundException
* #throws java.lang.InstantiationException
* #throws java.lang.IllegalAccessException
* #throws javax.swing.UnsupportedLookAndFeelException
*/
public static void main(String args[]) throws ClassNotFoundException, InstantiationException, IllegalAccessException, UnsupportedLookAndFeelException {
System.setProperty("apple.laf.useScreenMenuBar", "true");
System.setProperty("com.apple.mrj.application.apple.menu.about.name", "Transfer");
UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName());
/* Create and display the form */
java.awt.EventQueue.invokeLater(() -> {
try {
new Transfer().setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Transfer.class.getName()).log(Level.SEVERE, null, ex);
}
});
}
// Variables declaration - do not modify
private javax.swing.JPanel backgroundPanel;
private javax.swing.JFileChooser fileChooser;
private javax.swing.JButton openButton;
private javax.swing.JComboBox<Peer> peerBox;
private javax.swing.JLabel receiveHeaderLabel;
private javax.swing.JLabel receiveLabel;
private javax.swing.JPanel receivePanel;
private javax.swing.JProgressBar receiveProgressBar;
private javax.swing.JLabel sendHeaderLabel;
private javax.swing.JLabel sendLabel;
private javax.swing.JPanel sendPanel;
private javax.swing.JProgressBar sendProgressBar;
// End of variables declaration
}
ClientMutlicast.java
Sends out multicast packets and receives responses - modifies the GUI accordingly. This class is where the errors were.
package transfer;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.swing.JComboBox;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class ClientMulticast extends SwingWorker<Void, Peer> {
private boolean peerPreviouslyFound;
private byte[] sendData, receiveData;
private final DatagramSocket mSocket;
private DatagramPacket receivePacket;
private final JComboBox<Peer> peerBox;
private Peer peer;
private ArrayList<Peer> peerList;
public ClientMulticast(JComboBox<Peer> peerBox) throws SocketException {
peerList = new ArrayList<>();
this.peerBox = peerBox;
mSocket = new DatagramSocket();
mSocket.setBroadcast(true);
mSocket.setSoTimeout(300);
sendData = "CLIENT_MSG".getBytes();
}
#Override
protected Void doInBackground() throws IOException, InterruptedException, InvocationTargetException {
while (true) {
try {
receiveData = new byte[1024];
receivePacket = new DatagramPacket(receiveData, receiveData.length);
peerPreviouslyFound = false;
//send broadcast message
mSocket.send(new DatagramPacket(sendData, sendData.length, InetAddress.getByName("239.255.255.255"), 8888));
//receive response
mSocket.receive(receivePacket);
//don't have to worry about responses from local host because
//server rejects multicast packets from local host
peer = new Peer(receivePacket.getAddress(), receivePacket.getPort(), System.currentTimeMillis());
for (Peer peerList1 : peerList) {
if (peerList1.getIPAddress().equals(peer.getIPAddress())) {
peerList1.setTimestamp(System.currentTimeMillis());
peerPreviouslyFound = true;
break;
}
}
//add to peer list only if reponse is valid, not from local host, and not previously received from this host
if ("SERVER_RESPONSE".equalsIgnoreCase(new String(receivePacket.getData()).trim())
&& !peerPreviouslyFound) {
//publish(peer);
peerBox.addItem(peer);
peerList.add(peer);
}
for (int i = 0; i < peerList.size(); i++) {
//if peer is greater than 5 seconds old, remove from list
if (peerList.get(i).getTimestamp() + 5000 < System.currentTimeMillis()) {
peerBox.removeItemAt(i);
peerList.remove(i);
}
}
} catch (SocketTimeoutException ex) {
for (int i = 0; i < peerList.size(); i++) {
//if peer is greater than 5 seconds old, remove from list
if (peerList.get(i).getTimestamp() + 5000 < System.currentTimeMillis()) {
final int j = i;
SwingUtilities.invokeAndWait(() -> {
peerBox.removeItemAt(j);
peerList.remove(j);
});
}
}
}
TimeUnit.MILLISECONDS.sleep(500);
}//end while
}
#Override
protected void process(List<Peer> p) {
peerBox.addItem(p.get(p.size() - 1));
peerList.add(p.get(p.size() - 1));
}
}
I'm pretty sure the issue is when the FileDrop object within the UI constructor tries to execute the Client SwingWorker using client.execute(), there's a large delay. I can debug it but it doesn't show any issues. Also, I know the issue can't be with my socket.connect() call within Client(), because the print statement immediately before socket.connect() doesn't print until the program has resumes from wherever it's hanging. Any ideas? I'm completely lost.
-- EDIT: All code above works as desired, bugs noted here have been solved. If anyone wants the full source, I'll gladly share it.
I am passing my combo box, called peerBox, to my ClientMuticast class. I was modifying this structure directly, storing peers within. This was blocking the interface thread, as the ClientMulticast class was constantly accessing/changing the combo box.
I changed the code to use the combo box to ONLY display values to the gui and store the discovered peers. An array list was used to copy all the values from the combo box upon each invocation of the ClientMulticast constructor. If a peer is discovered, the combo box is updated via the publish() and process() methods within the ClientMulticast class. Any code placed within process() will be scheduled to execute on the EDT.

Why does my Java client/server app hang when using ObjectInputStream but doesn't when I use BufferedReader with InputStreamReader?

I am wondering why it gets stuck on the following line, but it didn't used to get stuck when I was using a BufferedReader with an InputStreamReader:
input = new ObjectInputStream(socket.getInputStream());
Here is my client code:
import java.awt.BorderLayout;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class MtgTestRunsClient {
private JFrame frame = new JFrame("MTG Test Runs");
private static int PORT = 8901;
private Socket socket;
//private BufferedReader inFromServer;
//private PrintWriter outToServer;
private ObjectInputStream inFromServer;
private ObjectOutputStream outToServer;
private Planeswalker planeswalker;
public MtgTestRunsClient(String serverAddress) throws Exception {
// Setup networking
socket = new Socket(serverAddress, PORT);
//inFromServer = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//outToServer = new PrintWriter(socket.getOutputStream(), true);
inFromServer = new ObjectInputStream(socket.getInputStream());
outToServer = new ObjectOutputStream(socket.getOutputStream());
planeswalker = new BUGDelverPlaneswalker();
planeswalker.setOutputToServer(outToServer);
// Frame content
JPanel contentPane = new JPanel(new BorderLayout());
frame.setContentPane(contentPane);
frame.getContentPane().add(planeswalker.getStatusBar(), BorderLayout.SOUTH);
frame.getContentPane().add(planeswalker, BorderLayout.CENTER);
}
public void play() throws Exception {
//String response;
Object response;
try {
//response = inFromServer.readLine();
response = inFromServer.readObject();
if (response instanceof String ){
if( ((String)response).startsWith("WELCOME")) {
char mark = ((String)response).charAt(8);
frame.setTitle("MTG Test Runs - Player " + mark);
}
}
while (true) {
//response = inFromServer.readLine();
response = inFromServer.readObject();
if (response instanceof String ){
if (((String)response).startsWith("OPPONENT_MOVED")) {
planeswalker.getStatusBar().setStatusString("Opponent "+((String)response).substring(15), false, true);
} else if (((String)response).startsWith("GAME_OVER")) {
break;
} else if (((String)response).startsWith("MESSAGE")) {
String messageText = ((String)response).substring(8);
planeswalker.getStatusBar().setStatusString(messageText, false, true);
}
}else if(response instanceof Planeswalker){
planeswalker.setOpponent((Planeswalker)response);
}
}
outToServer.writeObject("QUIT");
outToServer.flush();
}
finally {
socket.close();
}
}
private boolean wantsToPlayAgain() {
int response = JOptionPane.showConfirmDialog(frame,
"Want to play again?",
"Tic Tac Toe is Fun Fun Fun",
JOptionPane.YES_NO_OPTION);
frame.dispose();
return response == JOptionPane.YES_OPTION;
}
/**
* Runs the client as an application.
*/
public static void main(String[] args) throws Exception {
while (true) {
String serverAddress = (args.length == 0) ? "localhost" : args[1];
MtgTestRunsClient client = new MtgTestRunsClient(serverAddress);
client.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
client.frame.setExtendedState(client.frame.getExtendedState()|JFrame.MAXIMIZED_BOTH);
client.frame.setVisible(true);
client.frame.repaint();
client.play();
if (!client.wantsToPlayAgain()) {
break;
}
}
}
}
Here is my server code:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
* A server for a network multi-player tic tac toe game. Modified and
* extended from the class presented in Deitel and Deitel "Java How to
* Program" book. I made a bunch of enhancements and rewrote large sections
* of the code. The main change is instead of passing data between the
* client and server, I made a TTTP (tic tac toe protocol) which is totally
* plain text, so you can test the game with Telnet (always a good idea.)
* The strings that are sent in TTTP are:
*
* Client -> Server Server -> Client
* ---------------- ----------------
* MOVE (0 <= n <= 8) WELCOME (char in {X, O})
* QUIT VALID_MOVE
* OTHER_PLAYER_MOVED
* VICTORY
* DEFEAT
* TIE
* MESSAGE
*
* A second change is that it allows an unlimited number of pairs of
* players to play.
*/
public class MtgTestRunsServer {
/**
* Runs the application. Pairs up clients that connect.
*/
public static void main(String[] args) throws Exception {
ServerSocket listener = new ServerSocket(8901);
System.out.println("MTG Test Runs Server is Running");
try {
while (true) {
Game game = new Game();
Game.Player player1 = game.new Player(listener.accept(), '1');
Game.Player player2 = game.new Player(listener.accept(), '2');
player1.setOpponent(player2);
player2.setOpponent(player1);
game.currentPlayer = player1;
player1.start();
player2.start();
}
} finally {
listener.close();
}
}
}
/**
* A two-player game.
*/
class Game {
Player currentPlayer;
public synchronized boolean legalMove(Player player, String move) {
if (player == currentPlayer ) {
currentPlayer = currentPlayer.opponent;
currentPlayer.otherPlayerMoved(move);
return true;
}
return false;
}
class Player extends Thread {
char playerNo;
Player opponent;
Socket socket;
//BufferedReader input;
//PrintWriter output;
ObjectInputStream input;
ObjectOutputStream output;
public Player(Socket socket, char playerNumber) {
this.socket = socket;
this.playerNo = playerNumber;
try {
//input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//output = new PrintWriter(socket.getOutputStream(), true);
output = new ObjectOutputStream(socket.getOutputStream());
output.writeObject("WELCOME " + playerNumber);
output.flush();
output.writeObject("MESSAGE Waiting for opponent to connect");
output.flush();
**input = new ObjectInputStream(socket.getInputStream());** // Must do this after constructed ObjectOutputStream above
//output.println("WELCOME " + playerNumber);
} catch (IOException e) {
System.out.println("Player died: " + e);
}
}
/**
* Accepts notification of who the opponent is.
*/
public void setOpponent(Player opponent) {
this.opponent = opponent;
}
/**
* Handles the otherPlayerMoved message.
*/
public void otherPlayerMoved(String move) {
//output.println("OPPONENT_MOVED " + move);
try {
output.writeObject("OPPONENT_MOVED " + move);
output.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* The run method of this thread.
*/
public void run() {
try {
// The thread is only started after everyone connects.
//output.println("MESSAGE All players connected");
output.writeObject("MESSAGE All players connected");
output.flush();
// Tell the first player that it is her turn.
if (playerNo == '1') {
//output.println("MESSAGE Your move");
output.writeObject("MESSAGE Your move");
output.flush();
}
// Repeatedly get commands from the client and process them.
while (true) {
//String command = input.readLine();
Object command;
try {
command = input.readObject();
if(command instanceof String){
if (((String)command).startsWith("MOVE")) {
String move = ((String)command).substring(5);
if (legalMove(this, move)) {
//output.println("VALID_MOVE");
output.writeObject("VALID_MOVE");
} else {
output.writeObject("MESSAGE INVALID_MOVE");
//output.println("MESSAGE INVALID_MOVE");
}
} else if (((String)command).startsWith("QUIT")) {
return;
}
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} catch (IOException e) {
System.out.println("Player died: " + e);
} finally {
try {socket.close();} catch (IOException e) {}
}
}
}
}
Straight from the javadoc:
Creates an ObjectInputStream that reads from the specified InputStream. A serialization stream header is read from the stream and verified. This constructor will block until the corresponding ObjectOutputStream has written and flushed the header.
You need to construct the a ObjectOutputStream before the ObjectInputStream. Otherwise you get a deadlock because of the stream header written and read by the constructors.

The netty server seems to be blocked when I add a ExecutionHandler?

THE SCENE:
I am writing a echo client and server. The data being transfered is a string:
Client encode a string,and send it to server.
Server recv data, decode string, then encode the received string, send it back to client.
The above process will be repeated 100000 times.(Note: the connection is persistent).
DEFERENT CONTIONS:
When I run ONE server and TWO client at the same time, everything is ok,every client receives 100000 messages and terminated normally.
But When I add a ExecutionHandler on server, and then run ONE server and TWO client at the same time, one client will never terminate, and the network traffic is zero.
I cant locate the key point of this problem for now, will you give me some suggestions?
MY CODE:
string encoder , string decoder, client handler , server handler , client main ,server main.
//Decoder=======================================================
import java.nio.charset.Charset;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.FrameDecoder;
public class Dcd extends FrameDecoder {
public static final Charset cs = Charset.forName("utf8");
#Override
protected Object decode(ChannelHandlerContext ctx, Channel channel,
ChannelBuffer buffer) throws Exception {
if (buffer.readableBytes() < 4) {
return null;
}
int headlen = 4;
int length = buffer.getInt(0);
if (buffer.readableBytes() < length + headlen) {
return null;
}
String ret = buffer.toString(headlen, length, cs);
buffer.skipBytes(length + headlen);
return ret;
}
}
//Encoder =======================================================
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
public class Ecd extends OneToOneEncoder {
#Override
protected Object encode(ChannelHandlerContext ctx, Channel channel,
Object msg) throws Exception {
if (!(msg instanceof String)) {
return msg;
}
byte[] data = ((String) msg).getBytes();
ChannelBuffer buf = ChannelBuffers.dynamicBuffer(data.length + 4, ctx
.getChannel().getConfig().getBufferFactory());
buf.writeInt(data.length);
buf.writeBytes(data);
return buf;
}
}
//Client handler =======================================================
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
/**
* Handler implementation for the echo client. It initiates the ping-pong
* traffic between the echo client and server by sending the first message to
* the server.
*/
public class EchoClientHandler extends SimpleChannelUpstreamHandler {
private static final Logger logger = Logger
.getLogger(EchoClientHandler.class.getName());
private final AtomicLong transferredBytes = new AtomicLong();
private final AtomicInteger counter = new AtomicInteger(0);
private final AtomicLong startTime = new AtomicLong(0);
private String dd;
/**
* Creates a client-side handler.
*/
public EchoClientHandler(String data) {
dd = data;
}
public long getTransferredBytes() {
return transferredBytes.get();
}
#Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
// Send the first message. Server will not send anything here
// because the firstMessage's capacity is 0.
startTime.set(System.currentTimeMillis());
Channels.write(ctx.getChannel(), dd);
}
#Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
// Send back the received message to the remote peer.
transferredBytes.addAndGet(((String) e.getMessage()).length());
int i = counter.incrementAndGet();
int N = 100000;
if (i < N) {
e.getChannel().write(e.getMessage());
} else {
ctx.getChannel().close();
System.out.println(N * 1.0
/ (System.currentTimeMillis() - startTime.get()) * 1000);
}
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
// Close the connection when an exception is raised.
logger.log(Level.WARNING, "Unexpected exception from downstream.",
e.getCause());
e.getChannel().close();
}
}
//Client main =======================================================
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
/**
* Sends one message when a connection is open and echoes back any received data
* to the server. Simply put, the echo client initiates the ping-pong traffic
* between the echo client and server by sending the first message to the
* server.
*/
public class EchoClient {
private final String host;
private final int port;
public EchoClient(String host, int port) {
this.host = host;
this.port = port;
}
public void run() {
// Configure the client.
final ClientBootstrap bootstrap = new ClientBootstrap(
new NioClientSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
// Set up the pipeline factory.
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
return Channels.pipeline(new Dcd(), new Ecd(),
new EchoClientHandler("abcdd"));
}
});
bootstrap.setOption("sendBufferSize", 1048576);
bootstrap.setOption("receiveBufferSize", 1048576);
bootstrap.setOption("tcpNoDelay", true);
bootstrap.setOption("writeBufferLowWaterMark", 32 * 1024);
bootstrap.setOption("writeBufferHighWaterMark", 64 * 1024);
List<ChannelFuture> list = new ArrayList<ChannelFuture>();
for (int i = 0; i < 1; i++) {
// Start the connection attempt.
ChannelFuture future = bootstrap.connect(new InetSocketAddress(
host, port));
// Wait until the connection is closed or the connection
// attempt
// fails.
list.add(future);
}
for (ChannelFuture f : list) {
f.getChannel().getCloseFuture().awaitUninterruptibly();
}
// Shut down thread pools to exit.
bootstrap.releaseExternalResources();
}
private static void testOne() {
final String host = "192.168.0.102";
final int port = 8000;
new EchoClient(host, port).run();
}
public static void main(String[] args) throws Exception {
testOne();
}
}
//server handler =======================================================
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
/**
* Handler implementation for the echo server.
*/
public class EchoServerHandler extends SimpleChannelUpstreamHandler {
private static final Logger logger = Logger
.getLogger(EchoServerHandler.class.getName());
private final AtomicLong transferredBytes = new AtomicLong();
public long getTransferredBytes() {
return transferredBytes.get();
}
#Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
// Send back the received message to the remote peer.
transferredBytes.addAndGet(((String) e.getMessage()).length());
Channels.write(ctx.getChannel(), e.getMessage());
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
// Close the connection when an exception is raised.
logger.log(Level.WARNING, "Unexpected exception from downstream.",
e.getCause());
e.getChannel().close();
}
}
//Server main =======================================================
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.execution.ExecutionHandler;
import org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor;
/**
* Echoes back any received data from a client.
*/
public class EchoServer {
private final int port;
public EchoServer(int port) {
this.port = port;
}
public void run() {
// Configure the server.
ServerBootstrap bootstrap = new ServerBootstrap(
new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
System.out.println(Runtime.getRuntime().availableProcessors() * 2);
final ExecutionHandler executionHandler = new ExecutionHandler(
new OrderedMemoryAwareThreadPoolExecutor(16, 1048576, 1048576));
// Set up the pipeline factory.
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
System.out.println("new pipe");
return Channels.pipeline(new Dcd(), new Ecd(),
executionHandler, new EchoServerHandler());
}
});
bootstrap.setOption("child.sendBufferSize", 1048576);
bootstrap.setOption("child.receiveBufferSize", 1048576);
bootstrap.setOption("child.tcpNoDelay", true);
bootstrap.setOption("child.writeBufferLowWaterMark", 32 * 1024);
bootstrap.setOption("child.writeBufferHighWaterMark", 64 * 1024);
// Bind and start to accept incoming connections.
bootstrap.bind(new InetSocketAddress(port));
}
public static void main(String[] args) throws Exception {
int port = 8000;
new EchoServer(port).run();
}
}
I have found the reason now, it is a hard work, but full with pleasure.
When added a ExecutionHandler, the message will be wrapped into a Runnable task, and will be executed in a ChildExecutor. The key point is here : A task maybe added to ChildExecutor when the executor almostly exit , then is will be ignored by the ChildExecutor.
I added three lines code and some comments, the final code looks like below, and it works now,should I mail to the author? :
private final class ChildExecutor implements Executor, Runnable {
private final Queue<Runnable> tasks = QueueFactory
.createQueue(Runnable.class);
private final AtomicBoolean isRunning = new AtomicBoolean();
public void execute(Runnable command) {
// TODO: What todo if the add return false ?
tasks.add(command);
if (!isRunning.get()) {
doUnorderedExecute(this);
} else {
}
}
public void run() {
// check if its already running by using CAS. If so just return
// here. So in the worst case the thread
// is executed and do nothing
boolean acquired = false;
if (isRunning.compareAndSet(false, true)) {
acquired = true;
try {
Thread thread = Thread.currentThread();
for (;;) {
final Runnable task = tasks.poll();
// if the task is null we should exit the loop
if (task == null) {
break;
}
boolean ran = false;
beforeExecute(thread, task);
try {
task.run();
ran = true;
onAfterExecute(task, null);
} catch (RuntimeException e) {
if (!ran) {
onAfterExecute(task, e);
}
throw e;
}
}
//TODO NOTE (I added): between here and "isRunning.set(false)",some new tasks maybe added.
} finally {
// set it back to not running
isRunning.set(false);
}
}
//TODO NOTE (I added): Do the remaining works.
if (acquired && !isRunning.get() && tasks.peek() != null) {
doUnorderedExecute(this);
}
}
}
This was a bug and will be fixed in 3.4.0.Alpha2.
See https://github.com/netty/netty/issues/234

How to make a basic instant messaging program in pure Java [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
Reinstating this question with a bounty! I need an example that stays online, like a real instant messenger! It needs to always be ready to receive or send a message to an arbitrary address over an arbitrary port, using TCP. The program must not quit after sending/receiving a message.
Bounty goes to whoever can give the best example of a real, usable instant messenger.
Looking online, all resources I found are either useless tutorials, dead threads, dead tutorials, ancient examples, or tell the programmer to use external APIs. How can I create a basic instant messenger from the ground up, only using Java SE?
There must be a way to do this, and some sample code would be appreciated. It only needs to perform the simplest tasks: Check if a compatible client is online on another computer (IP will be provided by the user) and send a TCP packet to that client, which will receive and display its contents.
When this question was first asked and answered back in 2011, it was simply "Looking online, all resources I found are either useless
tutorials, dead threads, or tell the programmer to use external
APIs.". The provided links below met the criteria at the time. Further discussion follows in the comments.
First few Google results for "java socket chat":
http://cs.lmu.edu/~ray/notes/javanetexamples/
simple chatting program in java usings socket class
http://pirate.shu.edu/~wachsmut/Teaching/CSAS2214/Virtual/Lectures/chat-client-server.html
http://www.cise.ufl.edu/~amyles/tutorials/tcpchat/
http://ashishmyles.com/tutorials/tcpchat/index.html
Internet Archive link to fix missing Java source file downloads: https://web.archive.org/web/20150623102646/http://ashishmyles.com/tutorials/tcpchat/index.html
Or from "java 8 chat client":
https://gist.github.com/alex-zykov/b4052e3c1b6891081897
Many, many results following in the search. Pick one that suits your needs. You can even modify the Google search to only show results from the past year, if you wish.
I've done this when I was learning Java, something around 10 years ago. It works:
Constantes.java:
package jsc;
public interface Constantes {
public static final String MULTICAST_IP = "224.0.0.1";
public static final int MULTICAST_PORTA = 3333;
public static final String SEPARADOR = "[>>>]";
public static final int TAMANHO_MENSAGEM = 1024;
public static final long ESPERA = 3000;
public static final String ESTOUONLINE = "EstouOnline";
public static final String DESCONECTANDO = "Desconectando";
public static final String PRIVADO = "Privado";
}
ControladorThread.java
package jsc;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.Vector;
public class ControladorThread extends Thread implements Constantes{
private MulticastSocket mcSocket;
private Main main;
private Vector<Usuario> listaUsuarios; // lista de usuários ativos
public ControladorThread(Main main){
super("ReceptoraThread_" + main.getNick());
listaUsuarios = new Vector<Usuario>();
listaUsuarios.add(new Usuario(main.getNick(), new Date().getTime()));
this.main = main;
try{
mcSocket = new MulticastSocket(MULTICAST_PORTA);
mcSocket.joinGroup(InetAddress.getByName(MULTICAST_IP));
} catch(IOException e){
e.printStackTrace();
}
}
public void run(){
while(true){
try{
byte[] buffer = receberPacote();
processar(buffer);
removerUsuariosOciosos();
atualizarListaUsuarios();
} catch(IOException e){
e.printStackTrace();
}
}
}
public byte [] receberPacote() throws IOException{
byte[] buffer = new byte[TAMANHO_MENSAGEM];
DatagramPacket pacote = new DatagramPacket(buffer, buffer.length);
mcSocket.receive(pacote);
return buffer;
}
public void processar(byte[] buffer){
String mensagem = new String(buffer);
mensagem = mensagem.trim();
StringTokenizer tokens = new StringTokenizer(mensagem, SEPARADOR);
String t1 = tokens.nextToken();
String t2 = tokens.nextToken();
if(t1.equals(ESTOUONLINE))
atualizarEstadoUsuario(t2);
else if(t1.equals(DESCONECTANDO))
desconectarUsuario(t2);
else if(t1.equals(PRIVADO)){
String t3 = tokens.nextToken();
String t4 = tokens.nextToken();
if(t3.equals(main.getNick())){
receberMensagemPrivada(t2, t4);
}
}
else
main.setTextoEntrada(t1 + " diz: " + t2);
}
public void receberMensagemPrivada(String deUsuario, String mensagem){
main.abrirChatPrivado(main.getNick(), deUsuario, mensagem);
}
public boolean atualizarEstadoUsuario(String nomeUsuario){
int pos;
for(Iterator i = listaUsuarios.iterator(); i.hasNext(); ){
Usuario uAux = (Usuario) i.next();
if(uAux.getNome().equals(nomeUsuario)){
pos = listaUsuarios.indexOf(uAux);
listaUsuarios.remove(uAux);
uAux.setTempoInicio(new Date().getTime());
listaUsuarios.add(pos, uAux);
return true;
}
}
listaUsuarios.add(new Usuario(nomeUsuario, new Date().getTime()));
return false;
}
public void removerUsuariosOciosos(){
Usuario usuario = null;
for(Iterator i = listaUsuarios.iterator(); i.hasNext(); ){
usuario = (Usuario) i.next();
if(new Date().getTime() - usuario.getTempoInicio() > ESPERA){
desconectarUsuario(usuario.getNome());
i = listaUsuarios.iterator();
}
}
}
public void desconectarUsuario(String nomeUsuario){
for(Iterator i = listaUsuarios.iterator(); i.hasNext(); ){
Usuario uAux = (Usuario) i.next();
if(uAux.getNome().equals(nomeUsuario)){
i.remove();
break;
}
}
}
public void atualizarListaUsuarios(){
Vector<String> sVector = new Vector<String>();
Usuario uAux = null;
System.out.println("\nOnline: ");
for(Iterator i = listaUsuarios.iterator(); i.hasNext(); ){
uAux = (Usuario) i.next();
System.out.print( uAux.getNome() + " ");
sVector.add(uAux.getNome());
}
main.setUsuariosOnline(sVector);
}
private class Usuario{
private String nome;
private long tempoInicio;
public Usuario(){}
public Usuario(String nome, long tempoInicio){
this.nome = nome;
this.tempoInicio = tempoInicio;
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public long getTempoInicio() {
return tempoInicio;
}
public void setTempoInicio(long tempoInicio) {
this.tempoInicio = tempoInicio;
}
}
public void sair(){
try {
mcSocket.leaveGroup(InetAddress.getByName(MULTICAST_IP));
mcSocket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
EstouOnlineThread.java
package jsc;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
public class EstouOnlineThread extends Thread implements Constantes{
private MulticastSocket mcSocket;
private String nick;
private byte[] buffer;
public EstouOnlineThread(String nick){
super("EstouOnlineThread_" + nick);
this.nick = nick;
try {
mcSocket = new MulticastSocket();
} catch(IOException e) {
e.printStackTrace();
}
}
public void run(){
String saida = ESTOUONLINE + SEPARADOR + nick;
buffer = saida.getBytes();
while(true){
try{
DatagramPacket estouOnline = new DatagramPacket(buffer, buffer.length, InetAddress.getByName(MULTICAST_IP), MULTICAST_PORTA);
mcSocket.send(estouOnline);
System.out.println(saida);
sleep(ESPERA);
}
catch(InterruptedException e){
e.printStackTrace();
}
catch(IOException e){
e.printStackTrace();
}
}
}
}
MensagemPrivadaFrame.java
package jsc;
import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;
public class MensagemPrivadaFrame extends Frame implements Constantes{
private static final long serialVersionUID = 1L;
private TextArea entrada;
private TextField saida;
private String nomeJanela;
private String nick;
private String paraUsuario;
private MulticastSocket mcSocket;
private ActionListener saidaListener;
private WindowAdapter frameListener;
private boolean estouVivo; // indica que a janela ainda está ativa
public MensagemPrivadaFrame(String nick, String paraUsuario){
super("JSC - Chat com " + paraUsuario);
setIconImage(Toolkit.getDefaultToolkit().getImage("icone.4"));
this.nick = nick;
this.paraUsuario = paraUsuario;
this.nomeJanela = nick + paraUsuario;
try {
mcSocket = new MulticastSocket();
} catch (IOException e) {
e.printStackTrace();
}
iniciarComponentes();
estouVivo = true;
}
public void setNomeJanela(String nomeJanela){
this.nomeJanela = nomeJanela;
}
public String getNomeJanela(){
return nomeJanela;
}
public String getNick() {
return nick;
}
public void setNick(String nick) {
this.nick = nick;
}
public boolean estouVivo(){
return estouVivo;
}
public void iniciarComponentes(){
saidaListener = new ActionListener(){
public void actionPerformed(ActionEvent e){
TextField origem = (TextField) e.getSource();
enviarMensagem(origem.getText());
entrada.append("\n(" + nick + " diz) " + origem.getText());
origem.setText("");
}
};
frameListener = new WindowAdapter(){
public void windowClosing(WindowEvent e){
estouVivo = false;
dispose();
}
};
entrada = new TextArea("[JSC] Bate papo privado entre " + nick + " e " + paraUsuario + "\n");
entrada.setEditable(false);
saida = new TextField();
saida.addActionListener(saidaListener);
addWindowListener(frameListener);
setLayout(new BorderLayout());
int x = (int) (Math.random() * 500);
int y = (int) (Math.random() * 500);
setBounds(x, y, 400, 300);
System.out.println(x + " " + y);
add("Center", entrada);
add("South", saida);
setVisible(true);
saida.requestFocus();
}
public void setTextoEntrada(String texto){
entrada.append("\n" + texto);
entrada.setCaretPosition(entrada.getText().length());
}
public void enviarMensagem(String mensagem){
try{
mensagem = PRIVADO + SEPARADOR + nick + SEPARADOR + paraUsuario + SEPARADOR + mensagem;
byte[] bMensagem = mensagem.getBytes();
DatagramPacket pacote = new DatagramPacket(bMensagem, bMensagem.length, InetAddress.getByName(MULTICAST_IP), MULTICAST_PORTA);
mcSocket.send(pacote);
}
catch(UnknownHostException e){
e.printStackTrace();
}
catch(IOException e){
e.printStackTrace();
}
}
}
Main.java
package jsc;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.ScrollPane;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;
import java.util.Iterator;
import java.util.Vector;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
public class Main extends Frame implements Constantes{
private static final long serialVersionUID = 1L;
private TextArea entrada;
private TextField saida;
private JList usuariosOnline;
private ScrollPane usuariosOnlineScroll;
private WindowAdapter mainListener;
private ActionListener saidaListener;
private MouseAdapter listListener;
private MulticastSocket mcSocket; // soquete para multicasting
private Vector<String> listaUsuariosOnline; // lista com os nomes de usuários online
private Vector<MensagemPrivadaFrame> listaJanelasAbertas; // janelas de conversação privadas abertas
private String nick; // nome do usuário no chat
public void setNick(String nick){
this.nick = nick;
}
public String getNick(){
return nick;
}
public Main(String nick){
super("Java Socket Chat [" + nick + "]");
setIconImage(Toolkit.getDefaultToolkit().getImage("icone.1"));
this.nick = nick;
listaUsuariosOnline = new Vector<String>();
listaUsuariosOnline.add(nick);
listaJanelasAbertas = new Vector<MensagemPrivadaFrame>();
try{
mcSocket = new MulticastSocket();
}
catch(IOException e){
e.printStackTrace();
}
iniciarComponentes();
new EstouOnlineThread(nick).start();
new ControladorThread(this).start();
}
public void iniciarComponentes(){
mainListener = new WindowAdapter(){
public void windowClosing(WindowEvent e){
sair();
}
};
saidaListener = new ActionListener(){
public void actionPerformed(ActionEvent e){
TextField origem = (TextField) e.getSource();
enviarMensagem(origem.getText());
origem.setText("");
}
};
listListener = new MouseAdapter(){
public void mouseClicked(MouseEvent e){
if( e.getClickCount() >= 2 ){
// abrir a janela para mensagens privadas e passar o id do usuário
JList jlAux = (JList) e.getSource();
String paraUsuario = (String) jlAux.getSelectedValue();
abrirChatPrivado(nick, paraUsuario, null);
}
}
};
usuariosOnline = new JList(listaUsuariosOnline);
usuariosOnline.setSize(new Dimension(60, 280));
usuariosOnlineScroll = new ScrollPane();
usuariosOnlineScroll.add(usuariosOnline);
entrada = new TextArea("Olá " + nick);
entrada.setEditable(false);
entrada.setSize(300,280);
saida = new TextField();
saida.addActionListener(saidaListener);
usuariosOnline.addMouseListener(listListener);
usuariosOnline.setMinimumSize(new Dimension(60, 250));
addWindowListener(mainListener);
setSize(400, 300);
setLayout(new BorderLayout());
add("North", new JLabel("Java Socket ChatO"));
add("Center", entrada);
add("South", saida);
add("East", usuariosOnlineScroll);
setVisible(true);
requestFocus();
}
public void enviarMensagem(String mensagem){
try{
mensagem = nick + SEPARADOR + mensagem;
byte[] bMensagem = mensagem.getBytes();
DatagramPacket pacote = new DatagramPacket(bMensagem, bMensagem.length, InetAddress.getByName(MULTICAST_IP), MULTICAST_PORTA);
mcSocket.send(pacote);
}
catch(UnknownHostException e){
e.printStackTrace();
}
catch(IOException e){
e.printStackTrace();
}
}
private void desconectando(){
try{
String mensagem = "Desconectando" + SEPARADOR + nick;
byte[] bMensagem = mensagem.getBytes();
DatagramPacket pacote = new DatagramPacket(bMensagem, bMensagem.length, InetAddress.getByName(MULTICAST_IP), MULTICAST_PORTA);
mcSocket.send(pacote);
}
catch(UnknownHostException e){
e.printStackTrace();
}
catch(IOException e){
e.printStackTrace();
}
}
public void abrirChatPrivado(String nick, String paraUsuario, String mensagem){
removerJanelasInativas();
if(nick.equals(paraUsuario)){
JOptionPane.showMessageDialog(null, "Você não pode abrir um janela de conversação para você mesmo!", "Burro!", JOptionPane.ERROR_MESSAGE);
return;
}
String nome = nick + paraUsuario;
MensagemPrivadaFrame janela = null;
for(Iterator i = listaJanelasAbertas.iterator(); i.hasNext();){
janela = (MensagemPrivadaFrame) i.next();
if(nome.equals(janela.getNomeJanela())){
System.out.println(nick + " - " + janela.getNomeJanela() + " - " + janela.toString());
janela.setTextoEntrada("(" + paraUsuario + " diz) " + mensagem);
//janela.requestFocus();
return;
}
}
janela = new MensagemPrivadaFrame(nick, paraUsuario);
if(mensagem != null)
janela.setTextoEntrada("(" + paraUsuario + " diz) " + mensagem);
listaJanelasAbertas.add(janela);
//janela.requestFocus();
}
public void removerJanelasInativas(){
MensagemPrivadaFrame janela = null;
for(Iterator i = listaJanelasAbertas.iterator(); i.hasNext(); ){
janela = (MensagemPrivadaFrame) i.next();
if( !janela.estouVivo()){
i.remove();
}
}
}
public void setTextoEntrada(String texto){
entrada.append("\n" + texto);
entrada.setCaretPosition(entrada.getText().length());
}
public void setUsuariosOnline(Vector<String> listaUsuariosOnline){
usuariosOnline.setListData(listaUsuariosOnline);
}
public void sair(){
desconectando();
dispose();
System.exit(0);
}
public static void main(String args[]){
String nick = JOptionPane.showInputDialog("Digite seu nome (max. 20 caracteres): ");
if(nick != null && !nick.equals("")){
if(nick.length() > 20)
nick = nick.substring(0, 20);
new Main(nick);
}
else
JOptionPane.showMessageDialog(null, "É necessário informar um nome para entrar no bate-papo");
//System.exit(0);
}
}
Nowadays I'm not proud of the code, but it really works.
Edit:
As some have suggested, I've made some code improvements (refactoring) and post the project on GitHub: https://github.com/jaumzera/javasocketchat
Hm, I was tempted to direct you to a java implementation of a server implementing imap protocol (eg. gavamail). But this, of cources, might also qualify as "old" code and for sure would kill your expectation (for being a non-standard solution). Nevertheless it is a proper reference fulfilling your (terse) specification.
What do we have?
We need a solution that should be in java.
It must implement an basic instant messaging system.
The later is problematic as it covers a really broad range of functionality. But "basic" seem to allow for a minimum solution.
So what is a minimum instant messaging system? Let's try with the following:
a client that is posting and retrieving messages.
a server that is storing posted messages for (later) retrieval
We also would need a policy of how a client would identify a proper server. Most trivial solution for the later aspect is using a "central" server with a well-known address. For more complex cases we would need to have the server and/or client functionality distributed across several instances and devise a scheme or policy for identifying the proper instance(s) for communication.
We leave out more complex semantics like having different users or messages being related to a system of categories or tags.
Now, we are down to having two components:
A server implementing two entry points:
POST_MESSAGE
receive a mesage form a client and store it for later retrieval
This immediatley is asking the question of where to store such messages (in a database or filesystem for persistency or simply within memory for a "messages live as long as the server is up" semantics)
LOOKUP_MESSAGE
select a suitable message from the stored ones (preferrably an unread one) and return to caller.
This could also return a set of messages (but think of restricting such set for cases where a caller has a severe backlog of messages)
It might be necessary to keep track of the messages already having been read, either by marking the messages or by maintaining seen status at the client. This could even be as simple as keeping time or ordinal of last message seen and send this information along with the LOOKUP_MESSAGE request.
A client needs to interact with a user on one hand and the service on the other hand.
It will take gat a new message from the user (likely on explicit request (e.g. send button) and call the POST_MESSAGE service at the related server.
It also will (likely regularly, could also be on explicit request (e.g. user is starting client)) poll the server for new messages. (Alternatively, you could devise a separate notification service that is used by the server to notify the client of new messages. What suits your "needs" is beyond your question.)
That's it.
So any example of a TCP based client/server application will form a perfect starting point for a straight implementation.
I should also mention that you could cut the specification logic within the client and delegate user interaction to a standard browser and implement the client application logic into a (web-)server instance (together or separate from the server part). Nevertheless, you still will have both (client/server) logical functionality according to above minimum specification.
Another aspect you should be aware of:
With some comments you mentioned "host" and "guest" attributions available in current messenger examples. This actually is a logical structure of a tagging system provided with those messengers. The messages are still sent from a client to a server and then being retrieved by other clients. Whether a client can see a message is determined by the client being eligible to the specific tag. E.g posting a message to a contact from yours (user b) just tags the message with the tag "for_user_b" and as such it is only visible to the poster and anybody that is also allowed to read "for_user_b" tag messages (user b in our example). So, please be aware that the logical structure of a messaging system is determined by the access and distribution policy and not by the physical distribution structure!
I am not even sure if this question is still being used or what but I liked the task and I thought:
why not?
Here is my implementation, as simple as it gets but without forgetting fundamental parts.
Written in pure Java, makes use of, among the rest, Sockets, Threads and SynchronizedList:
SimpleChat.java (Main)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class SimpleChat {
private static boolean isRunning = true;
private static Sender sender;
private static Receiver receiver;
public static void main(String[] args) throws IOException {
if(args.length < 3){
showUsage();
}
try {
receiver = new Receiver(Integer.parseInt(args[1]));
sender = new Sender(args[0], args[2], Integer.parseInt(args[3]));
} catch (InterruptedException e) {
showUsage();
}
// Read user input
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Chat started. Type '\\exit' to quit.");
while(isRunning) {
String input = br.readLine();
if(input.equals("\\exit")){
receiver.stop();
sender.stop();
isRunning = false;
} else {
sender.sendMessage(input);
}
}
}
static void showUsage(){
System.out.println("Usage: java SimpleChat.java listening_port target_IP target_port");
System.exit(1);
}
}
Receiver.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class Receiver {
private boolean isRunning = true;
public Receiver(int listeningPort) throws IOException {
Runnable receiverT = new Runnable() {
public void run() {
ServerSocket serverSocket;
try {
serverSocket = new ServerSocket(listeningPort);
Socket clientSocket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
while(isRunning) {
try {
System.out.println(in.readLine());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
};
new Thread(receiverT).start();
}
public void stop(){
isRunning = false;
}
}
Sender.java
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class Sender {
private boolean isRunning = true;
private volatile List<String> msgs;
public Sender(String username, String targetIP, int targetPort) throws InterruptedException, UnknownHostException, IOException {
msgs = Collections.synchronizedList(new ArrayList<String>());
Runnable senderT = new Runnable() {
public void run() {
try {
Socket socket = new Socket(targetIP, targetPort);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
while(isRunning) {
synchronized(msgs){
Iterator<String> it = msgs.iterator();
while(it.hasNext()){
out.println(username + ": " + it.next());
}
// Clear messages to send
msgs.clear();
}
}
out.close();
socket.close();
} catch (UnknownHostException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
};
new Thread(senderT).start();
}
public void stop(){
isRunning = false;
}
public void sendMessage(String msg){
synchronized(msgs){
msgs.add(msg);
}
}
}
As the 'showUsage()' says, use this program as follows:
java SimpleChat.java username listening_port target_ip target_port
Example:
java SimpleChat.java Supuhstar 1234 127.0.0.1 1234
[To talk to yourself]
I think you should clarify some details regarding what exactly you mean by a "basic instant messaging program" and what your objectives actually are regarding this project.
In a 2011 comment, you mentioned that there should be no "central hub", but in a more recent comment, you say that you would like something more in line with Skype or iMessage, where users do not have to know which peer is the server... It is technically possible (using protocols such as mdns, dlna or ssdp) for the program to transparently search the local network for potential existing server nodes and have it either connect to the server peer if there is one, or establish itself as a local server for other nodes to connect to it. That is for example how Apple iChat's Bonjour protocol used to work. This is however a rather complex solution to implement right, and is definitely not in line with what is done by current mass market messaging programs.
Also establishing direct peer-to-peer communication between users pose several practical issues (particularly because of firewalls and NAT, but there are also concerns of confidentiality and security). Most protocols therefore relay most messages through the central server, and negotiate a direct connection only for the purpose of file transfers and audio/video calls.
For all these reasons, unless you are looking merely for an example of local network communication between two hosts, you most certainly want two distinct programs: a server and a client.
Then, assuming my assumption is correct, there are two other questions that need to be clarified. First, do you have an actual reason to conceive the protocol by yourself, or would it be acceptable to rather implement an existing protocol (such as XMPP, IRC, SIMPLE... there are tons). Even though these protocols might looks highly complex at first, it is almost always possible to implement only a subset these protocol's features/messages. Designing a naive network protocol by yourself isn't that difficult, but there are tons of potential mistakes (mostly inefficiencies, incompleteness and other minor issues) that you will have to go through. Maybe that is indeed what you are specifically aiming for (that is, gaining experience at designing a network protocol from scratch), but unless it is so, you should seriously opt for implementing an existing protocol. Indeed, working with an existing protocol will not only avoid such design mistakes, but better yet, you will gain significant knowledge from how others (generally experienced protocol designers) actually resolved problems they met along the way. Using an existing protocol will also make it much easier and more interesting for you to develop that program, given that you will for example be able to test your client and server programs independently by connecting from/to an official client/server implementation. You will also be able to exploit exiting protocol-decoders in traffic sniffing tools in order to debug messages going through.
The second important question is how realistic you would like the server program to be, and most importantly in regard to persistance. Should the server maintain a persistant list of users and authenticate them? Should the server store a list of allowed contacts for each user? Should the server allow store messages aimed at a peer that is currently offline or that can't be reached at that exact moment? Real messaging server programs generally do such things, and though implementing such mechanisms isn't highly difficult, it is best considered early in the design of a program's architecture. For example, should you decide that these features are indeed desirable, then it might turn out to be much more interesting for you to immediately design your server around a persistant message queue engine, such as ActiveMQ...
I know this is not the examples you were asking for, but I still hope these thoughts may help you.
As said before, there is a lot of things that you need to put a real chat to work.
But i belive that you want something to start. And if you know the address and the port of the other "client" it is easy.
Extreme simple "chat" implementation
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
public class SimpleChat {
protected boolean running = true;
protected int port;
private Thread server;
public static void main(String... arg) {
//create 2 clients in the localhost to test
SimpleChat app1 = new SimpleChat(8989);
SimpleChat app2 = new SimpleChat(8988);
app1.sendMessage("localhost", 8988, "Message from app1 to app2");
app2.sendMessage("localhost", 8989, "Message from app2 to app1");
System.exit(0); // ugly way to kill the threads and exit
}
public SimpleChat(int port) {
this.port = port;
start();
}
public void start() {
server = new Thread(new Server());
server.start();
}
public boolean sendMessage(String host, int port, String message) {
try {
//Connect to a server on given host and port and "send" the message
InetSocketAddress destination
= new InetSocketAddress(host, port);
Socket s = SocketFactory.getDefault().createSocket();
s.connect(destination);
OutputStream out = s.getOutputStream();
out.write(message.getBytes());
out.flush();
out.close();
s.close();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public void messageRecived(String message) {
System.out.println("Message recived: " + message);
}
public void stop() {
this.running = false; // only stop after a socked connection
}
class Server implements Runnable {
public void run() {
try {
//Create a server socket to recieve the connection
ServerSocket ss = ServerSocketFactory.getDefault()
.createServerSocket(port);
while (running) {
Socket s = ss.accept();
InputStream in = s.getInputStream();
StringBuilder message = new StringBuilder();
int len;
byte[] buf = new byte[2048];
while ((len = in.read(buf)) > -1) {
if (len > 0) {
message.append(new String(buf, 0, len));
}
}
messageRecived(message.toString());
}
} catch (Exception e) {
e.printStackTrace();
System.exit(-1);
}
}
}
}

Pipe transfer blocks when writing object with small pipe size

I'm having a bit of problem with an example I'm currently testing. For some reason, the execution blocks when writing at oos.writeObject(new SimpleObject());, despite that fact that the pipe should transfer the data across, even (I'd assume) if it had to do it in smaller operations due to a small pipe size. Anyway, the example succeeds when the pipe size is larger than the object, and fails when the pipe size is smaller than the object. If anyone could shed some light on this, it'd be much appreciated.
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.nio.ByteBuffer;
import org.apache.mina.filter.codec.serialization.ObjectSerializationInputStream;
import org.apache.mina.filter.codec.serialization.ObjectSerializationOutputStream;
public class ObjTest4 {
public static void main(String[] args) {
System.out.println("exec1");
int objectsToSend = 10;
int objectsRecvd = 0;
try {
System.out.println("exec2");
PipedOutputStream pos = new PipedOutputStream();
ObjectSerializationOutputStream oos = new ObjectSerializationOutputStream(pos);
PipedInputStream pis = new PipedInputStream(pos, 500);
ObjectSerializationInputStream ois = new ObjectSerializationInputStream(pis);
oos.setMaxObjectSize(2000);
ois.setMaxObjectSize(2000);
while (objectsRecvd < objectsToSend) {
System.out.println("exec3");
oos.writeObject(new SimpleObject());
System.out.println("exec3.1");
oos.flush();
System.out.println("exec3.2");
System.out.println("oisavail: " + ois.available());
Object o = ois.readObject();
if (o != null) {
objectsRecvd++;
System.out.println("o: " + o);
} else {
System.out.println("recvd null");
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
and the class for the object being serialised:
package objtest;
import java.io.Serializable;
import java.util.EnumSet;
public class SimpleObject implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
public String moo = "moo";
public EnumSet<EnumTest> set = EnumSet.of(EnumTest.Test);
public String moo2 = "moo2";
public String moo3 = "moo3";
public String moo4 = "moo4_";
{
for (int i = 0; i < 8; i++) {
moo4 += moo4;
}
}
/**
*
*/
public SimpleObject() {
// TODO Auto-generated constructor stub
}
/**
* #return the moo
*/
public String getMoo() {
return moo;
}
/**
* #param moo the moo to set
*/
public void setMoo(String moo) {
this.moo = moo;
}
}
Cheers,
Chris
Sorry, I found the problem -- the Java documentation says not to use piped streams from a single thread, as it may deadlock the thread:
Attempting to use both objects from a
single thread is not recommended, as
it may deadlock the thread.

Categories

Resources