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 /** 044 * The default DatagramSocketFactory shared by all DatagramSocketClient instances. 045 */ 046 private static final DatagramSocketFactory DEFAULT_SOCKET_FACTORY = new DefaultDatagramSocketFactory(); 047 048 /** 049 * Charset to use for byte IO. 050 */ 051 private Charset charset = Charset.defaultCharset(); 052 053 /** The timeout to use after opening a socket. */ 054 protected int _timeout_; 055 056 /** The datagram socket used for the connection. */ 057 protected DatagramSocket _socket_; 058 059 /** 060 * A status variable indicating if the client's socket is currently open. 061 */ 062 protected boolean _isOpen_; 063 064 /** The datagram socket's DatagramSocketFactory. */ 065 protected DatagramSocketFactory _socketFactory_ = DEFAULT_SOCKET_FACTORY; 066 067 /** 068 * Constructs a new instance. Initializes _socket_ to null, _timeout_ to 0, and _isOpen_ to false. 069 */ 070 public DatagramSocketClient() { 071 } 072 073 /** 074 * Gets the non-null DatagramSocket or throws {@link NullPointerException}. 075 * 076 * <p> 077 * This method does not allocate resources. 078 * </p> 079 * 080 * @return the non-null DatagramSocket. 081 * @since 3.10.0 082 */ 083 protected DatagramSocket checkOpen() { 084 return Objects.requireNonNull(_socket_, "DatagramSocket"); 085 } 086 087 /** 088 * 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 089 * {@link #open open()} again. _isOpen_ is set to false and _socket_ is set to null. 090 */ 091 @Override 092 public void close() { 093 IOUtils.closeQuietly(_socket_); // DatagramSocket#close() doesn't throw in its signature. 094 _socket_ = null; 095 _isOpen_ = false; 096 } 097 098 /** 099 * Gets the charset. 100 * 101 * @return the charset. 102 * @since 3.3 103 */ 104 public Charset getCharset() { 105 return charset; 106 } 107 108 /** 109 * Gets the charset name. 110 * 111 * @return the charset name. 112 * @since 3.3 113 * @deprecated Use {@link #getCharset()} instead 114 */ 115 @Deprecated 116 public String getCharsetName() { 117 return charset.name(); 118 } 119 120 /** 121 * Gets the default timeout in milliseconds that is used when opening a socket. 122 * 123 * @return The default timeout in milliseconds that is used when opening a socket. 124 * @deprecated Use {@link #getDefaultTimeoutDuration()}. 125 */ 126 @Deprecated 127 public int getDefaultTimeout() { 128 return _timeout_; 129 } 130 131 /** 132 * Gets the default timeout duration that is used when opening a socket. 133 * 134 * @return The default timeout duration that is used when opening a socket. 135 * @since 3.13.0 136 */ 137 public Duration getDefaultTimeoutDuration() { 138 return Duration.ofMillis(_timeout_); 139 } 140 141 /** 142 * 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 143 * thrown. 144 * 145 * @return The local address to which the client's socket is bound. 146 */ 147 public InetAddress getLocalAddress() { 148 return checkOpen().getLocalAddress(); 149 } 150 151 /** 152 * 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 153 * NullPointerException is thrown. 154 * 155 * @return The port number of the open socket on the local host used for the connection. 156 */ 157 public int getLocalPort() { 158 return checkOpen().getLocalPort(); 159 } 160 161 /** 162 * 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 163 * thrown. 164 * 165 * @return The timeout in milliseconds of the currently opened socket. 166 * @throws SocketException if an error getting the timeout. 167 * @deprecated Use {@link #getSoTimeoutDuration()}. 168 */ 169 @Deprecated 170 public int getSoTimeout() throws SocketException { 171 return checkOpen().getSoTimeout(); 172 } 173 174 /** 175 * Gets the timeout duration of the currently opened socket. If you call this method when the client socket is not open, a NullPointerException is 176 * thrown. 177 * 178 * @return The timeout in milliseconds of the currently opened socket. 179 * @throws SocketException if an error getting the timeout. 180 */ 181 public Duration getSoTimeoutDuration() throws SocketException { 182 return Duration.ofMillis(checkOpen().getSoTimeout()); 183 } 184 185 /** 186 * Tests whether the client has a currently open socket. 187 * 188 * @return True if the client has a currently open socket, false otherwise. 189 */ 190 public boolean isOpen() { 191 return _isOpen_; 192 } 193 194 /** 195 * 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 196 * {@link #setDefaultTimeout setDefaultTimeout()}. 197 * <p> 198 * _isOpen_ is set to true after calling this method and _socket_ is set to the newly opened socket. 199 * </p> 200 * 201 * @throws SocketException If the socket could not be opened or the timeout could not be set. 202 */ 203 public void open() throws SocketException { 204 _socket_ = _socketFactory_.createDatagramSocket(); 205 _socket_.setSoTimeout(_timeout_); 206 _isOpen_ = true; 207 } 208 209 /** 210 * 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 211 * setDefaultTimeout()}. 212 * <p> 213 * _isOpen_ is set to true after calling this method and _socket_ is set to the newly opened socket. 214 * </p> 215 * 216 * @param port The port to use for the socket. 217 * @throws SocketException If the socket could not be opened or the timeout could not be set. 218 */ 219 public void open(final int port) throws SocketException { 220 _socket_ = _socketFactory_.createDatagramSocket(port); 221 _socket_.setSoTimeout(_timeout_); 222 _isOpen_ = true; 223 } 224 225 /** 226 * 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 227 * {@link #setDefaultTimeout setDefaultTimeout()}. 228 * <p> 229 * _isOpen_ is set to true after calling this method and _socket_ is set to the newly opened socket. 230 * </p> 231 * 232 * @param port The port to use for the socket. 233 * @param localAddress The local address to use. 234 * @throws SocketException If the socket could not be opened or the timeout could not be set. 235 */ 236 public void open(final int port, final InetAddress localAddress) throws SocketException { 237 _socket_ = _socketFactory_.createDatagramSocket(port, localAddress); 238 _socket_.setSoTimeout(_timeout_); 239 _isOpen_ = true; 240 } 241 242 /** 243 * Sets the charset. 244 * 245 * @param charset the charset. 246 * @since 3.3 247 */ 248 public void setCharset(final Charset charset) { 249 this.charset = charset; 250 } 251 252 /** 253 * Sets the DatagramSocketFactory used by the DatagramSocketClient to open DatagramSockets. If the factory value is null, then a default factory is used 254 * (only do this to reset the factory after having previously altered it). 255 * 256 * @param factory The new DatagramSocketFactory the DatagramSocketClient should use. 257 */ 258 public void setDatagramSocketFactory(final DatagramSocketFactory factory) { 259 if (factory == null) { 260 _socketFactory_ = DEFAULT_SOCKET_FACTORY; 261 } else { 262 _socketFactory_ = factory; 263 } 264 } 265 266 /** 267 * 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 268 * 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 269 * currently open socket. _timeout_ contains the new timeout value. 270 * 271 * @param timeout The timeout durations to use for the datagram socket connection. 272 */ 273 public void setDefaultTimeout(final Duration timeout) { 274 _timeout_ = Math.toIntExact(timeout.toMillis()); 275 } 276 277 /** 278 * 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 279 * 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 280 * currently open socket. _timeout_ contains the new timeout value. 281 * 282 * @param timeout The timeout in milliseconds to use for the datagram socket connection. 283 * @deprecated Use {@link #setDefaultTimeout(Duration)}. 284 */ 285 @Deprecated 286 public void setDefaultTimeout(final int timeout) { 287 _timeout_ = timeout; 288 } 289 290 /** 291 * Sets the timeout duration of a currently open connection. Only call this method after a connection has been opened by {@link #open open()}. 292 * 293 * @param timeout The timeout in milliseconds to use for the currently open datagram socket connection. 294 * @throws SocketException if an error setting the timeout. 295 * @since 3.10.0 296 */ 297 public void setSoTimeout(final Duration timeout) throws SocketException { 298 checkOpen().setSoTimeout(Math.toIntExact(timeout.toMillis())); 299 } 300 301 /** 302 * Sets the timeout in milliseconds of a currently open connection. Only call this method after a connection has been opened by {@link #open open()}. 303 * 304 * @param timeout The timeout in milliseconds to use for the currently open datagram socket connection. 305 * @throws SocketException if an error setting the timeout. 306 * @deprecated Use {@link #setSoTimeout(Duration)}. 307 */ 308 @Deprecated 309 public void setSoTimeout(final int timeout) throws SocketException { 310 checkOpen().setSoTimeout(timeout); 311 } 312}