001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.net; 019 020import java.net.DatagramSocket; 021import java.net.InetAddress; 022import java.net.SocketException; 023import java.nio.charset.Charset; 024import java.time.Duration; 025import java.util.Objects; 026 027import org.apache.commons.io.IOUtils; 028 029/** 030 * The DatagramSocketClient provides the basic operations that are required of client objects accessing datagram sockets. It is meant to be subclassed to avoid 031 * having to rewrite the same code over and over again to open a socket, close a socket, set timeouts, etc. Of special note is the 032 * {@link #setDatagramSocketFactory setDatagramSocketFactory)} method, which allows you to control the type of DatagramSocket the DatagramSocketClient creates 033 * for network communications. This is especially useful for adding things like proxy support as well as better support for applets. For example, you could 034 * create a {@link org.apache.commons.net.DatagramSocketFactory} that requests browser security capabilities before creating a socket. All classes derived from 035 * DatagramSocketClient should use the {@link #_socketFactory_ _socketFactory_} member variable to create DatagramSocket instances rather than instantiating 036 * them by directly invoking a constructor. By honoring this contract you guarantee that a user will always be able to provide his own Socket implementations by 037 * substituting his own SocketFactory. 038 * 039 * @see DatagramSocketFactory 040 */ 041public abstract class DatagramSocketClient implements AutoCloseable { 042 /** 043 * The default DatagramSocketFactory shared by all DatagramSocketClient instances. 044 */ 045 private static final DatagramSocketFactory DEFAULT_SOCKET_FACTORY = new DefaultDatagramSocketFactory(); 046 047 /** 048 * Charset to use for byte IO. 049 */ 050 private Charset charset = Charset.defaultCharset(); 051 052 /** The timeout to use after opening a socket. */ 053 protected int _timeout_; 054 055 /** The datagram socket used for the connection. */ 056 protected DatagramSocket _socket_; 057 058 /** 059 * A status variable indicating if the client's socket is currently open. 060 */ 061 protected boolean _isOpen_; 062 063 /** The datagram socket's DatagramSocketFactory. */ 064 protected DatagramSocketFactory _socketFactory_ = DEFAULT_SOCKET_FACTORY; 065 066 /** 067 * Constructs a new instance. Initializes _socket_ to null, _timeout_ to 0, and _isOpen_ to false. 068 */ 069 public DatagramSocketClient() { 070 } 071 072 /** 073 * Gets the non-null DatagramSocket or throws {@link NullPointerException}. 074 * 075 * <p> 076 * This method does not allocate resources. 077 * </p> 078 * 079 * @return the non-null DatagramSocket. 080 * @since 3.10.0 081 */ 082 protected DatagramSocket checkOpen() { 083 return Objects.requireNonNull(_socket_, "DatagramSocket"); 084 } 085 086 /** 087 * Closes the DatagramSocket used for the connection. You should call this method after you've finished using the class instance and also before you call 088 * {@link #open open()} again. _isOpen_ is set to false and _socket_ is set to null. 089 */ 090 @Override 091 public void close() { 092 IOUtils.closeQuietly(_socket_); // DatagramSocket#close() doesn't throw in its signature. 093 _socket_ = null; 094 _isOpen_ = false; 095 } 096 097 /** 098 * Gets the charset. 099 * 100 * @return the charset. 101 * @since 3.3 102 */ 103 public Charset getCharset() { 104 return charset; 105 } 106 107 /** 108 * Gets the charset name. 109 * 110 * @return the charset name. 111 * @since 3.3 112 * @deprecated Use {@link #getCharset()} instead 113 */ 114 @Deprecated 115 public String getCharsetName() { 116 return charset.name(); 117 } 118 119 /** 120 * Gets the default timeout in milliseconds that is used when opening a socket. 121 * 122 * @return The default timeout in milliseconds that is used when opening a socket. 123 */ 124 public int getDefaultTimeout() { 125 return _timeout_; 126 } 127 128 /** 129 * Gets the local address to which the client's socket is bound. If you call this method when the client socket is not open, a NullPointerException is 130 * thrown. 131 * 132 * @return The local address to which the client's socket is bound. 133 */ 134 public InetAddress getLocalAddress() { 135 return checkOpen().getLocalAddress(); 136 } 137 138 /** 139 * Gets the port number of the open socket on the local host used for the connection. If you call this method when the client socket is not open, a 140 * NullPointerException is thrown. 141 * 142 * @return The port number of the open socket on the local host used for the connection. 143 */ 144 public int getLocalPort() { 145 return checkOpen().getLocalPort(); 146 } 147 148 /** 149 * Gets the timeout in milliseconds of the currently opened socket. If you call this method when the client socket is not open, a NullPointerException is 150 * thrown. 151 * 152 * @return The timeout in milliseconds of the currently opened socket. 153 * @throws SocketException if an error getting the timeout. 154 * @deprecated Use {@link #getSoTimeoutDuration()}. 155 */ 156 @Deprecated 157 public int getSoTimeout() throws SocketException { 158 return checkOpen().getSoTimeout(); 159 } 160 161 /** 162 * Gets the timeout duration of the currently opened socket. If you call this method when the client socket is not open, a NullPointerException is 163 * thrown. 164 * 165 * @return The timeout in milliseconds of the currently opened socket. 166 * @throws SocketException if an error getting the timeout. 167 */ 168 public Duration getSoTimeoutDuration() throws SocketException { 169 return Duration.ofMillis(checkOpen().getSoTimeout()); 170 } 171 172 /** 173 * Tests whether the client has a currently open socket. 174 * 175 * @return True if the client has a currently open socket, false otherwise. 176 */ 177 public boolean isOpen() { 178 return _isOpen_; 179 } 180 181 /** 182 * Opens a DatagramSocket on the local host at the first available port. Also sets the timeout on the socket to the default timeout set by 183 * {@link #setDefaultTimeout setDefaultTimeout()}. 184 * <p> 185 * _isOpen_ is set to true after calling this method and _socket_ is set to the newly opened socket. 186 * </p> 187 * 188 * @throws SocketException If the socket could not be opened or the timeout could not be set. 189 */ 190 public void open() throws SocketException { 191 _socket_ = _socketFactory_.createDatagramSocket(); 192 _socket_.setSoTimeout(_timeout_); 193 _isOpen_ = true; 194 } 195 196 /** 197 * Opens a DatagramSocket on the local host at a specified port. Also sets the timeout on the socket to the default timeout set by {@link #setDefaultTimeout 198 * setDefaultTimeout()}. 199 * <p> 200 * _isOpen_ is set to true after calling this method and _socket_ is set to the newly opened socket. 201 * </p> 202 * 203 * @param port The port to use for the socket. 204 * @throws SocketException If the socket could not be opened or the timeout could not be set. 205 */ 206 public void open(final int port) throws SocketException { 207 _socket_ = _socketFactory_.createDatagramSocket(port); 208 _socket_.setSoTimeout(_timeout_); 209 _isOpen_ = true; 210 } 211 212 /** 213 * Opens a DatagramSocket at the specified address on the local host at a specified port. Also sets the timeout on the socket to the default timeout set by 214 * {@link #setDefaultTimeout setDefaultTimeout()}. 215 * <p> 216 * _isOpen_ is set to true after calling this method and _socket_ is set to the newly opened socket. 217 * </p> 218 * 219 * @param port The port to use for the socket. 220 * @param localAddress The local address to use. 221 * @throws SocketException If the socket could not be opened or the timeout could not be set. 222 */ 223 public void open(final int port, final InetAddress localAddress) throws SocketException { 224 _socket_ = _socketFactory_.createDatagramSocket(port, localAddress); 225 _socket_.setSoTimeout(_timeout_); 226 _isOpen_ = true; 227 } 228 229 /** 230 * Sets the charset. 231 * 232 * @param charset the charset. 233 * @since 3.3 234 */ 235 public void setCharset(final Charset charset) { 236 this.charset = charset; 237 } 238 239 /** 240 * Sets the DatagramSocketFactory used by the DatagramSocketClient to open DatagramSockets. If the factory value is null, then a default factory is used 241 * (only do this to reset the factory after having previously altered it). 242 * 243 * @param factory The new DatagramSocketFactory the DatagramSocketClient should use. 244 */ 245 public void setDatagramSocketFactory(final DatagramSocketFactory factory) { 246 if (factory == null) { 247 _socketFactory_ = DEFAULT_SOCKET_FACTORY; 248 } else { 249 _socketFactory_ = factory; 250 } 251 } 252 253 /** 254 * Sets the default timeout in to use when opening a socket. After a call to open, the timeout for the socket is set using this value. This 255 * method should be used prior to a call to {@link #open open()} and should not be confused with {@link #setSoTimeout setSoTimeout()} which operates on the 256 * currently open socket. _timeout_ contains the new timeout value. 257 * 258 * @param timeout The timeout durations to use for the datagram socket connection. 259 */ 260 public void setDefaultTimeout(final Duration timeout) { 261 _timeout_ = Math.toIntExact(timeout.toMillis()); 262 } 263 264 /** 265 * Sets the default timeout in milliseconds to use when opening a socket. After a call to open, the timeout for the socket is set using this value. This 266 * method should be used prior to a call to {@link #open open()} and should not be confused with {@link #setSoTimeout setSoTimeout()} which operates on the 267 * currently open socket. _timeout_ contains the new timeout value. 268 * 269 * @param timeout The timeout in milliseconds to use for the datagram socket connection. 270 * @deprecated Use {@link #setDefaultTimeout(Duration)}. 271 */ 272 @Deprecated 273 public void setDefaultTimeout(final int timeout) { 274 _timeout_ = timeout; 275 } 276 277 /** 278 * Sets the timeout duration of a currently open connection. Only call this method after a connection has been opened by {@link #open open()}. 279 * 280 * @param timeout The timeout in milliseconds to use for the currently open datagram socket connection. 281 * @throws SocketException if an error setting the timeout. 282 * @since 3.10.0 283 */ 284 public void setSoTimeout(final Duration timeout) throws SocketException { 285 checkOpen().setSoTimeout(Math.toIntExact(timeout.toMillis())); 286 } 287 288 /** 289 * Sets the timeout in milliseconds of a currently open connection. Only call this method after a connection has been opened by {@link #open open()}. 290 * 291 * @param timeout The timeout in milliseconds to use for the currently open datagram socket connection. 292 * @throws SocketException if an error setting the timeout. 293 * @deprecated Use {@link #setSoTimeout(Duration)}. 294 */ 295 @Deprecated 296 public void setSoTimeout(final int timeout) throws SocketException { 297 checkOpen().setSoTimeout(timeout); 298 } 299}