/*********************************************************************************
* TotalCross Software Development Kit *
* Copyright (C) 1998, 1999 Wabasoft <www.wabasoft.com> *
* Copyright (C) 2000-2012 SuperWaba Ltda. *
* All Rights Reserved *
* *
* This library and virtual machine is distributed in the hope that it will *
* be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
* *
* This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 *
* A copy of this license is located in file license.txt at the root of this *
* SDK or can be downloaded here: *
* http://www.gnu.org/licenses/lgpl-3.0.txt *
* *
*********************************************************************************/
package totalcross.net;
import totalcross.io.*;
import totalcross.sys.*;
import java.io.IOException;
/**
* Socket is a TCP/IP network socket.
* <p>
* Under Java and Windows CE, if no network is present, the socket constructor
* may hang for an extended period of time due to the implementation of sockets
* in the underlying OS. This is a known problem.
* <p>
* Here is an example showing data being written and read from a socket:
*
* <pre>
* Socket socket = new Socket("www.totalcross.com", 80);
* DataStream ds = new DataStream(socket);
* ds.writeBytes("GET / HTTP/1.0\n\n");
* String ack = socket.readLine();
* socket.close();
* </pre>
*
* Important: you cannot open a socket before the main event loop. In other
* words, you cannot open a socket in the app's constructor, but CAN in the
* initUI method.
* <p>
* When using GPRS connections, it is very important that you set a big timeout
* (20 seconds at least), otherwise the connection will be closed before the
* data is fully flushed (which only occurs after Socket.close).
*/
public class Socket extends Stream
{
/**
* Stores the timeout value for read operations. The value specifies the
* number of milliseconds to wait from the time of last activity before
* timing out a read operation. Passing a value of 0 sets no timeout causing
* any read operation to return immediately with or without data. The default
* read timeout is 5000 milliseconds.
*/
public int readTimeout = DEFAULT_READ_TIMEOUT;
/**
* Stores the timeout value for write operations. The value specifies the
* number of milliseconds to wait from the time of last activity before
* timing out a write operation. Passing a value of 0 sets no timeout causing
* any write operation to return immediately, successfully or not. The
* default write timeout is 2000 milliseconds.
*/
public int writeTimeout = DEFAULT_WRITE_TIMEOUT;
/** Used just in the desktop side */
Object socketRef;
/** Used just in the desktop side */
private Object myInputStream;
/** Used just in the desktop side */
private Object myOutputStream;
/** The remote host which this socket is connected to */
private String host;
/** The remote port which this socket is connected to */
private int port;
/** Default timeout value for socket creation */
public static final int DEFAULT_OPEN_TIMEOUT = 2000;
/** Default timeout value for read operations */
public static final int DEFAULT_READ_TIMEOUT = 5000;
/** Default timeout value for write operations */
public static final int DEFAULT_WRITE_TIMEOUT = 2000;
/** For internal use only */
protected Socket()
{
}
/**
* Opens a socket with the given host, port and open timeout of 1500ms.
*
* @param host the host name or IP address to connect to
* @param port the port number to connect to
* @see #Socket(String, int, int, String)
*/
public Socket(String host, int port)
throws totalcross.net.UnknownHostException, totalcross.io.IOException
{
this(host, port, DEFAULT_OPEN_TIMEOUT, null);
}
/**
* Opens a socket with the given host, port, open timeout.
*
* @param host the host name or IP address to connect to
* @param port the port number to connect to
* @param timeout the specified timeout, in milliseconds.
* @see #Socket(String, int, int, String)
*/
public Socket(String host, int port, int timeout)
throws totalcross.net.UnknownHostException, totalcross.io.IOException
{
this(host, port, timeout, null);
}
/**
* Opens a socket with the given parameters. This method establishes a socket connection by looking up
* the given host and performing the 3 way TCP/IP handshake.
*
* @param host the host name or IP address to connect to
* @param port the port number to connect to
* @param timeout the specified timeout, in milliseconds.
* @param params
* the string specifying additional parameters to this socket.
* Each parameter is specified in a 'key=value' form and separated
* by a ';' from the next parameter. For example: 'p1=v1;p2=v2'.
* On BlackBerry, the following parameters are valid: 'apn=[value]',
* 'apnuser=[value]', 'apnpass=[value]', 'directtcp=[true|false]' and
* 'nolinger=[true|false]'.
* On Palm OS devices, the only valid parameter is 'nolinger=[true|
* false]'. If true, the socket is closed immediately, and no ack
* is waited from the server. Note that this must be done for the
* very first socket creation per application.
* @throws totalcross.net.UnknownHostException
* @throws totalcross.io.IOException
*/
public Socket(String host, int port, int timeout, String params)
throws totalcross.net.UnknownHostException, totalcross.io.IOException
{
if (port < 0 || port > 65535)
throw new java.lang.IllegalArgumentException("Invalid value for argument 'port': " + port);
if (timeout < 0)
throw new java.lang.IllegalArgumentException("Invalid value for argument 'timeout': " + timeout);
this.host = host;
this.port = port;
socketCreate(host, port, timeout);
}
/**
* Opens a socket with the given host, port, timeout and linger option.
*
* @param host the host name or IP address to connect to
* @param port the port number to connect to
* @param timeout the specified timeout, in milliseconds.
* @param noLinger if true, the socket is closed immediately, and no ack is waited
* from the server. Note that this must be done for the very first
* socket creation per application.
* @see #Socket(String, int, int, String)
*/
public Socket(String host, int port, int timeout, boolean noLinger)
throws totalcross.net.UnknownHostException, totalcross.io.IOException
{
this(host, port, timeout, noLinger ? ("nolinger="+noLinger) : null);
}
/**
* Native implementation for socket creation. <br>
* NOTE: The socket constructor already took care of all validation.
*
* @param host
* @param port
* @param timeout
* @param noLingerOrDirectTCP
* @throws totalcross.net.UnknownHostException
* @throws totalcross.io.IOException
*/
final private void socketCreate(final String host, final int port, final int timeout)
throws totalcross.net.UnknownHostException, totalcross.io.IOException
{
try
{
socketRef = new java.net.Socket(host, port);
}
catch (java.lang.SecurityException e)
{
throw new totalcross.io.IOException(e.getMessage());
}
catch (java.net.UnknownHostException e)
{
throw new totalcross.net.UnknownHostException(e.getMessage());
}
catch (java.io.IOException e)
{
throw new totalcross.io.IOException(e.getMessage());
}
try
{
((java.net.Socket) socketRef).setSoTimeout(readTimeout);
myInputStream = ((java.net.Socket) socketRef).getInputStream();
myOutputStream = ((java.net.Socket) socketRef).getOutputStream();
}
catch (java.io.IOException e1)
{
try
{
((java.net.Socket) socketRef).close();
throw new totalcross.io.IOException(e1.getMessage());
}
catch (java.io.IOException e2)
{
throw new totalcross.io.IOException(e1.getMessage());
}
}
}
/**
* Closes the socket.
*
* @throws totalcross.io.IOException If the socket is closed more than once
*/
public void close() throws totalcross.io.IOException
{
if (socketRef == null)
throw new totalcross.io.IOException("The socket is already closed.");
try
{
nativeClose();
}
finally
{
socketRef = null;
}
}
/**
* Native implementation for socket close. <br>
* NOTE: All validation was done by the public close method.
*
* @throws totalcross.io.IOException
*/
final private void nativeClose() throws totalcross.io.IOException
{
try
{
((java.net.Socket) this.socketRef).close();
}
catch (java.io.IOException e)
{
throw new totalcross.io.IOException(e.getMessage());
}
}
/**
* Reads bytes from the socket into a byte array. Note that in the device it
* may return with less bytes than requested. Note also that, if -1 is
* returned and the lastError is 4626, please insist 3 or 4 more times,
* because it may work.
*
* @param buf the byte array to read data into
* @param start the start position in the byte array
* @param count the number of bytes to read
* @return The number of bytes actually read; or <= 0 if no data is
* available; or -1 if the server closed the connection or an error
* prevented the read operation from occurring.
* @throws totalcross.io.IOException
*/
public int readBytes(byte buf[], int start, int count) throws totalcross.io.IOException
{
if (socketRef == null)
throw new totalcross.io.IOException("The socket is closed.");
if (buf == null)
throw new NullPointerException();
if (start < 0 || count < 0 || start + count > buf.length)
throw new IndexOutOfBoundsException();
if (count == 0)
return 0;
return readWriteBytes(buf, start, count, true);
}
/**
* Native implementation for reading and writing bytes from a socket.
* NOTE: All validation was done by the read/write methods.
*/
final private int readWriteBytes(byte buf[], int start, int count, boolean isRead)
throws totalcross.io.IOException
{
if (isRead)
{
if (myInputStream == null)
{
try
{
myInputStream = ((java.net.Socket) socketRef).getInputStream();
}
catch (java.io.IOException e)
{
throw new totalcross.io.IOException(e.getMessage());
}
}
try
{
((java.net.Socket) socketRef).setSoTimeout(readTimeout < 0 ? 0 : readTimeout);
}
catch (java.net.SocketException e)
{
throw new totalcross.io.IOException(e.getMessage());
}
try
{
int result = ((java.io.InputStream) myInputStream).read(buf, start, count);
return result == -1 ? 0 : result;
}
catch (java.net.SocketTimeoutException e)
{
throw new totalcross.net.SocketTimeoutException(e.getMessage());
}
catch (java.io.IOException e)
{
throw new totalcross.io.IOException(e.getMessage());
}
}
else
{
if (myOutputStream == null)
{
try
{
myOutputStream = ((java.net.Socket) socketRef).getOutputStream();
}
catch (java.io.IOException e)
{
throw new totalcross.io.IOException(e.getMessage());
}
}
try
{
((java.io.OutputStream) this.myOutputStream).write(buf, start, count);
return count;
}
catch (IOException e)
{
throw new totalcross.io.IOException(e.getMessage());
}
}
}
/**
* Reads bytes from the socket into a byte array, from offset 0 to
* buf.length. Note that in the device it may return with less bytes than
* requested. Note also that, if -1 is returned and the lastError is 4626,
* please insist 3 or 4 more times, because it may work.
*
* @param buf
* the byte array to read data into
* @return The number of bytes actually read; or <= 0 if no data is
* available; or -1 if the server closed the connection or an error
* prevented the read operation from occurring.
* @throws totalcross.io.IOException
* @since SuperWaba 5.6
* @see #readBytes(byte[], int, int)
*/
public int readBytes(byte buf[]) throws totalcross.io.IOException
{
if (socketRef == null)
throw new totalcross.io.IOException("The socket is closed.");
if (buf == null)
throw new java.lang.NullPointerException();
if (buf.length == 0)
return 0;
return readWriteBytes(buf, 0, buf.length, true);
}
/**
* Writes to the socket. Returns the number of bytes written or -1 if an
* error prevented the write operation from occurring. If data can't be
* written to the socket for approximately 2 seconds, the write operation
* will time out.
*
* @param buf the byte array to write data from
* @param start the start position in the byte array
* @param count the number of bytes to write
* @throws totalcross.io.IOException
*/
public int writeBytes(byte buf[], int start, int count) throws totalcross.io.IOException
{
if (socketRef == null)
throw new totalcross.io.IOException("The socket is closed.");
if (buf == null)
throw new NullPointerException();
if (start < 0 || count < 0 || start + count > buf.length)
throw new IndexOutOfBoundsException();
if (count == 0)
return 0;
return readWriteBytes(buf, start, count, false);
}
private byte[] rlbuf; // guich@tc123_48: use a byte[] buffer instead of a StringBuffer
/**
* Reads a line of text from this socket. Any char lower than space
* is considered a new line separator. This method correctly handles newlines
* with \\n or \\r\\n.
* <br><br>Note: this method is VERY slow since it reads a single character per time. Consider using
* new LineReader(socket) or new BufferedStream(socket) instead.
*
* @return the read line or <code>null</code> if nothing was read.
* @throws totalcross.io.IOException
* @since SuperWaba 5.61
* @see totalcross.io.LineReader
* @see totalcross.io.BufferedStream
*/
public String readLine() throws totalcross.io.IOException
{
if (socketRef == null)
throw new totalcross.io.IOException("The socket is closed.");
if (rlbuf == null) // guich@tc123_31: no longer static and initialized on first use
rlbuf = new byte[256];
byte[] buf = rlbuf;
int pos = 0;
int r;
while ((r = readWriteBytes(buf, pos, 1, true)) == 1)
{
if (buf[pos] == '\n') // guich@tc123_47
{
if (pos > 0 && buf[pos-1] == '\r') // cut last \r
pos--;
// note that pos must be same of length, otherwise the String will be constructed with one less character
break;
}
if (++pos == buf.length) // reached buffer size?
{
byte[] temp = new byte[buf.length+256];
Vm.arrayCopy(buf, 0, temp, 0, pos);
rlbuf = buf = temp;
}
}
return (pos > 0 || r == 1) ? new String(Convert.charConverter.bytes2chars(buf, 0, pos)) : null; // brunosoares@582_11
}
protected void finalize()
{
try
{
close();
}
catch (totalcross.io.IOException e)
{
}
}
/** Used internally by the SSL classes. Not available at the device. */
public Object getNativeSocket()
{
return socketRef;
}
/**
* Returns the remote host which this socket is connected to, passed in the constructor.
*/
public String getHost()
{
return host;
}
/**
* Returns the remote port which this socket is connected to, passed in the constructor.
*/
public int getPort()
{
return port;
}
}