AppInventor2 TCP client extension crashes the app - java

I have been using a tcp client extension by Jean-Rodolphe Letertre for app inventor 2 and it works flawlessly until you call the disconnect method and than it crashes the app. After looking at the code for the extension i found that disconnect only shuts down output, input and than closes the socket which shouldn't cause any crashes so my suspicion fell on the connect method which runs a thread because it keeps reading data in a loop from a tcp socket and when we call disconnect we don't finish the thread which causes application crash because input is closed and an exception goes unhandled.
NOTE: The code is not mine and i don't ask to fix it for me i only want to know if iv'e found the problem which causes crashes and if so i will fix it myself. Thanks in advance for any help!
The code:
// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2009-2011 Google, All Rights reserved
// Copyright 2011-2012 MIT, All rights reserved
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0
package com.gmail.at.moicjarod;
import com.google.appinventor.components.runtime.*;
import com.google.appinventor.components.runtime.util.RuntimeErrorAlert;
import com.google.appinventor.components.annotations.DesignerComponent;
import com.google.appinventor.components.annotations.DesignerProperty;
import com.google.appinventor.components.annotations.PropertyCategory;
import com.google.appinventor.components.annotations.SimpleEvent;
import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.annotations.SimpleObject;
import com.google.appinventor.components.annotations.SimpleProperty;
import com.google.appinventor.components.annotations.UsesLibraries;
import com.google.appinventor.components.annotations.UsesPermissions;
import com.google.appinventor.components.common.ComponentCategory;
import com.google.appinventor.components.common.PropertyTypeConstants;
import com.google.appinventor.components.runtime.util.AsynchUtil;
import com.google.appinventor.components.runtime.util.ErrorMessages;
import com.google.appinventor.components.runtime.util.YailList;
import com.google.appinventor.components.runtime.util.SdkLevel;
import com.google.appinventor.components.runtime.errors.YailRuntimeError;
import android.app.Activity;
import android.text.TextUtils;
import android.util.Log;
import android.os.StrictMode;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.InetSocketAddress;
import java.net.SocketException;
/**
* Simple Client Socket
* #author moicjarod#gmail.com (Jean-Rodolphe Letertre)
* with the help of the work of lizlooney # google.com (Liz Looney) and josmasflores # gmail.com (Jose Dominguez)
* the help of Alexey Brylevskiy for debugging
* and the help of Hossein Amerkashi from AppyBuilder for compatibility with AppyBuilder
*/
#DesignerComponent(version = 4,
description = "Non-visible component that provides client socket connectivity.",
category = ComponentCategory.EXTENSION,
nonVisible = true,
iconName = "http://jr.letertre.free.fr/Projets/AIClientSocket/clientsocket.png")
#SimpleObject(external = true)
#UsesPermissions(permissionNames = "android.permission.INTERNET")
public class ClientSocketAI2Ext extends AndroidNonvisibleComponent implements Component
{
private static final String LOG_TAG = "ClientSocketAI2Ext";
private final Activity activity;
// the socket object
private Socket clientSocket = null;
// the address to connect to
private String serverAddress = "";
// the port to connect to
private String serverPort = "";
// boolean that indicates the state of the connection, true = connected, false = not connected
private boolean connectionState = false;
// boolean that indicates the mode used, false = string sent as is, true = String is considered as hexadecimal data and will be converted before sending
// same behavior is used when receiving data
private boolean hexaStringMode = false;
InputStream inputStream = null;
/**
* Creates a new Client Socket component.
*
* #param container the Form that this component is contained in.
*/
public ClientSocketAI2Ext(ComponentContainer container)
{
super(container.$form());
activity = container.$context();
// compatibility with AppyBuilder (thx Hossein Amerkashi <kkashi01 [at] gmail [dot] com>)
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}
/**
* Method that returns the server's address.
*/
#SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "The address of the server the client will connect to.")
public String ServerAddress()
{
return serverAddress;
}
/**
* Method to specify the server's address
*/
#DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING)
#SimpleProperty
public void ServerAddress(String address)
{
serverAddress = address;
}
/**
* Method that returns the server's port.
*/
#SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "The port of the server the client will connect to.")
public String ServerPort()
{
return serverPort;
}
/**
* Method to specify the server's port
*/
#DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING)
#SimpleProperty
public void ServerPort(String port)
{
serverPort = port;
}
/**
* Method that returns the connection state
*/
#SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "The state of the connection - true = connected, false = disconnected")
public boolean ConnectionState()
{
return connectionState;
}
/**
* Method that returns the mode (string or hexastring)
*/
#SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "The mode of sending and receiving data.")
public boolean HexaStringMode()
{
return hexaStringMode;
}
/**
* Method to specify the mode (string or hexastring)
*/
#DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN)
#SimpleProperty
public void HexaStringMode(boolean mode)
{
hexaStringMode = mode;
}
/**
* Creates the socket, connect to the server and launches the thread to receive data from server
*/
#SimpleFunction(description = "Tries to connect to the server and launches the thread for receiving data (blocking until connected or failed)")
public void Connect()
{
if (connectionState == true)
{
throw new YailRuntimeError("Connect error, socket connected yet, please disconnect before reconnect !", "Error");
}
try
{
// connecting the socket
clientSocket = new Socket();
clientSocket.connect(new InetSocketAddress(serverAddress, Integer.parseInt(serverPort)), 5000);
connectionState = true;
// begin the receive loop in a new thread
AsynchUtil.runAsynchronously(new Runnable()
{
#Override
public void run()
{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(1024);
byte[] buffer = new byte[1024];
int bytesRead;
try
{
// get the input stream and save the data
inputStream = clientSocket.getInputStream();
while (true)
{
// test if there is a server problem then close socket properly (thx Axeley :-))
try
{
bytesRead = inputStream.read(buffer);
if(bytesRead == -1)
break;
}
catch(SocketException e)
{
if(e.getMessage().indexOf("ETIMEDOUT") >= 0)
break;
throw e;
}
byteArrayOutputStream.write(buffer, 0, bytesRead);
final String dataReceived;
// hexaStringMode is false, so we don't transform the string received
if (hexaStringMode == false)
{
dataReceived = byteArrayOutputStream.toString("UTF-8");
}
// hexaStringMode is true, so we make a string with each character as an hexa symbol representing the received message
else
{
int i;
char hexaSymbol1, hexaSymbol2;
String tempData = "";
byte[] byteArray = byteArrayOutputStream.toByteArray();
for (i = 0; i < byteArrayOutputStream.size(); i++)
{
if (((byteArray[i] & 0xF0) >> 4) < 0xA)
// 0 to 9 symbol
hexaSymbol1 = (char)(((byteArray[i] & 0xF0) >> 4) + 0x30);
else
// A to F symbol
hexaSymbol1 = (char)(((byteArray[i] & 0xF0) >> 4) + 0x37);
if ((byteArray[i] & 0x0F) < 0xA)
hexaSymbol2 = (char)((byteArray[i] & 0x0F) + 0x30);
else
hexaSymbol2 = (char)((byteArray[i] & 0x0F) + 0x37);
tempData = tempData + hexaSymbol1 + hexaSymbol2;
}
dataReceived = tempData;
}
// reset of the byteArrayOutputStream to flush the content
byteArrayOutputStream.reset();
// then we send the data to the user using an event
// events must be sent by the main thread (UI)
activity.runOnUiThread(new Runnable()
{
#Override
public void run()
{
DataReceived(dataReceived);
}
} );
}
// When we go there, either we have
// - server shutdown
// - disconnection asked (inputstream closed => -1 returned)
// - connection problem
// so, if it is not disconnected yet, we disconnect the socket and inform the user of it.
if (connectionState == true)
{
Disconnect();
// events must be sent by the main thread (UI)
activity.runOnUiThread(new Runnable()
{
#Override
public void run()
{
RemoteConnectionClosed();
}
} );
}
}
catch (SocketException e)
{
Log.e(LOG_TAG, "ERROR_READ", e);
throw new YailRuntimeError("Connect error (read)" + e.getMessage(), "Error");
}
catch (IOException e)
{
Log.e(LOG_TAG, "ERROR_READ", e);
throw new YailRuntimeError("Connect error (read)", "Error");
}
catch (Exception e)
{
connectionState = false;
Log.e(LOG_TAG, "ERROR_READ", e);
throw new YailRuntimeError("Connect error (read)" + e.getMessage(), "Error");
}
}
} );
}
catch (SocketException e)
{
Log.e(LOG_TAG, "ERROR_CONNECT", e);
throw new YailRuntimeError("Connect error" + e.getMessage(), "Error");
}
catch (Exception e)
{
connectionState = false;
Log.e(LOG_TAG, "ERROR_CONNECT", e);
throw new YailRuntimeError("Connect error (Socket Creation)" + e.getMessage(), "Error");
}
}
/**
* Send data through the socket to the server
*/
#SimpleFunction(description = "Send data to the server")
public void SendData(final String data)
{
final byte [] dataToSend;
byte [] dataCopy = data.getBytes();
if (connectionState == false)
{
throw new YailRuntimeError("Send error, socket not connected.", "Error");
}
if (hexaStringMode == false)
{
//dataSend = new byte [data.length()];
// if hexaStringMode is false, we send data as is
dataToSend = data.getBytes();
}
else
{
// if hexaStringMode is true, we begin to verify we can transcode the symbols
// verify if the data we want to send contains only hexa symbols
int i;
for (i = 0; i < data.length(); i++)
{
if (((dataCopy[i] < 0x30) || (dataCopy[i] > 0x39)) && ((dataCopy[i] < 0x41) || (dataCopy[i] > 0x46)) && ((dataCopy[i] < 0x61) || (dataCopy[i] > 0x66)))
throw new YailRuntimeError("Send data : hexaStringMode is selected and non hexa symbol found in send String.", "Error");
}
// verify that the number of symbols is even
if ((data.length() %2) == 1)
{
throw new YailRuntimeError("Send data : hexaStringMode is selected and send String length is odd. Even number of characters needed.", "Error");
}
// if all tests pass, we transcode the data :
dataToSend=new byte[data.length()/2+1];
for (i = 0; i < data.length(); i=i+2)
{
byte [] temp1 = new byte [2];
temp1 [0] = dataCopy[i];
temp1 [1] = dataCopy[i+1];
String temp2 = new String (temp1);
dataToSend[i/2]=(byte)Integer.parseInt(temp2, 16);
}
// end of c-type string character
dataToSend[i/2] = (byte)0x00;
}
// we then send asynchonously the data
AsynchUtil.runAsynchronously(new Runnable()
{
#Override
public void run()
{
try
{
OutputStream out;
out = clientSocket.getOutputStream();
out.write(dataToSend);
}
catch (SocketException e)
{
Log.e(LOG_TAG, "ERROR_SEND", e);
throw new YailRuntimeError("Send data" + e.getMessage(), "Error");
}
catch (Exception e)
{
Log.e(LOG_TAG, "ERROR_UNABLE_TO_SEND_DATA", e);
throw new YailRuntimeError("Send Data", "Error");
}
}
} );
}
/**
* Close the socket
*/
#SimpleFunction(description = "Disconnect to the server")
public void Disconnect()
{
if (connectionState == true)
{
connectionState = false;
try
{
// shutdown the input socket,
clientSocket.shutdownInput();
clientSocket.shutdownOutput();
clientSocket.close();
}
catch (SocketException e)
{
// modifications by axeley too :-)
if(e.getMessage().indexOf("ENOTCONN") == -1)
{
Log.e(LOG_TAG, "ERROR_CONNECT", e);
throw new YailRuntimeError("Disconnect" + e.getMessage(), "Error");
}
// if not connected, then just ignore the exception
}
catch (IOException e)
{
Log.e(LOG_TAG, "ERROR_CONNECT", e);
throw new YailRuntimeError("Disconnect" + e.getMessage(), "Error");
}
catch (Exception e)
{
Log.e(LOG_TAG, "ERROR_CONNECT", e);
throw new YailRuntimeError("Disconnect" + e.getMessage(), "Error");
}
finally
{
clientSocket=null;
}
}
else
throw new YailRuntimeError("Socket not connected, can't disconnect.", "Error");
}
/**
* Event indicating that a message has been received
*
* #param data the data sent by the server
*/
#SimpleEvent
public void DataReceived(String data)
{
// invoke the application's "DataReceived" event handler.
EventDispatcher.dispatchEvent(this, "DataReceived", data);
}
/**
* Event indicating that the remote socket closed the connection
*
*/
#SimpleEvent
public void RemoteConnectionClosed()
{
// invoke the application's "RemoteConnectionClosed" event handler.
EventDispatcher.dispatchEvent(this, "RemoteConnectionClosed");
}
}

By commenting the following lines, it could be achieved:
throw new YailRuntimeError("...
The problem on this approach is that we wouldn't be able do know the disconnection reason, however we must agree that these messages are rather informative than functional in the sense of the application workflow, therefore an alternative option is to add the ability to disable these calls in runtime.

Related

ClassNotFoundException across different machines

I am trying to write a messaging application in java, but keep getting an error. When I run the client on server on the same device, I get the same message. However, when I run the client and server on the same device, the client can receive messages from the server, but is unable to send a message. If I do try to send a message from the separate machine, I get the following errors:
Server has close the connection: java.io.EOFException
java.lang.ClassNotFoundException: ChatMessage
I do not know what this error means, and do not know how to fix it. I would very much appreciate some help and precise instructions on how to fix this error, and prevent it in the future.
Server Class:
public class Server {
// a unique ID for each connection
private static int uniqueId;
// an ArrayList to keep the list of the Client
private ArrayList<ClientThread> al;
// if I am in a GUI
private ServerGUI sg;
// to display time
private SimpleDateFormat sdf;
// the port number to listen for connection
private int port;
// the boolean that will be turned of to stop the server
private boolean keepGoing;
private Socket socket;
/*
* server constructor that receive the port to listen to for connection as parameter
* in console
*/
public Server(int port) {
this(port, null);
}
public Server(int port, ServerGUI sg) {
// GUI or not
this.sg = sg;
// the port
this.port = port;
// to display hh:mm:ss
sdf = new SimpleDateFormat("HH:mm:ss");
// ArrayList for the Client list
al = new ArrayList<ClientThread>();
}
public void start() {
keepGoing = true;
/* create socket server and wait for connection requests */
try
{
// the socket used by the server
ServerSocket serverSocket = new ServerSocket(port);
// infinite loop to wait for connections
while(keepGoing)
{
// format message saying we are waiting
display("Server waiting for Clients on port " + port + ".");
Socket socket = serverSocket.accept(); // accept connection
// if I was asked to stop
if(!keepGoing)
break;
ClientThread t = new ClientThread(socket); // make a thread of it
al.add(t); // save it in the ArrayList
t.start();
}
// I was asked to stop
try {
serverSocket.close();
for(int i = 0; i < al.size(); ++i) {
ClientThread tc = al.get(i);
try {
tc.sInput.close();
tc.sOutput.close();
tc.socket.close();
}
catch(IOException ioE) {
// not much I can do
}
}
}
catch(Exception e) {
display("Exception closing the server and clients: " + e);
}
}
// something went bad
catch (IOException e) {
String msg = sdf.format(new Date()) + " Exception on new ServerSocket: " + e + "\n";
display(msg);
}
}
/*
* For the GUI to stop the server
*/
protected void stop() {
keepGoing = false;
// connect to myself as Client to exit statement
// Socket socket = serverSocket.accept();
try {
new Socket("localhost", port);
}
catch(Exception e) {
// nothing I can really do
}
}
/*
* Display an event (not a message) to the console or the GUI
*/
private void display(String msg) {
String time = sdf.format(new Date()) + " " + msg;
if(sg == null)
System.out.println(time);
else
sg.appendEvent(time + "\n");
}
/*
* to broadcast a message to all Clients
*/
private synchronized void broadcast(String message) {
// add HH:mm:ss and \n to the message
String time = sdf.format(new Date());
String messageLf = time + " " + message + "\n";
// display message on console or GUI
if(sg == null)
System.out.print(messageLf);
else
sg.appendRoom(messageLf); // append in the room window
// we loop in reverse order in case we would have to remove a Client
// because it has disconnected
for(int i = al.size(); --i >= 0;) {
ClientThread ct = al.get(i);
// try to write to the Client if it fails remove it from the list
if(!ct.writeMsg(messageLf)) {
al.remove(i);
display("Disconnected Client " + ct.username + " removed from list.");
}
}
}
// for a client who logoff using the LOGOUT message
synchronized void remove(int id) {
// scan the array list until we found the Id
for(int i = 0; i < al.size(); ++i) {
ClientThread ct = al.get(i);
// found it
if(ct.id == id) {
al.remove(i);
return;
}
}
}
/*
* To run as a console application just open a console window and:
* > java Server
* > java Server portNumber
* If the port number is not specified 1500 is used
*/
public static void main(String[] args) {
// start server on port 1500 unless a PortNumber is specified
int portNumber = 1500;
switch(args.length) {
case 1:
try {
portNumber = Integer.parseInt(args[0]);
}
catch(Exception e) {
System.out.println("Invalid port number.");
System.out.println("Usage is: > java Server [portNumber]");
return;
}
case 0:
break;
default:
System.out.println("Usage is: > java Server [portNumber]");
return;
}
// create a server object and start it
Server server = new Server(portNumber);
server.start();
}
/** One instance of this thread will run for each client */
class ClientThread extends Thread {
// the socket where to listen/talk
Socket socket;
ObjectInputStream sInput;
ObjectOutputStream sOutput;
// my unique id (easier for deconnection)
int id;
// the Username of the Client
String username;
// the only type of message a will receive
ChatMessage cm;
// the date I connect
String date;
// Constructore
ClientThread(Socket socket) {
// a unique id
id = ++uniqueId;
this.socket = socket;
/* Creating both Data Stream */
System.out.println("Thread trying to create Object Input/Output Streams");
try
{
// create output first
sOutput = new ObjectOutputStream(socket.getOutputStream());
sInput = new ObjectInputStream(socket.getInputStream());
// read the username
username = (String) sInput.readObject();
display(username + " just connected.");
}
catch (IOException e) {
display("Exception creating new Input/output Streams: " + e);
return;
}
// have to catch ClassNotFoundException
// but I read a String, I am sure it will work
catch (ClassNotFoundException e) {
}
date = new Date().toString() + "\n";
}
// what will run forever
public void run() {
// to loop until LOGOUT
boolean keepGoing = true;
while(keepGoing) {
// read a String (which is an object)
try {
cm = (ChatMessage) sInput.readObject();
}
catch (IOException e) {
display(username + " Exception reading Streams: " + e);
break;
}
catch(ClassNotFoundException e2) {
display(e2.toString());
break;
}
// the messaage part of the ChatMessage
String message = cm.getMessage();
// Switch on the type of message receive
switch(cm.getType()) {
case ChatMessage.MESSAGE:
broadcast(username + ": " + message);
break;
case ChatMessage.LOGOUT:
display(username + " disconnected with a LOGOUT message.");
keepGoing = false;
break;
case ChatMessage.WHOISIN:
writeMsg("List of the users connected at " + sdf.format(new Date()) + "\n");
// scan al the users connected
for(int i = 0; i < al.size(); ++i) {
ClientThread ct = al.get(i);
writeMsg((i+1) + ") " + ct.username + " since " + ct.date);
}
break;
}
}
// remove myself from the arrayList containing the list of the
// connected Clients
remove(id);
close();
}
// try to close everything
private void close() {
// try to close the connection
try {
if(sOutput != null) sOutput.close();
}
catch(Exception e) {}
try {
if(sInput != null) sInput.close();
}
catch(Exception e) {};
try {
if(socket != null) socket.close();
}
catch (Exception e) {}
}
/*
* Write a String to the Client output stream
*/
private boolean writeMsg(String msg) {
// if Client is still connected send the message to it
if(!socket.isConnected()) {
close();
return false;
}
// write the message to the stream
try {
sOutput.writeObject(msg);
}
// if an error occurs, do not abort just inform the user
catch(IOException e) {
display("Error sending message to " + username);
display(e.toString());
}
return true;
}
}
}
Client Class:
public class Client {
// for I/O
private ObjectInputStream sInput; // to read from the socket
private ObjectOutputStream sOutput; // to write on the socket
private Socket socket;
// if I use a GUI or not
private ClientGUI cg;
// the server, the port and the username
private String server, username;
private int port;
/*
* Constructor called by console mode
* server: the server address
* port: the port number
* username: the username
*/
Client(String server, int port, String username) {
// which calls the common constructor with the GUI set to null
this(server, port, username, null);
}
/*
* Constructor call when used from a GUI
* in console mode the ClienGUI parameter is null
*/
Client(String server, int port, String username, ClientGUI cg) {
this.server = server;
this.port = port;
this.username = username;
// save if we are in GUI mode or not
this.cg = cg;
}
/*
* To start the dialog
*/
public boolean start() {
// try to connect to the server
try {
socket = new Socket(server, port);
}
// if it failed not much I can so
catch(Exception ec) {
display("Error connectiong to server:" + ec);
return false;
}
String msg = "Connection accepted " + socket.getInetAddress() + ":" + socket.getPort();
display(msg);
/* Creating both Data Stream */
try
{
sInput = new ObjectInputStream(socket.getInputStream());
sOutput = new ObjectOutputStream(socket.getOutputStream());
}
catch (IOException eIO) {
display("Exception creating new Input/output Streams: " + eIO);
return false;
}
// creates the Thread to listen from the server
new ListenFromServer().start();
// Send our username to the server this is the only message that we
// will send as a String. All other messages will be ChatMessage objects
try
{
sOutput.writeObject(username);
}
catch (IOException eIO) {
display("Exception doing login : " + eIO);
disconnect();
return false;
}
// success we inform the caller that it worked
return true;
}
/*
* To send a message to the console or the GUI
*/
private void display(String msg) {
if(cg == null)
System.out.println(msg); // println in console mode
else
cg.append(msg + "\n"); // append to the ClientGUI JTextArea (or whatever)
}
/*
* To send a message to the server
*/
void sendMessage(ChatMessage msg) {
try {
sOutput.writeObject(msg);
}
catch(IOException e) {
display("Exception writing to server: " + e);
}
}
/*
* When something goes wrong
* Close the Input/Output streams and disconnect not much to do in the catch clause
*/
private void disconnect() {
try {
if(sInput != null) sInput.close();
}
catch(Exception e) {} // not much else I can do
try {
if(sOutput != null) sOutput.close();
}
catch(Exception e) {} // not much else I can do
try{
if(socket != null) socket.close();
}
catch(Exception e) {} // not much else I can do
// inform the GUI
if(cg != null)
cg.connectionFailed();
}
/*
* To start the Client in console mode use one of the following command
* > java Client
* > java Client username
* > java Client username portNumber
* > java Client username portNumber serverAddress
* at the console prompt
* If the portNumber is not specified 1500 is used
* If the serverAddress is not specified "localHost" is used
* If the username is not specified "Anonymous" is used
* > java Client
* is equivalent to
* > java Client Anonymous 1500 localhost
* are eqquivalent
*
* In console mode, if an error occurs the program simply stops
* when a GUI id used, the GUI is informed of the disconnection
*/
public static void main(String[] args) {
// default values
int portNumber = 1500;
String serverAddress = "localHost";
String userName = "Anonymous";
// depending of the number of arguments provided we fall through
switch(args.length) {
// > javac Client username portNumber serverAddr
case 3:
serverAddress = args[2];
// > javac Client username portNumber
case 2:
try {
portNumber = Integer.parseInt(args[1]);
}
catch(Exception e) {
System.out.println("Invalid port number.");
System.out.println("Usage is: > java Client [username] [portNumber] [serverAddress]");
return;
}
// > javac Client username
case 1:
userName = args[0];
// > java Client
case 0:
break;
// invalid number of arguments
default:
System.out.println("Usage is: > java Client [username] [portNumber] {serverAddress]");
return;
}
// create the Client object
Client client = new Client(serverAddress, portNumber, userName);
// test if we can start the connection to the Server
// if it failed nothing we can do
if(!client.start())
return;
// wait for messages from user
Scanner scan = new Scanner(System.in);
// loop forever for message from the user
while(true) {
System.out.print("> ");
// read message from user
String msg = scan.nextLine();
// logout if message is LOGOUT
if(msg.equalsIgnoreCase("LOGOUT")) {
client.sendMessage(new ChatMessage(ChatMessage.LOGOUT, ""));
// break to do the disconnect
break;
}
// message WhoIsIn
else if(msg.equalsIgnoreCase("WHOISIN")) {
client.sendMessage(new ChatMessage(ChatMessage.WHOISIN, ""));
}
else { // default to ordinary message
client.sendMessage(new ChatMessage(ChatMessage.MESSAGE, msg));
}
}
// done disconnect
client.disconnect();
}
/*
* a class that waits for the message from the server and append them to the JTextArea
* if we have a GUI or simply System.out.println() it in console mode
*/
class ListenFromServer extends Thread {
public void run() {
while(true) {
try {
String msg = (String) sInput.readObject();
// if console mode print the message and add back the prompt
if(cg == null) {
System.out.println(msg);
System.out.print("> ");
}
else {
cg.append(msg);
}
}
catch(IOException e) {
display("Server has close the connection: " + e);
if(cg != null)
cg.connectionFailed();
break;
}
// can't happen with a String object but need the catch anyhow
catch(ClassNotFoundException e2) {
}
}
}
}
}
ChatMessage Class:
public class ChatMessage implements Serializable {
protected static final long serialVersionUID = 1112122200L;
// The different types of message sent by the Client
// WHOISIN to receive the list of the users connected
// MESSAGE an ordinary message
// LOGOUT to disconnect from the Server
static final int WHOISIN = 0, MESSAGE = 1, LOGOUT = 2;
private int type;
private String message;
// constructor
ChatMessage(int type, String message) {
this.type = type;
this.message = message;
}
// getters
int getType() {
return type;
}
String getMessage() {
return message;
}
}
Note that I am able to send messages just fine if I load both the server and client on the same machine. If I load the server on one machine, and the client on another, however, the client can receive messages from the server, but the server cannot receive messages from the client. If I attempt to send a message from the client to the Server, it gives me the errors shown above.

restricting number of connections in java socket server

I have created a Java socket server which creates a socket server on a specified port and then spawns a RecordWriter object to perform some operation on the data stream obtained from each connection.
I start the program with port as 61000 and numthreads as 2.
I also started 3 clients to connect to it.
On the client side I could see that all 3 of them connected to the receiver however, the receiver logs indicated only two of them connected.
netstat -an|grep 61000|grep -i ESTABLISHED
indicated total 6 connections as the client and server are being run on the same machine.
My doubts are:
Why does the client log for the third time show that it could connect to the program on 61000 while I am using the backlog of 2. Also Executors.newFixedThreadPool(numThreads); is allowing only 2 clients to be connected.
Although the server.accept happens in the MyWriter.java and there is no indication in logs that the 3rd client could connect, why does netstat show this as an Established connection
Here are my codes:
MyReceiver.java
package com.vikas;
import java.net.ServerSocket;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class MyReceiver{
protected int serverPort = -1;
protected int numThreads = -1;
protected boolean isStopped = false;
protected Thread runningThread = null;
protected ExecutorService threadPool = null;
protected static Logger logger = LogManager.getLogger(MyReceiver.class);
protected static ServerSocket serverSocket = null;
protected static Map<String, String> mapConnections = new ConcurrentHashMap<String, String>();
public MyReceiver(int port){
this.serverPort = port;
}
public void run(int numThreads){
this.threadPool = Executors.newFixedThreadPool(numThreads);
try {
logger.info("Starting server on port " + this.serverPort);
MyReceiver.serverSocket = new ServerSocket(this.serverPort, numThreads);
} catch (IOException e) {
//throw new RuntimeException("Cannot open port " + this.serverPort, e);
logger.error("Cannot open port " + this.serverPort, e);
}
while(!isStopped()){
this.threadPool.execute(new MyWriter());
}
if(MyReceiver.mapConnections.isEmpty()){
this.threadPool.shutdown();
//System.out.println("Server Stopped after shutdown.") ;
logger.info("Server Stopped after shutdown.");
}
}
public synchronized boolean isStopped() {
return this.isStopped;
}
public synchronized void stop(){
this.isStopped = true;
try {
MyReceiver.serverSocket.close();
} catch (IOException e) {
//throw new RuntimeException("Error closing server", e);
logger.error("Error closing server", e);
}
}
public static void main(String[] args) {
if(args.length != 2){
System.out.println("Number of input arguements is not equal to 4.");
System.out.println("Usage: java -cp YOUR_CLASSPATH -Dlog4j.configurationFile=/path/to/log4j2.xml com.vikas.MyReceiver <port> <number of threads>");
System.out.println("java -cp \"$CLASSPATH:./MyReceiver.jar:./log4j-api-2.6.2.jar:./log4j-core-2.6.2.jar\" -Dlog4j.configurationFile=log4j2.xml com.vikas.MyReceiver 61000 2");
}
int port = Integer.parseInt(args[0].trim());
int numThreads = Integer.parseInt(args[1].trim());
final MyReceiver myConnection = new MyReceiver(port, topic, brokers);
myConnection.run(numThreads);
/*Thread t = new Thread(myConnection);
t.start();*/
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
//e.printStackTrace();
logger.error("Something went wrong", e);
}
//System.out.println("Stopping Server");
Runtime.getRuntime().addShutdownHook(new Thread()
{
#Override
public void run()
{
logger.info("SocketServer - Receive SIGINT!!!");
logger.info("Stopping Server");
if(!myConnection.isStopped()){
myConnection.stop();
}
logger.info("Server Stopped successfully");
try
{
Thread.sleep(1000);
}
catch (Exception e) {}
}
});
//myConnection.stop();
}
}
MyWriter.java
package com.vikas;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.net.Socket;
import java.util.Properties;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class MyWriter implements Runnable{
protected String topic = null;
protected String brokers = null;
protected static Logger logger = LogManager.getLogger(MyWriter.class);
public MyWriter () {
}
public void run() {
while(!MyReceiver.serverSocket.isClosed()){
Socket server = null;
try {
server = MyReceiver.serverSocket.accept();
//System.out.println("Just connected to " + server.getRemoteSocketAddress());
logger.info("Just connected to " + server.getRemoteSocketAddress());
MyReceiver.mapConnections.put(server.getRemoteSocketAddress().toString().trim(), "");
//change for prod deployment //change implemented
String key = null;
String message = null;
char ch;
StringBuilder msg = new StringBuilder();
int value = 0;
try {
BufferedReader in = new BufferedReader(new InputStreamReader(server.getInputStream()));
while((value = in.read()) != -1){
ch = (char)value;
if(ch == 0x0a){
//msg.append(ch);
//System.out.println(msg);
message = msg.toString().trim();
//code change as part of testing in prod
if(message.length() != 0){
//do something
msg.setLength(0);
}
else{
logger.error("Blank String received");
msg.setLength(0);
}
}
else{
msg.append(ch);
}
}
logger.info("Closing connection for client :" + server.getRemoteSocketAddress());
//System.out.println("Closing connection for client :" + this.getClientSocket().getRemoteSocketAddress());
server.close();
MyReceiver.mapConnections.remove(server.getRemoteSocketAddress());
} catch (IOException e) {
//report exception somewhere.
//e.printStackTrace();
logger.error("Something went wrong!!", e);
}
finally{
producer.close();
}
} catch (IOException e) {
if(MyReceiver.serverSocket.isClosed()) {
//System.out.println("Server was found to be Stopped.");
logger.error("Server was found to be Stopped.");
logger.error("Error accepting client connection", e);
break;
}
}
}
}
}
The backlog parameter of the ServerSocket constructor restricts the size of the incoming connection queue not the total number of times you are allowed to successfully call accept(). If you want to restrict the number of active connections you need to keep track of how many connections you've accepted then when you hit your threshold don't call accept() again until at least one of the active connections has been closed.
while(!MyReceiver.serverSocket.isClosed()){
Socket server = null;
try {
server = MyReceiver.serverSocket.accept();
//System.out.println("Just connected to " + server.getRemoteSocketAddress());
logger.info("Just connected to " + server.getRemoteSocketAddress());
MyReceiver.mapConnections.put(server.getRemoteSocketAddress().toString().trim(), "");
if (activeConnections == maxConnections) break; // exit accept loop

NIO chat application not working properly for multiple clients

I've been working on a NIO-based chat application of quite trivial logic: any message sent by any client should be visible to the rest of the users.
Right now, I'm sort of in the middle of the work, I've got pretty complete classes of the clients (and their GUI part) and the server but I've stumbled on a problem I couldn't find any solution on anywhere. Namely, if I run an instance of the server and one instance of the client, in my consoles (one for client, one for the server) I see a nice, expected conversation. However, after adding additional client, this newly created client doesn't get responses from the server - the first still has a valid connection.
I'm not thinking about broadcasting messages to all the clients yet, now I'd like to solve the problem of the lack of proper communication between each of my clients and the server since, I think that broadcasting shouldn't be so big a deal if the communication is fine.
I'd like to also add that I've tried many other ways of instantiating the clients: in one thread, firstly instantiating the clients then applying methods on them, I've event tried using invokeLater from SwingUtilities, since that's the proper way to boot up GUI. Sadly, neither worked.
What should I change to achieve proper communication between clients and the server? What am I doing wrong?
This is the log from client console:
Awaiting message from: client2...
Awaiting message from: client1...
after creating the clients - before any action
1 Message: client1 :: simpleMess1
2 started pushing message from: client1
3 Server response on client side: ECHO RESPONSE: client1 :: simpleMess1
4 Message: client2 :: simpleMessage from c2
5 started pushing message from: client2
6
7 -- No response from client2. AND next try from client2 shows no log at all (!)
8
9 Message: client1 :: simple mess2 from c1
10 started pushing message from: client1
11 Server response on client side: ECHO RESPONSE: client1 :: simpleMess1
And the log from server side console:
1 Server started...
2 S: Key is acceptable
3 S: Key is acceptable
4
5 -- after creating the clients before any action
6 S: Key is readable.
The console output clearly shows that the server receives acceptable keys from both clients but it suggest also that only one SocketChannel has a SelectionKey of readable type, but I've got no clue why. Moreover, I think that the order of creating the clients doesn't matter because as I tested: the client that talks properly with the server is always the one that starts communication as first.
Below I'm posting my Server and Client classes code, hoping You'll Guys help me sort it out.
Firstly, Server class:
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
public class Server {
private ServerSocketChannel serverSocketChannel = null;
private Selector selector = null;
private StringBuffer messageResponse = new StringBuffer();
private static Charset charset = Charset.forName("ISO-8859-2");
private static final int BSIZE = 1024;
private ByteBuffer byteBuffer = ByteBuffer.allocate(BSIZE);
private StringBuffer incomingClientMessage = new StringBuffer();
Set<SocketChannel> clientsSet = new HashSet<>();
public Server(String host, int port) {
try {
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(host, port));
selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
catch (Exception exc) {
exc.printStackTrace();
System.exit(1);
}
System.out.println("Server started...");
serviceConnections();
}
private void serviceConnections() {
boolean serverIsRunning = true;
while (serverIsRunning) {
try {
selector.select();
Set keys = selector.selectedKeys();
Iterator iter = keys.iterator();
while (iter.hasNext()) {
SelectionKey key = (SelectionKey) iter.next();
iter.remove();
if (key.isAcceptable()) {
System.out.println("\tS: Key is acceptable");
SocketChannel incomingSocketChannel = serverSocketChannel.accept();
incomingSocketChannel.configureBlocking(false);
incomingSocketChannel.register(selector, SelectionKey.OP_READ);
clientsSet.add(incomingSocketChannel);
continue;
}
if (key.isReadable()) {
System.out.println("\tS: Key is readable.");
SocketChannel incomingSocketChannel = (SocketChannel) key.channel();
serviceRequest(incomingSocketChannel);
continue;
}
}
}
catch (Exception exc) {
exc.printStackTrace();
continue;
}
}
}
private void serviceRequest(SocketChannel sc) {
if (!sc.isOpen()) return;
incomingClientMessage.setLength(0);
byteBuffer.clear();
try {
while (true) {
int n = sc.read(byteBuffer);
if (n > 0) {
byteBuffer.flip();
CharBuffer cbuf = charset.decode(byteBuffer);
while (cbuf.hasRemaining()) {
char c = cbuf.get();
if (c == '\r' || c == '\n') break;
incomingClientMessage.append(c);
}
}
writeResp(sc, "ECHO RESPONSE: " + incomingClientMessage.toString());
}
}
catch (Exception exc) {
exc.printStackTrace();
try {
sc.close();
sc.socket().close();
}
catch (Exception e) {
}
}
}
private void writeResp(SocketChannel sc, String addMsg)
throws IOException {
messageResponse.setLength(0);
messageResponse.append(addMsg);
messageResponse.append('\n');
ByteBuffer buf = charset.encode(CharBuffer.wrap(messageResponse));
sc.write(buf);
}
//second version - with an attempt to acomlish broadcasting
private void writeResp(SocketChannel sc, String addMsg)
throws IOException {
messageResponse.setLength(0);
messageResponse.append(addMsg);
messageResponse.append('\n');
ByteBuffer buf = charset.encode(CharBuffer.wrap(messageResponse));
System.out.println("clientsSet: " + clientsSet.size());
for (SocketChannel socketChannel : clientsSet) {
System.out.println("writing to: " + socketChannel.getRemoteAddress());
socketChannel.write(buf);
buf.rewind();
}
}
public static void main(String[] args) {
try {
final String HOST = "localhost";
final int PORT = 5000;
new Server(HOST, PORT);
}
catch (Exception exc) {
exc.printStackTrace();
System.exit(1);
}
}
}
and the Client class:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class Client {
private ClientView clientView;
private String hostName;
private int port;
private String clientName;
private Socket socket = null;
private PrintWriter printWriterOUT = null;
private BufferedReader bufferedReaderIN = null;
public Client(String hostName, int port, String clientName) {
this.hostName = hostName;
this.port = port;
this.clientName = clientName;
initView();
}
public void handleConnection() {
try {
socket = new Socket(hostName, port);
printWriterOUT = new PrintWriter(socket.getOutputStream(), true);
bufferedReaderIN = new BufferedReader(new InputStreamReader(socket.getInputStream()));
waitForIncomingMessageFromClientView();
bufferedReaderIN.close();
printWriterOUT.close();
socket.close();
}
catch (UnknownHostException e) {
System.err.println("Unknown host: " + hostName);
System.exit(2);
}
catch (IOException e) {
System.err.println("I/O err dla");
System.exit(3);
}
catch (Exception exc) {
exc.printStackTrace();
System.exit(4);
}
}
public void initView() {
clientView = new ClientView(clientName);
}
public void waitForIncomingMessageFromClientView() {
System.out.println("Awaiting message from: " + clientName + "...");
while (true) {
if (clientView.isSent) {
System.out.println("Message: " + clientView.getOutgoingMessage());
pushClientViewMessageToServer();
clientView.setIsSent(false);
}
}
}
public void pushClientViewMessageToServer() {
String clientViewMessage = clientView.getOutgoingMessage();
System.out.println("started pushing message from: " + clientView.getClientName());
try {
printWriterOUT.println(clientViewMessage);
String resp = bufferedReaderIN.readLine();
System.out.println("Server response on client side: " + resp);
}
catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
#Override
public void run() {
Client c1 = new Client("localhost", 5000, "client1");
c1.handleConnection();
}
});
thread1.start();
Thread thread2 = new Thread(new Runnable() {
#Override
public void run() {
Client c2 = new Client("localhost", 5000, "client2");
c2.handleConnection();
}
});
thread2.start();
}
}
I'll apprecaite any help from You Guys.
EDIT:
the second version of writeResp method attempting to broadcast echo to all the clients produces such log:
Server started...
clientsSet: 2
writing to: /127.0.0.1:63666
writing to: /127.0.0.1:63665
clientsSet: 2
writing to: /127.0.0.1:63666
writing to: /127.0.0.1:63665
It seems like there are two clients and I'm wondering why they don't get proper reply from the server.
while (true) {
int n = sc.read(byteBuffer);
if (n > 0) {
byteBuffer.flip();
CharBuffer cbuf = charset.decode(byteBuffer);
while (cbuf.hasRemaining()) {
char c = cbuf.get();
if (c == '\r' || c == '\n') break;
incomingClientMessage.append(c);
}
}
There is a major problem here. If read() returns -1 you should close the SocketChannel, and if it returns -1 or zero you should break out of the loop.

Android Arduino Bluetooth weird errors connecting microcontroller

I am using a Teensy microcontroller (https://www.pjrc.com/teensy/teensy31.html) to connect to an Android application to get Bluetooth.
I have used this Android application with other embedded bluetooth devices successfully, however now I am running into the following error I see in LogCat:
04-02 20:06:29.713: E/BTLD(5499):
######################################################################
04-02 20:06:29.713: E/BTLD(5499): #
04-02 20:06:29.713: E/BTLD(5499): # WARNING : BTU HCI(id=0) command timeout. opcode=0x405
04-02 20:06:29.713: E/BTLD(5499): #
04-02 20:06:29.713: E/BTLD(5499): ######################################################################
I have zero idea where this error is coming from. I also see the following error occasionally:
04-02 20:25:19.242: E/bt-btif(914): DISCOVERY_COMP_EVT slot id:11, failed to find channle, status:1, scn:0
The worst part is, with my current hardware setup, half the time it will work, the other half it wont. And I have no clue why. For reference, I am using this bluetooth module: https://www.sparkfun.com/products/12577 And I have the pins connected:
Vcc <---> Vcc
GND <---> GND
TX (BT) <---> RX1 (Teensy)
RX (BT) <---> TX1 (Teensy)
The lack of consistency is killing me. Sometimes just pushing the reset button on the Teensy fixes it.
I am working off of this tutorial: http://stafava.blogspot.ca/2012/12/connect-teensy-to-bluetooth-module.html Here is the Ardunio Microcontroller code running on the Teensy:
HardwareSerial bt = HardwareSerial();
#define Seria1_PORT_SPEED 115200
void setup()
{
bt.begin(Seria1_PORT_SPEED);
bt.println();
Wire.begin();
}
void loop()
{
////////////////// Bluetooth stuff /////////////////////////
if (bt.available() >= 2) {
if (bt.read() == '#') {// Start of new control message
int command = bt.read(); // Commands
if (command == 'f') {// request one output _f_rame
//output_single_on = true; //unused?
} else if (command == 's') { // _s_ynch request
// Read ID
byte id[2];
id[0] = readChar();
id[1] = readChar();
// Reply with synch message
bt.print("#SYNCH");
bt.write(id, 2);
bt.println();
}
}
}
sendDataToAndroid();
}
void sendDataToAndroid() { //arrays defined as float array[3] = ...
bt.write((byte*) array1, 12);
bt.write((byte*) array2, 12);
bt.write((byte*) array3, 12);
}
char readChar()
{
while (bt.available() < 1) { } // Block
return bt.read();
}
And here is the relevant Android code taken from this tutorial: https://github.com/ptrbrtz/razor-9dof-ahrs/tree/master/Arduino/Razor_AHRS
package com.jest.razor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
import org.apache.http.util.EncodingUtils;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
/**
* Class to easily interface the Razor AHRS via Bluetooth.
* <p>
* Bluetooth seems to be even more picky on Android than it is anyway. Be sure to have a look at the
* section about Android Bluetooth in the tutorial at
* <a href="https://github.com/ptrbrtz/razor-9dof-ahrs">
* https://github.com/ptrbrtz/razor-9dof-ahrs</a>!
* <p>
* The app using this class has to
* <ul>
* <li>target Android 2.0 (API Level 5) or later.
* <li>specify the uses-permissions <code>BLUETOOTH</code> and <code>BLUETOOTH_ADMIN</code> in it's
* AndroidManifest.xml.
* <li>add this Library Project as a referenced library (Project Properties -> Android -> Library)
* </ul>
* <p>
* TODOs:
* <ul>
* <li>Add support for USB OTG (Android device used as USB host), if using FTDI is possible.
* </ul>
*
* #author Peter Bartz
*/
public class RazorAHRS {
private static final String TAG = "RazorAHRS";
private static final boolean DEBUG = false;
private static final String SYNCH_TOKEN = "#SYNCH";
private static final String NEW_LINE = "\r\n";
// Timeout to init Razor AHRS after a Bluetooth connection has been established
public static final int INIT_TIMEOUT_MS = 10000;
// IDs passed to internal message handler
private static final int MSG_ID__YPR_DATA = 0;
private static final int MSG_ID__IO_EXCEPTION_AND_DISCONNECT = 1;
private static final int MSG_ID__CONNECT_OK = 2;
private static final int MSG_ID__CONNECT_FAIL = 3;
private static final int MSG_ID__CONNECT_ATTEMPT = 4;
private static final int MSG_ID__AMG_DATA = 5;
private static final UUID UUID_SPP = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
/**
* Razor output modes.
* Use <code>YAW_PITCH_ROLL_ANGLES</code> to receive yaw, pitch and roll in degrees. <br>
* Use <code>RAW_SENSOR_DATA</code> or <code>CALIBRATED_SENSOR_DATA</code> to read raw or
* calibrated xyz sensor data of the accelerometer, magnetometer and the gyroscope.
*/
public enum RazorOutputMode {
YAW_PITCH_ROLL_ANGLES,
RAW_SENSOR_DATA,
CALIBRATED_SENSOR_DATA
}
private RazorOutputMode razorOutputMode;
private enum ConnectionState {
DISCONNECTED,
CONNECTING,
CONNECTED,
USER_DISCONNECT_REQUEST
}
volatile private ConnectionState connectionState = ConnectionState.DISCONNECTED;
volatile private BluetoothSocket btSocket;
volatile private BluetoothDevice btDevice;
volatile private InputStream inStream;
volatile private OutputStream outStream;
private RazorListener razorListener;
private boolean callbacksEnabled = true;
BluetoothThread btThread;
private int numConnectAttempts;
// Object pools
private ObjectPool<float[]> float3Pool = new ObjectPool<float[]>(new ObjectPool.ObjectFactory<float[]>() {
#Override
public float[] newObject() {
return new float[3];
}
});
private ObjectPool<float[]> float9Pool = new ObjectPool<float[]>(new ObjectPool.ObjectFactory<float[]>() {
#Override
public float[] newObject() {
return new float[9];
}
});
/**
* Constructor.
* Must be called from the thread where you want receive the RazorListener callbacks! So if you
* want to manipulate Android UI from the callbacks you have to call this from your main/UI
* thread.
*
* #param btDevice {#link android.bluetooth.BluetoothDevice BluetoothDevice} holding the Razor
* AHRS to connect to.
* #param razorListener {#link RazorListener} that will be notified of Razor AHRS events.
* #throws RuntimeException thrown if one of the parameters is null.
*/
public RazorAHRS(BluetoothDevice btDevice, RazorListener razorListener)
throws RuntimeException {
this(btDevice, razorListener, RazorOutputMode.CALIBRATED_SENSOR_DATA);
}
/**
* Constructor.
* Must be called from the thread where you want receive the RazorListener callbacks! So if you
* want to manipulate Android UI from the callbacks you have to call this from your main/UI
* thread.
*
* #param btDevice {#link android.bluetooth.BluetoothDevice BluetoothDevice} holding the Razor
* AHRS to connect to.
* #param razorListener {#link RazorListener} that will be notified of Razor AHRS events.
* #param razorOutputMode {#link RazorOutputMode} that you desire.
* #throws RuntimeException thrown if one of the parameters is null.
*/
public RazorAHRS(BluetoothDevice btDevice, RazorListener razorListener, RazorOutputMode razorOutputMode)
throws RuntimeException {
if (btDevice == null)
throw new RuntimeException("BluetoothDevice can not be null.");
this.btDevice = btDevice;
if (razorListener == null)
throw new RuntimeException("RazorListener can not be null.");
this.razorListener = razorListener;
if (razorOutputMode == null)
throw new RuntimeException("RazorMode can not be null.");
this.razorOutputMode = razorOutputMode;
}
/**
* #return <code>true</code> if listener callbacks are currently enabled, <code>false</code> else.
*/
public boolean getCallbacksEnabled() {
return callbacksEnabled;
}
/**
* Enables/disables listener callbacks.
* #param enabled
*/
public void setCallbacksEnabled(boolean enabled) {
callbacksEnabled = enabled;
}
/**
* Connect and start reading. Both is done asynchronously. {#link RazorListener#onConnectOk()}
* or {#link RazorListener#onConnectFail(IOException)} callbacks will be invoked.
*
* #param numConnectAttempts Number of attempts to make when trying to connect. Often connecting
* only works on the 2rd try or later. Bluetooth hooray.
*/
public void asyncConnect(int numConnectAttempts) {
if (DEBUG) Log.d(TAG, "asyncConnect() BEGIN");
// Disconnect and wait for running thread to end, if needed
if (btThread != null) {
asyncDisconnect();
try {
btThread.join();
} catch (InterruptedException e) { }
}
// Bluetooth thread not running any more, we're definitely in DISCONNECTED state now
// Create new thread to connect to Razor AHRS and read input
this.numConnectAttempts = numConnectAttempts;
connectionState = ConnectionState.CONNECTING;
btThread = new BluetoothThread();
btThread.start();
if (DEBUG) Log.d(TAG, "asyncConnect() END");
}
/**
* Disconnects from Razor AHRS. If still connecting this will also cancel the connection process.
*/
public void asyncDisconnect() {
if (DEBUG) Log.d(TAG, "asyncDisconnect() BEGIN");
synchronized (connectionState) {
if (DEBUG) Log.d(TAG, "asyncDisconnect() SNYNCHRONIZED");
// Don't go to USER_DISCONNECT_REQUEST state if we are disconnected already
if (connectionState == ConnectionState.DISCONNECTED)
return;
// This is a wanted disconnect, so we force (blocking) I/O to break
connectionState = ConnectionState.USER_DISCONNECT_REQUEST;
closeSocketAndStreams();
}
if (DEBUG) Log.d(TAG, "asyncDisconnect() END");
}
/**
* Writes out a string using ASCII encoding. Assumes we're connected. Does not handle
* exceptions itself.
*
* #param text Text to send out
* #throws IOException
*/
private void write(String text) throws IOException {
outStream.write(EncodingUtils.getAsciiBytes(text));
}
/**
* Closes I/O streams and Bluetooth socket.
*/
private void closeSocketAndStreams() {
if (DEBUG) Log.d(TAG, "closeSocketAndStreams() BEGIN");
// Try to switch off streaming output of Razor in preparation of next connect
try {
if (outStream != null)
write("#o0");
} catch (IOException e) { }
// Close Bluetooth socket => I/O operations immediately will throw exception
try {
if (btSocket != null)
btSocket.close();
} catch (IOException e) { }
if (DEBUG) Log.d(TAG, "closeSocketAndStreams() BT SOCKET CLOSED");
// Close streams
try {
if (inStream != null)
inStream.close();
} catch (IOException e) { }
try {
if (outStream != null)
outStream.close();
} catch (IOException e) { }
if (DEBUG) Log.d(TAG, "closeSocketAndStreams() STREAMS CLOSED");
// Do not set socket and streams null, because input thread might still access them
//inStream = null;
//outStream = null;
//btSocket = null;
if (DEBUG) Log.d(TAG, "closeSocketAndStreams() END");
}
/**
* Thread that handles connecting to and reading from Razor AHRS.
*/
private class BluetoothThread extends Thread {
byte[] inBuf = new byte[512];
int inBufPos = 0;
/**
* Blocks until it can read one byte of input, assumes we have a connection up and running.
*
* #return One byte from input stream
* #throws IOException If reading input stream fails
*/
private byte readByte() throws IOException {
int in = inStream.read();
if (in == -1)
throw new IOException("End of Stream");
return (byte) in;
}
/**
* Converts a buffer of bytes to an array of floats. This method does not do any error
* checking on parameter array sizes.
* #param byteBuf Byte buffer with length of at least <code>numFloats * 4</code>.
* #param floatArr Float array with length of at least <code>numFloats</code>.
* #param numFloats Number of floats to convert
*/
private void byteBufferToFloatArray(byte[] byteBuf, float[] floatArr, int numFloats) {
//int numFloats = byteBuf.length / 4;
for (int i = 0; i < numFloats * 4; i += 4) {
// Convert from little endian (Razor) to big endian (Java) and interpret as float
floatArr[i/4] = Float.intBitsToFloat((byteBuf[i] & 0xff) + ((byteBuf[i+1] & 0xff) << 8) +
((byteBuf[i+2] & 0xff) << 16) + ((byteBuf[i+3] & 0xff) << 24));
}
}
/**
* Parse input stream for given token.
* #param token Token to find
* #param in Next byte from input stream
* #return <code>true</code> if token was found
*/
private boolean readToken(byte[] token, byte in) {
if (in == token[inBufPos++]) {
if (inBufPos == token.length) {
// Synch token found
inBufPos = 0;
if (DEBUG) Log.d(TAG, "Token found");
return true;
}
} else {
inBufPos = 0;
}
return false;
}
/**
* Synches with Razor AHRS and sets parameters.
* #throws IOException
*/
private void initRazor() throws IOException {
long t0, t1, t2;
// Start time
t0 = SystemClock.uptimeMillis();
/* See if Razor is there */
// Request synch token to see when Razor is up and running
final String contactSynchID = "00";
final String contactSynchRequest = "#s" + contactSynchID;
final byte[] contactSynchReply = EncodingUtils.getAsciiBytes(SYNCH_TOKEN + contactSynchID + NEW_LINE);
write(contactSynchRequest);
t1 = SystemClock.uptimeMillis();
while (true) {
// Check timeout
t2 = SystemClock.uptimeMillis();
if (t2 - t1 > 200) {
// 200ms elapsed since last request and no answer -> request synch again.
// (This happens when DTR is connected and Razor resets on connect)
write(contactSynchRequest);
t1 = t2;
}
if (t2 - t0 > INIT_TIMEOUT_MS)
// Timeout -> tracker not present
throw new IOException("Can not init Razor: response timeout");
// See if we can read something
if (inStream.available() > 0) {
// Synch token found?
if (readToken(contactSynchReply, readByte()))
break;
} else {
// No data available, wait
delay(5); // 5ms
}
}
/* Configure tracker */
// Set binary output mode, enable continuous streaming, disable errors and request synch
// token. So we're good, no matter what state the tracker currently is in.
final String configSynchID = "01";
final byte[] configSynchReply = EncodingUtils.getAsciiBytes(SYNCH_TOKEN + configSynchID + NEW_LINE);
write("#ob#o1#oe0#s" + configSynchID);
while (!readToken(configSynchReply, readByte())) { }
}
/**
* Opens Bluetooth connection to Razor AHRS.
* #throws IOException
*/
private void connect() throws IOException {
// Create Bluetooth socket
btSocket = btDevice.createRfcommSocketToServiceRecord(UUID_SPP);
if (btSocket == null) {
if (DEBUG) Log.d(TAG, "btSocket is null in connect()");
throw new IOException("Could not create Bluetooth socket");
}
// This could be used to create the RFCOMM socekt on older Android devices where
//createRfcommSocketToServiceRecord is not present yet.
/*try {
Method m = btDevice.getClass().getMethod("createRfcommSocket", new Class[] { int.class });
btSocket = (BluetoothSocket) m.invoke(btDevice, Integer.valueOf(1));
} catch (Exception e) {
throw new IOException("Could not create Bluetooth socket using reflection");
}*/
// Connect socket to Razor AHRS
if (DEBUG) Log.d(TAG, "Canceling bt discovery");
BluetoothAdapter.getDefaultAdapter().cancelDiscovery(); // Recommended
if (DEBUG) Log.d(TAG, "Trying to connect() btSocket");
btSocket.connect();
// Get the input and output streams
if (DEBUG) Log.d(TAG, "Trying to create streams");
inStream = btSocket.getInputStream();
outStream = btSocket.getOutputStream();
if (inStream == null || outStream == null) {
if (DEBUG) Log.d(TAG, "Could not create I/O stream(s) in connect()");
throw new IOException("Could not create I/O stream(s)");
}
}
/**
* Bluetooth I/O thread entry method.
*/
public void run() {
if (DEBUG) Log.d(TAG, "Bluetooth I/O thread started");
try {
// Check if btDevice is set
if (btDevice == null) {
if (DEBUG) Log.d(TAG, "btDevice is null in run()");
throw new IOException("Bluetooth device is null");
}
// Make several attempts to connect
int i = 1;
while (true) {
if (DEBUG) Log.d(TAG, "Connect attempt " + i + " of " + numConnectAttempts);
sendToParentThread(MSG_ID__CONNECT_ATTEMPT, i);
try {
connect();
break; // Alrighty!
} catch (IOException e) {
if (DEBUG) Log.d(TAG, "Attempt failed: " + e.getMessage());
// Maximum number of attempts reached or cancel requested?
if (i == numConnectAttempts || connectionState == ConnectionState.USER_DISCONNECT_REQUEST)
throw e;
// We couldn't connect on first try, manually starting Bluetooth discovery
// often helps
if (DEBUG) Log.d(TAG, "Starting BT discovery");
BluetoothAdapter.getDefaultAdapter().startDiscovery();
delay(5000); // 5 seconds - long enough?
i++;
}
}
// Set Razor output mode
if (DEBUG) Log.d(TAG, "Trying to set Razor output mode");
initRazor();
// We're connected and initialized (unless disconnect was requested)
synchronized (connectionState) {
if (connectionState == ConnectionState.USER_DISCONNECT_REQUEST) {
closeSocketAndStreams();
throw new IOException(); // Dummy exception to force disconnect
}
else connectionState = ConnectionState.CONNECTED;
}
// Tell listener we're ready
sendToParentThread(MSG_ID__CONNECT_OK, null);
// Keep reading inStream until an exception occurs
if (DEBUG) Log.d(TAG, "Starting input loop");
while (true) {
// Read byte from input stream
inBuf[inBufPos++] = (byte) readByte();
if (razorOutputMode == RazorOutputMode.YAW_PITCH_ROLL_ANGLES) {
if (inBufPos == 12) { // We received a full frame
float[] ypr = float3Pool.get();
byteBufferToFloatArray(inBuf, ypr, 3);
// Forward to parent thread handler
sendToParentThread(MSG_ID__YPR_DATA, ypr);
// Rewind input buffer position
inBufPos = 0;
}
} else { // Raw or calibrated sensor data mode
if (inBufPos == 36) { // We received a full frame
float[] amg = float9Pool.get();
byteBufferToFloatArray(inBuf, amg, 9);
// Forward to parent thread handler
sendToParentThread(MSG_ID__AMG_DATA, amg);
// Rewind input buffer position
inBufPos = 0;
}
}
}
} catch (IOException e) {
if (DEBUG) Log.d(TAG, "IOException in Bluetooth thread: " + e.getMessage());
synchronized (connectionState) {
// Don't forward exception if it was thrown because we broke I/O on purpose in
// other thread when user requested disconnect
if (connectionState != ConnectionState.USER_DISCONNECT_REQUEST) {
// There was a true I/O error, cleanup and forward exception
closeSocketAndStreams();
if (DEBUG) Log.d(TAG, "Forwarding exception");
if (connectionState == ConnectionState.CONNECTING)
sendToParentThread(MSG_ID__CONNECT_FAIL, e);
else
sendToParentThread(MSG_ID__IO_EXCEPTION_AND_DISCONNECT, e);
} else {
// I/O error was caused on purpose, socket and streams are closed already
}
// I/O closed, thread done => we're disconnected now
connectionState = ConnectionState.DISCONNECTED;
}
}
}
/**
* Sends a message to Handler assigned to parent thread.
*
* #param msgId
* #param data
*/
private void sendToParentThread(int msgId, Object o) {
if (callbacksEnabled)
parentThreadHandler.obtainMessage(msgId, o).sendToTarget();
}
/**
* Sends a message to Handler assigned to parent thread.
*
* #param msgId
* #param data
*/
private void sendToParentThread(int msgId, int i) {
if (callbacksEnabled)
parentThreadHandler.obtainMessage(msgId, i, -1).sendToTarget();
}
/**
* Wrapper for {#link Thread#sleep(long)};
* #param ms Milliseconds
*/
void delay(long ms) {
try {
sleep(ms); // Sleep 5ms
} catch (InterruptedException e) { }
}
}
/**
* Handler that forwards messages to the RazorListener callbacks. This handler runs in the
* thread this RazorAHRS object was created in and receives data from the Bluetooth I/O thread.
*/
private Handler parentThreadHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ID__YPR_DATA: // Yaw, pitch and roll data
float[] ypr = (float[]) msg.obj;
razorListener.onAnglesUpdate(ypr[0], ypr[1], ypr[2]);
float3Pool.put(ypr);
break;
case MSG_ID__AMG_DATA: // Accelerometer, magnetometer and gyroscope data
float[] amg = (float[]) msg.obj;
razorListener.onSensorsUpdate(amg[0], amg[1], amg[2], amg[3], amg[4], amg[5],
amg[6], amg[7], amg[8]);
float9Pool.put(amg);
break;
case MSG_ID__IO_EXCEPTION_AND_DISCONNECT:
razorListener.onIOExceptionAndDisconnect((IOException) msg.obj);
break;
case MSG_ID__CONNECT_ATTEMPT:
razorListener.onConnectAttempt(msg.arg1, numConnectAttempts);
break;
case MSG_ID__CONNECT_OK:
razorListener.onConnectOk();
break;
case MSG_ID__CONNECT_FAIL:
razorListener.onConnectFail((IOException) msg.obj);
break;
}
}
};
}

Blackberry Best way to retry connection if failed?

I have the following code, i want to be able to restart the thread if an exception occurred while processing a request.
The following in the run method of a thread:
int status = httpConn.getResponseCode();
if (status == HttpConnection.HTTP_OK) {
// Is this html?
String contentType = httpConn
.getHeaderField(HEADER_CONTENTTYPE);
boolean htmlContent = (contentType != null && contentType
.startsWith(CONTENTTYPE_TEXTHTML));
InputStream input = s.openInputStream();
byte[] data = new byte[1000];
int len = 0;
int size = 0;
StringBuffer raw = new StringBuffer();
while (-1 != (len = input.read(data))) {
// Exit condition for the thread. An
// IOException
// is
// thrown because of the call to
// httpConn.close(),
// causing the thread to terminate.
if (_stop) {
httpConn.close();
s.close();
input.close();
}
raw.append(new String(data, 0, len));
size += len;
}
// raw.insert(0, "bytes received]\n");
// raw.insert(0, size);
// raw.insert(0, '[');
content = raw.toString();
if (htmlContent) {
content = prepareData(raw.toString());
}
input.close();
} else {
try{
httpConn.close();
}catch (Exception e) {
// TODO: handle exception
}
errorDialog(status+", status code");
retryFeed(getUrl(), "Network error. Retrying...");
}
s.close();
} else {
errorDialog("Sorry Insufficient Network Coverage.");
return;
}
} catch (IOCancelledException e) {
errorDialog(e.getMessage());
retryFeed(getUrl(), "Network error. Retrying...");
} catch (IOException e) {
errorDialog(e.getMessage());
retryFeed(getUrl(), "Network error. Retrying...");
}
What is the safest way to retry the connection if failed?
Thanks.
//New This is the Error thread. That check for errors in the connection... will this help? and is it the most efficient method? thanks..
/Error Thread - Thread to check errors/
private class ErrorThread extends Thread {
private static final int TIMEOUT = 3000; // EVERY 3 Seconds
private boolean hasException = false;
private String _theUrl;
/**
* Stops this thread from listening for messages
*/
private synchronized void stop()
{
hasException =false;
}
/**
* Listens for incoming messages until stop() is called
* #see #stop()
* #see java.lang.Runnable#run()
*/
public void run()
{
try
{
while (true) {
if((hasException==true))
{
// Synchronize here so that we don't end up creating a connection that is never closed.
errorDialog("Will Fetch new");
synchronized(this)
{
hasException=false;
if (!_connectionThread.isStarted()) {
fetchPage(_theUrl);
} else {
createNewFetch(_theUrl);
}
}
}
try {
//errorDialog("No exception.");
sleep(TIMEOUT);
} catch (InterruptedException e)
{
errorDialog("Exceptions"+e.toString()+e.getMessage());
System.exit(1);
//System.exit(0);/*Kill System*/
}
}
}
catch (Exception except)
{
}
}
public void setActive(boolean exception,String url)
{
this.hasException=exception;
this._theUrl=url;
}
}
If the connecrtion fails, typically, you want to close it, pause a small time, and retry. The purpose of the pause is to prevent your device from devoting excessive resources to trying to connect to a server that's having issues.

Categories

Resources