blob: 532b53d5172eaffdbabd5e787bf4d002f8340b1c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package java.net;
import java.io.IOException;
import java.nio.channels.DatagramChannel;
import org.apache.harmony.luni.net.PlainDatagramSocketImpl;
/**
* This class implements a UDP socket for sending and receiving {@code
* DatagramPacket}. A {@code DatagramSocket} object can be used for both
* endpoints of a connection for a packet delivery service.
*
* @see DatagramPacket
* @see DatagramSocketImplFactory
*/
public class DatagramSocket {
DatagramSocketImpl impl;
InetAddress address;
int port = -1;
static DatagramSocketImplFactory factory;
boolean isBound = false;
private boolean isConnected = false;
private boolean isClosed = false;
private static class Lock {
}
private Object lock = new Lock();
/**
* Constructs a UDP datagram socket which is bound to any available port on
* the localhost.
*
* @throws SocketException
* if an error occurs while creating or binding the socket.
*/
public DatagramSocket() throws SocketException {
this(0);
}
/**
* Constructs a UDP datagram socket which is bound to the specific port
* {@code aPort} on the localhost. Valid values for {@code aPort} are
* between 0 and 65535 inclusive.
*
* @param aPort
* the port to bind on the localhost.
* @throws SocketException
* if an error occurs while creating or binding the socket.
*/
public DatagramSocket(int aPort) throws SocketException {
super();
checkListen(aPort);
createSocket(aPort, Inet4Address.ANY);
}
/**
* Constructs a UDP datagram socket which is bound to the specific local
* address {@code addr} on port {@code aPort}. Valid values for {@code
* aPort} are between 0 and 65535 inclusive.
*
* @param aPort
* the port to bind on the localhost.
* @param addr
* the address to bind on the localhost.
* @throws SocketException
* if an error occurs while creating or binding the socket.
*/
public DatagramSocket(int aPort, InetAddress addr) throws SocketException {
super();
checkListen(aPort);
createSocket(aPort, null == addr ? Inet4Address.ANY : addr);
}
/**
* Sends prior to attempting to bind the socket, checks whether the port is
* within the valid port range and verifies with the security manager that
* the port may be bound by the current context.
*
* @param aPort
* the port on the localhost that is to be bound.
*/
void checkListen(int aPort) {
if (aPort < 0 || aPort > 65535) {
throw new IllegalArgumentException("Port out of range: " + aPort);
}
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkListen(aPort);
}
}
/**
* Closes this UDP datagram socket and all possibly associated channels.
*/
// In the documentation jdk1.1.7a/guide/net/miscNet.html, this method is
// noted as not being synchronized.
public void close() {
isClosed = true;
impl.close();
}
/**
* Connects this UDP datagram socket to the specific target host with the
* address {@code anAdress} on port {@code aPort}. The host and port are
* validated, thereafter the only validation on {@code send()} and {@code
* receive()} is to check whether the packet address/port matches the
* connected target.
*
* @param anAddress
* the target address of this socket.
* @param aPort
* the target port of this socket.
*/
public void connect(InetAddress anAddress, int aPort) {
if (anAddress == null || aPort < 0 || aPort > 65535) {
throw new IllegalArgumentException("Address null or destination port out of range");
}
synchronized (lock) {
if (isClosed()) {
return;
}
try {
checkClosedAndBind(true);
} catch (SocketException e) {
// Ignored
}
SecurityManager security = System.getSecurityManager();
if (security != null) {
if (anAddress.isMulticastAddress()) {
security.checkMulticast(anAddress);
} else {
security.checkConnect(anAddress.getHostName(), aPort);
}
}
try {
impl.connect(anAddress, aPort);
} catch (SocketException e) {
// not connected at the native level just do what we did before
}
address = anAddress;
port = aPort;
isConnected = true;
}
}
/**
* Disconnects this UDP datagram socket from the remote host. This method
* called on an unconnected socket does nothing.
*/
public void disconnect() {
if (isClosed() || !isConnected()) {
return;
}
impl.disconnect();
address = null;
port = -1;
isConnected = false;
}
synchronized void createSocket(int aPort, InetAddress addr)
throws SocketException {
impl = factory != null ? factory.createDatagramSocketImpl()
: new PlainDatagramSocketImpl();
impl.create();
try {
impl.bind(aPort, addr);
isBound = true;
} catch (SocketException e) {
close();
throw e;
}
}
/**
* Gets the {@code InetAddress} instance representing the remote address to
* which this UDP datagram socket is connected.
*
* @return the remote address this socket is connected to or {@code null} if
* this socket is not connected.
*/
public InetAddress getInetAddress() {
return address;
}
/**
* Gets the {@code InetAddress} instance representing the bound local
* address of this UDP datagram socket.
*
* @return the local address to which this socket is bound to or {@code
* null} if this socket is closed.
*/
public InetAddress getLocalAddress() {
if (isClosed()) {
return null;
}
if (!isBound()) {
return Inet4Address.ANY;
}
InetAddress anAddr = impl.getLocalAddress();
try {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkConnect(anAddr.getHostName(), -1);
}
} catch (SecurityException e) {
return Inet4Address.ANY;
}
return anAddr;
}
/**
* Gets the local port which this socket is bound to.
*
* @return the local port of this socket or {@code -1} if this socket is
* closed and {@code 0} if it is unbound.
*/
public int getLocalPort() {
if (isClosed()) {
return -1;
}
if (!isBound()) {
return 0;
}
return impl.getLocalPort();
}
/**
* Gets the remote port which this socket is connected to.
*
* @return the remote port of this socket. The return value {@code -1}
* indicates that this socket is not connected.
*/
public int getPort() {
return port;
}
/**
* Indicates whether this socket is multicast or not.
*
* @return the return value is always {@code false}.
*/
boolean isMulticastSocket() {
return false;
}
/**
* Gets the socket receive buffer size. ( {@code SocketOptions.SO_RCVBUF} )
*
* @return the input buffer size.
* @throws SocketException
* if an error occurs while getting the option value.
*/
public synchronized int getReceiveBufferSize() throws SocketException {
checkClosedAndBind(false);
return ((Integer) impl.getOption(SocketOptions.SO_RCVBUF)).intValue();
}
/**
* Gets the socket send buffer size. ( {@code SocketOptions.SO_SNDBUF} )
*
* @return the output buffer size.
* @throws SocketException
* if an error occurs while getting the option value.
*/
public synchronized int getSendBufferSize() throws SocketException {
checkClosedAndBind(false);
return ((Integer) impl.getOption(SocketOptions.SO_SNDBUF)).intValue();
}
/**
* Gets the socket {@link SocketOptions#SO_TIMEOUT receive timeout}.
*
* @throws SocketException
* if an error occurs while getting the option value.
*/
public synchronized int getSoTimeout() throws SocketException {
checkClosedAndBind(false);
return ((Integer) impl.getOption(SocketOptions.SO_TIMEOUT)).intValue();
}
/**
* Receives a packet from this socket and stores it in the argument {@code
* pack}. All fields of {@code pack} must be set according to the data
* received. If the received data is longer than the packet buffer size it
* is truncated. This method blocks until a packet is received or a timeout
* has expired. If a security manager exists, its {@code checkAccept} method
* determines whether or not a packet is discarded. Any packets from
* unacceptable origins are silently discarded.
*
* @param pack
* the {@code DatagramPacket} to store the received data.
* @throws IOException
* if an error occurs while receiving the packet.
*/
public synchronized void receive(DatagramPacket pack) throws IOException {
checkClosedAndBind(true);
InetAddress senderAddr;
int senderPort;
DatagramPacket tempPack = new DatagramPacket(new byte[1], 1);
// means that we have received the packet into the temporary buffer
boolean copy = false;
SecurityManager security = System.getSecurityManager();
if (address != null || security != null) {
// The socket is connected or we need to check security permissions
// Check pack before peeking
if (pack == null) {
throw new NullPointerException();
}
// iterate over incoming packets
while (true) {
copy = false;
// let's get sender's address and port
try {
senderPort = impl.peekData(tempPack);
senderAddr = tempPack.getAddress();
} catch (SocketException e) {
if (e.getMessage().equals(
"The socket does not support the operation")) {
// receive packet to temporary buffer
tempPack = new DatagramPacket(new byte[pack.getCapacity()],
pack.getCapacity());
impl.receive(tempPack);
// tempPack's length field is now updated, capacity is unchanged
// let's extract address & port
senderAddr = tempPack.getAddress();
senderPort = tempPack.getPort();
copy = true;
} else {
throw e;
}
}
if (address == null) {
// if we are not connected let's check if we are allowed to
// receive packets from sender's address and port
try {
security.checkAccept(senderAddr.getHostName(),
senderPort);
// address & port are valid
break;
} catch (SecurityException e) {
if (!copy) {
// drop this packet and continue
impl.receive(tempPack);
}
}
} else if (port == senderPort && address.equals(senderAddr)) {
// we are connected and the packet came
// from the address & port we are connected to
break;
} else if (!copy) {
// drop packet and continue
impl.receive(tempPack);
}
}
}
if (copy) {
System.arraycopy(tempPack.getData(), 0, pack.getData(), pack
.getOffset(), tempPack.getLength());
// we shouldn't update the pack's capacity field in order to be
// compatible with RI
pack.setLengthOnly(tempPack.getLength());
pack.setAddress(tempPack.getAddress());
pack.setPort(tempPack.getPort());
} else {
pack.setLength(pack.getCapacity());
impl.receive(pack);
// pack's length field is now updated by native code call;
// pack's capacity field is unchanged
}
}
/**
* Sends a packet over this socket. The packet must satisfy the security
* policy before it may be sent. If a security manager is installed, this
* method checks whether it is allowed to send this packet to the specified
* address.
*
* @param pack
* the {@code DatagramPacket} which has to be sent.
* @throws IOException
* if an error occurs while sending the packet.
*/
public void send(DatagramPacket pack) throws IOException {
checkClosedAndBind(true);
InetAddress packAddr = pack.getAddress();
if (address != null) { // The socket is connected
if (packAddr != null) {
if (!address.equals(packAddr) || port != pack.getPort()) {
throw new IllegalArgumentException("Packet address mismatch with connected address");
}
} else {
pack.setAddress(address);
pack.setPort(port);
}
} else {
// not connected so the target address is not allowed to be null
if (packAddr == null) {
if (pack.getPort() == -1) {
throw new NullPointerException("Destination address is null");
}
return;
}
SecurityManager security = System.getSecurityManager();
if (security != null) {
if (packAddr.isMulticastAddress()) {
security.checkMulticast(packAddr);
} else {
security.checkConnect(packAddr.getHostName(), pack
.getPort());
}
}
}
impl.send(pack);
}
/**
* Sets the socket send buffer size. This buffer size determines which the
* maximum packet size is that can be sent over this socket. It depends on
* the network implementation what will happen if the packet is bigger than
* the buffer size. ( {@code SocketOptions.SO_SNDBUF} )
*
* @param size
* the buffer size in bytes. The size must be at least one byte.
* @throws SocketException
* if an error occurs while setting the option.
*/
public synchronized void setSendBufferSize(int size) throws SocketException {
if (size < 1) {
throw new IllegalArgumentException("size < 1");
}
checkClosedAndBind(false);
impl.setOption(SocketOptions.SO_SNDBUF, Integer.valueOf(size));
}
/**
* Sets the socket receive buffer size. This buffer size determines which
* the maximum packet size is that can be received over this socket. It
* depends on the network implementation what will happen if the packet is
* bigger than the buffer size. ( {@code SocketOptions.SO_RCVBUF} )
*
* @param size
* the buffer size in bytes. The size must be at least one byte.
* @throws SocketException
* if an error occurs while setting the option.
*/
public synchronized void setReceiveBufferSize(int size) throws SocketException {
if (size < 1) {
throw new IllegalArgumentException("size < 1");
}
checkClosedAndBind(false);
impl.setOption(SocketOptions.SO_RCVBUF, Integer.valueOf(size));
}
/**
* Sets the {@link SocketOptions#SO_TIMEOUT read timeout} in milliseconds for this socket.
* This receive timeout defines the period the socket will block waiting to
* receive data before throwing an {@code InterruptedIOException}. The value
* {@code 0} (default) is used to set an infinite timeout. To have effect
* this option must be set before the blocking method was called.
*
* @param timeout the timeout in milliseconds or 0 for no timeout.
* @throws SocketException
* if an error occurs while setting the option.
*/
public synchronized void setSoTimeout(int timeout) throws SocketException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout < 0");
}
checkClosedAndBind(false);
impl.setOption(SocketOptions.SO_TIMEOUT, Integer.valueOf(timeout));
}
/**
* Sets the socket implementation factory. This may only be invoked once
* over the lifetime of the application. This factory is used to create
* a new datagram socket implementation. If a security manager is set its
* method {@code checkSetFactory()} is called to check if the operation is
* allowed. A {@code SecurityException} is thrown if the operation is not
* allowed.
*
* @param fac
* the socket factory to use.
* @throws IOException
* if the factory has already been set.
* @see DatagramSocketImplFactory
*/
public static synchronized void setDatagramSocketImplFactory(
DatagramSocketImplFactory fac) throws IOException {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkSetFactory();
}
if (factory != null) {
throw new SocketException("Factory already set");
}
factory = fac;
}
/**
* Constructs a new {@code DatagramSocket} using the specific datagram
* socket implementation {@code socketImpl}. The created {@code
* DatagramSocket} will not be bound.
*
* @param socketImpl
* the DatagramSocketImpl to use.
*/
protected DatagramSocket(DatagramSocketImpl socketImpl) {
if (socketImpl == null) {
throw new NullPointerException();
}
impl = socketImpl;
}
/**
* Constructs a new {@code DatagramSocket} bound to the host/port specified
* by the {@code SocketAddress} {@code localAddr} or an unbound {@code
* DatagramSocket} if the {@code SocketAddress} is {@code null}.
*
* @param localAddr
* the local machine address and port to bind to.
* @throws IllegalArgumentException
* if the SocketAddress is not supported
* @throws SocketException
* if a problem occurs creating or binding the socket.
*/
public DatagramSocket(SocketAddress localAddr) throws SocketException {
if (localAddr != null) {
if (!(localAddr instanceof InetSocketAddress)) {
throw new IllegalArgumentException("Local address not an InetSocketAddress: " +
localAddr.getClass());
}
checkListen(((InetSocketAddress) localAddr).getPort());
}
impl = factory != null ? factory.createDatagramSocketImpl()
: new PlainDatagramSocketImpl();
impl.create();
if (localAddr != null) {
try {
bind(localAddr);
} catch (SocketException e) {
close();
throw e;
}
}
// SocketOptions.SO_BROADCAST is set by default for DatagramSocket
setBroadcast(true);
}
void checkClosedAndBind(boolean bind) throws SocketException {
if (isClosed()) {
throw new SocketException("Socket is closed");
}
if (bind && !isBound()) {
checkListen(0);
impl.bind(0, Inet4Address.ANY);
isBound = true;
}
}
/**
* Binds this socket to the local address and port specified by {@code
* localAddr}. If this value is {@code null} any free port on a valid local
* address is used.
*
* @param localAddr
* the local machine address and port to bind on.
* @throws IllegalArgumentException
* if the SocketAddress is not supported
* @throws SocketException
* if the socket is already bound or a problem occurs during
* binding.
*/
public void bind(SocketAddress localAddr) throws SocketException {
checkClosedAndBind(false);
int localPort = 0;
InetAddress addr = Inet4Address.ANY;
if (localAddr != null) {
if (!(localAddr instanceof InetSocketAddress)) {
throw new IllegalArgumentException("Local address not an InetSocketAddress: " +
localAddr.getClass());
}
InetSocketAddress inetAddr = (InetSocketAddress) localAddr;
addr = inetAddr.getAddress();
if (addr == null) {
throw new SocketException("Host is unresolved: " + inetAddr.getHostName());
}
localPort = inetAddr.getPort();
checkListen(localPort);
}
impl.bind(localPort, addr);
isBound = true;
}
/**
* Connects this datagram socket to the remote host and port specified by
* {@code remoteAddr}. The host and port are validated, thereafter the only
* validation on {@code send()} and {@code receive()} is that the packet
* address/port matches the connected target.
*
* @param remoteAddr
* the address and port of the target host.
* @throws SocketException
* if an error occurs during connecting.
*/
public void connect(SocketAddress remoteAddr) throws SocketException {
if (remoteAddr == null) {
throw new IllegalArgumentException("remoteAddr == null");
}
if (!(remoteAddr instanceof InetSocketAddress)) {
throw new IllegalArgumentException("Remote address not an InetSocketAddress: " +
remoteAddr.getClass());
}
InetSocketAddress inetAddr = (InetSocketAddress) remoteAddr;
if (inetAddr.getAddress() == null) {
throw new SocketException("Host is unresolved: " + inetAddr.getHostName());
}
synchronized (lock) {
// make sure the socket is open
checkClosedAndBind(true);
SecurityManager security = System.getSecurityManager();
if (security != null) {
if (inetAddr.getAddress().isMulticastAddress()) {
security.checkMulticast(inetAddr.getAddress());
} else {
security.checkConnect(inetAddr.getAddress().getHostName(),
inetAddr.getPort());
}
}
// now try to do the connection at the native level. To be
// compatible for the case when the address is inaddr_any we just
// eat the exception an act as if we are connected at the java level
try {
impl.connect(inetAddr.getAddress(), inetAddr.getPort());
} catch (Exception e) {
// not connected at the native level just do what we did before
}
// if we get here then we connected ok
address = inetAddr.getAddress();
port = inetAddr.getPort();
isConnected = true;
}
}
/**
* Determines whether the socket is bound to an address or not.
*
* @return {@code true} if the socket is bound, {@code false} otherwise.
*/
public boolean isBound() {
return isBound;
}
/**
* Determines whether the socket is connected to a target host.
*
* @return {@code true} if the socket is connected, {@code false} otherwise.
*/
public boolean isConnected() {
return isConnected;
}
/**
* Gets the address and port of the connected remote host. If this socket is
* not connected yet, {@code null} is returned.
*
* @return the remote socket address.
*/
public SocketAddress getRemoteSocketAddress() {
if (!isConnected()) {
return null;
}
return new InetSocketAddress(getInetAddress(), getPort());
}
/**
* Gets the bound local address and port of this socket. If the socket is
* unbound, {@code null} is returned.
*
* @return the local socket address.
*/
public SocketAddress getLocalSocketAddress() {
if (!isBound()) {
return null;
}
return new InetSocketAddress(getLocalAddress(), getLocalPort());
}
/**
* Sets the socket option {@code SocketOptions.SO_REUSEADDR}. This option
* has to be enabled if more than one UDP socket wants to be bound to the
* same address. That could be needed for receiving multicast packets.
* <p>
* There is an undefined behavior if this option is set after the socket is
* already bound.
*
* @param reuse
* the socket option value to enable or disable this option.
* @throws SocketException
* if the socket is closed or the option could not be set.
*/
public void setReuseAddress(boolean reuse) throws SocketException {
checkClosedAndBind(false);
impl.setOption(SocketOptions.SO_REUSEADDR, Boolean.valueOf(reuse));
}
/**
* Gets the state of the socket option {@code SocketOptions.SO_REUSEADDR}.
*
* @return {@code true} if the option is enabled, {@code false} otherwise.
* @throws SocketException
* if the socket is closed or the option is invalid.
*/
public boolean getReuseAddress() throws SocketException {
checkClosedAndBind(false);
return ((Boolean) impl.getOption(SocketOptions.SO_REUSEADDR))
.booleanValue();
}
/**
* Sets the socket option {@code SocketOptions.SO_BROADCAST}. This option
* must be enabled to send broadcast messages.
*
* @param broadcast
* the socket option value to enable or disable this option.
* @throws SocketException
* if the socket is closed or the option could not be set.
*/
public void setBroadcast(boolean broadcast) throws SocketException {
checkClosedAndBind(false);
impl.setOption(SocketOptions.SO_BROADCAST, Boolean.valueOf(broadcast));
}
/**
* Gets the state of the socket option {@code SocketOptions.SO_BROADCAST}.
*
* @return {@code true} if the option is enabled, {@code false} otherwise.
* @throws SocketException
* if the socket is closed or the option is invalid.
*/
public boolean getBroadcast() throws SocketException {
checkClosedAndBind(false);
return ((Boolean) impl.getOption(SocketOptions.SO_BROADCAST))
.booleanValue();
}
/**
* Sets the {@see SocketOptions#IP_TOS} value for every packet sent by this socket.
*
* @throws SocketException
* if the socket is closed or the option could not be set.
*/
public void setTrafficClass(int value) throws SocketException {
checkClosedAndBind(false);
if (value < 0 || value > 255) {
throw new IllegalArgumentException();
}
impl.setOption(SocketOptions.IP_TOS, Integer.valueOf(value));
}
/**
* Returns this socket's {@see SocketOptions#IP_TOS} setting.
*
* @throws SocketException
* if the socket is closed or the option is invalid.
*/
public int getTrafficClass() throws SocketException {
checkClosedAndBind(false);
return (Integer) impl.getOption(SocketOptions.IP_TOS);
}
/**
* Gets the state of this socket.
*
* @return {@code true} if the socket is closed, {@code false} otherwise.
*/
public boolean isClosed() {
return isClosed;
}
/**
* Gets the related DatagramChannel of this socket. This implementation
* returns always {@code null}.
*
* @return the related DatagramChannel or {@code null} if this socket was
* not created by a {@code DatagramChannel} object.
*/
public DatagramChannel getChannel() {
return null;
}
}