8ec378a6c8
Reviewed-by: jpai
886 lines
30 KiB
Java
886 lines
30 KiB
Java
/*
|
|
* Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code 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. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
/* Test utility classes
|
|
*
|
|
*/
|
|
|
|
import java.io.*;
|
|
import java.net.*;
|
|
import java.util.ArrayList;
|
|
import java.util.Date;
|
|
import java.util.List;
|
|
|
|
import jdk.test.lib.Utils;
|
|
|
|
|
|
public class TestServers {
|
|
|
|
private TestServers() { }
|
|
|
|
/**
|
|
* An abstract server identifies a server which listens on a port on a
|
|
* given machine.
|
|
*/
|
|
static abstract class AbstractServer {
|
|
|
|
private AbstractServer() {
|
|
}
|
|
|
|
public abstract int getPort();
|
|
|
|
public abstract InetAddress getAddress();
|
|
}
|
|
|
|
/**
|
|
* A downgraded type of AbstractServer which will refuse connections. Note:
|
|
* use it once and throw it away - this implementation opens an anonymous
|
|
* socket and closes it, returning the address of the closed socket. If
|
|
* other servers are started afterwards, the address/port might get reused
|
|
* and become connectable again - so it's not a good idea to assume that
|
|
* connections using this address/port will always be refused. Connections
|
|
* will be refused as long as the address/port of the refusing server has
|
|
* not been reused.
|
|
*/
|
|
static class RefusingServer extends AbstractServer {
|
|
|
|
final InetAddress address;
|
|
final int port;
|
|
|
|
private RefusingServer(InetAddress address, int port) {
|
|
this.address = address;
|
|
this.port = port;
|
|
}
|
|
|
|
@Override
|
|
public int getPort() {
|
|
return port;
|
|
}
|
|
|
|
@Override
|
|
public InetAddress getAddress() {
|
|
return address;
|
|
}
|
|
|
|
public static RefusingServer newRefusingServer() throws IOException {
|
|
return new RefusingServer(InetAddress.getLocalHost(),
|
|
Utils.refusingEndpoint().getPort());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An abstract class for implementing small TCP servers for the nio tests
|
|
* purposes. Disclaimer: This is a naive implementation that uses the old
|
|
* networking APIs (not those from {@code java.nio.*}) and shamelessly
|
|
* extends/creates Threads instead of using an executor service.
|
|
*/
|
|
static abstract class AbstractTcpServer extends AbstractServer
|
|
implements Runnable, Closeable {
|
|
|
|
protected final long linger; // #of ms to wait before responding
|
|
private Thread acceptThread; // thread waiting for accept
|
|
// list of opened connections that should be closed on close.
|
|
private List<TcpConnectionThread> connections = new ArrayList<>();
|
|
private ServerSocket serverSocket; // the server socket
|
|
private boolean started = false; // whether the server is started
|
|
Throwable error = null;
|
|
|
|
/**
|
|
* Creates a new abstract TCP server.
|
|
*
|
|
* @param linger the amount of time the server should wait before
|
|
* responding to requests.
|
|
*/
|
|
protected AbstractTcpServer(long linger) {
|
|
this.linger = linger;
|
|
}
|
|
|
|
/**
|
|
* The local port to which the server is bound.
|
|
*
|
|
* @return The local port to which the server is bound.
|
|
* @exception IllegalStateException is thrown if the server is not
|
|
* started.
|
|
*/
|
|
@Override
|
|
public final synchronized int getPort() {
|
|
if (!started) {
|
|
throw new IllegalStateException("Not started");
|
|
}
|
|
return serverSocket.getLocalPort();
|
|
}
|
|
|
|
/**
|
|
* The local address to which the server is bound.
|
|
*
|
|
* @return The local address to which the server is bound.
|
|
* @exception IllegalStateException is thrown if the server is not
|
|
* started.
|
|
*/
|
|
@Override
|
|
public final synchronized InetAddress getAddress() {
|
|
if (!started) {
|
|
throw new IllegalStateException("Not started");
|
|
}
|
|
return serverSocket.getInetAddress();
|
|
}
|
|
|
|
/**
|
|
* Tells whether the server is started.
|
|
*
|
|
* @return true if the server is started.
|
|
*/
|
|
public final synchronized boolean isStarted() {
|
|
return started;
|
|
}
|
|
|
|
/**
|
|
* Creates a new server socket.
|
|
*
|
|
* @param port local port to bind to.
|
|
* @param backlog requested maximum length of the queue of incoming
|
|
* connections.
|
|
* @param address local address to bind to.
|
|
* @return a new bound server socket ready to accept connections.
|
|
* @throws IOException if the socket cannot be created or bound.
|
|
*/
|
|
protected ServerSocket newServerSocket(int port, int backlog,
|
|
InetAddress address)
|
|
throws IOException {
|
|
return new ServerSocket(port, backlog, address);
|
|
}
|
|
|
|
/**
|
|
* Starts listening for connections.
|
|
*
|
|
* @throws IOException if the server socket cannot be created or bound.
|
|
*/
|
|
public final synchronized void start() throws IOException {
|
|
if (started) {
|
|
return;
|
|
}
|
|
final ServerSocket socket =
|
|
newServerSocket(0, 100, InetAddress.getLocalHost());
|
|
serverSocket = socket;
|
|
acceptThread = new Thread(this);
|
|
acceptThread.setDaemon(true);
|
|
acceptThread.start();
|
|
started = true;
|
|
}
|
|
|
|
/**
|
|
* Calls {@code Thread.sleep(linger);}
|
|
*/
|
|
protected final void lingerIfRequired() {
|
|
if (linger > 0) {
|
|
try {
|
|
Thread.sleep(linger);
|
|
} catch (InterruptedException x) {
|
|
Thread.interrupted();
|
|
final ServerSocket socket = serverSocket();
|
|
if (socket != null && !socket.isClosed()) {
|
|
System.err.println("Thread interrupted...");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
final synchronized ServerSocket serverSocket() {
|
|
return this.serverSocket;
|
|
}
|
|
|
|
/**
|
|
* The main accept loop.
|
|
*/
|
|
@Override
|
|
public final void run() {
|
|
final ServerSocket sSocket = serverSocket();
|
|
try {
|
|
Socket s;
|
|
while (isStarted() && !Thread.interrupted()
|
|
&& (s = sSocket.accept()) != null) {
|
|
lingerIfRequired();
|
|
listen(s);
|
|
}
|
|
} catch (Exception x) {
|
|
error = x;
|
|
} finally {
|
|
synchronized (this) {
|
|
if (!sSocket.isClosed()) {
|
|
try {
|
|
sSocket.close();
|
|
} catch (IOException x) {
|
|
System.err.println("Failed to close server socket");
|
|
}
|
|
}
|
|
if (started && this.serverSocket == sSocket) {
|
|
started = false;
|
|
this.serverSocket = null;
|
|
this.acceptThread = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Represents a connection accepted by the server.
|
|
*/
|
|
protected abstract class TcpConnectionThread extends Thread {
|
|
|
|
protected final Socket socket;
|
|
|
|
protected TcpConnectionThread(Socket socket) {
|
|
this.socket = socket;
|
|
this.setDaemon(true);
|
|
}
|
|
|
|
public void close() throws IOException {
|
|
socket.close();
|
|
interrupt();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a new TcpConnnectionThread to handle the connection through
|
|
* an accepted socket.
|
|
*
|
|
* @param s the socket returned by {@code serverSocket.accept()}.
|
|
* @return a new TcpConnnectionThread to handle the connection through
|
|
* an accepted socket.
|
|
*/
|
|
protected abstract TcpConnectionThread createConnection(Socket s);
|
|
|
|
/**
|
|
* Creates and starts a new TcpConnectionThread to handle the accepted
|
|
* socket.
|
|
*
|
|
* @param s the socket returned by {@code serverSocket.accept()}.
|
|
*/
|
|
private synchronized void listen(Socket s) {
|
|
TcpConnectionThread c = createConnection(s);
|
|
c.start();
|
|
addConnection(c);
|
|
}
|
|
|
|
/**
|
|
* Add the connection to the list of accepted connections.
|
|
*
|
|
* @param connection an accepted connection.
|
|
*/
|
|
protected synchronized void addConnection(
|
|
TcpConnectionThread connection) {
|
|
connections.add(connection);
|
|
}
|
|
|
|
/**
|
|
* Remove the connection from the list of accepted connections.
|
|
*
|
|
* @param connection an accepted connection.
|
|
*/
|
|
protected synchronized void removeConnection(
|
|
TcpConnectionThread connection) {
|
|
connections.remove(connection);
|
|
}
|
|
|
|
/**
|
|
* Close the server socket and all the connections present in the list
|
|
* of accepted connections.
|
|
*
|
|
* @throws IOException
|
|
*/
|
|
@Override
|
|
public synchronized void close() throws IOException {
|
|
if (serverSocket != null && !serverSocket.isClosed()) {
|
|
serverSocket.close();
|
|
}
|
|
if (acceptThread != null) {
|
|
acceptThread.interrupt();
|
|
}
|
|
int failed = 0;
|
|
for (TcpConnectionThread c : connections) {
|
|
try {
|
|
c.close();
|
|
} catch (IOException x) {
|
|
// no matter - we're closing.
|
|
failed++;
|
|
}
|
|
}
|
|
connections.clear();
|
|
if (failed > 0) {
|
|
throw new IOException("Failed to close some connections");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A small TCP Server that emulates the echo service for tests purposes. See
|
|
* http://en.wikipedia.org/wiki/Echo_Protocol This server uses an anonymous
|
|
* port - NOT the standard port 7. We don't guarantee that its behavior
|
|
* exactly matches the RFC - the only purpose of this server is to have
|
|
* something that responds to nio tests...
|
|
*/
|
|
static class EchoServer extends AbstractTcpServer {
|
|
|
|
public EchoServer() {
|
|
this(0L);
|
|
}
|
|
|
|
public EchoServer(long linger) {
|
|
super(linger);
|
|
}
|
|
|
|
@Override
|
|
protected TcpConnectionThread createConnection(Socket s) {
|
|
return new EchoConnection(s);
|
|
}
|
|
|
|
private final class EchoConnection extends TcpConnectionThread {
|
|
|
|
public EchoConnection(Socket socket) {
|
|
super(socket);
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
final InputStream is = socket.getInputStream();
|
|
final OutputStream out = socket.getOutputStream();
|
|
byte[] b = new byte[255];
|
|
int n;
|
|
while ((n = is.read(b)) > 0) {
|
|
lingerIfRequired();
|
|
out.write(b, 0, n);
|
|
}
|
|
} catch (IOException io) {
|
|
// fall through to finally
|
|
} finally {
|
|
if (!socket.isClosed()) {
|
|
try {
|
|
socket.close();
|
|
} catch (IOException x) {
|
|
System.err.println(
|
|
"Failed to close echo connection socket");
|
|
}
|
|
}
|
|
removeConnection(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static EchoServer startNewServer() throws IOException {
|
|
return startNewServer(0);
|
|
}
|
|
|
|
public static EchoServer startNewServer(long linger) throws IOException {
|
|
final EchoServer echoServer = new EchoServer(linger);
|
|
echoServer.start();
|
|
return echoServer;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A small TCP Server that accept connections but does not response to any input.
|
|
*/
|
|
static final class NoResponseServer extends EchoServer {
|
|
public NoResponseServer() {
|
|
super(Long.MAX_VALUE);
|
|
}
|
|
|
|
public static NoResponseServer startNewServer() throws IOException {
|
|
final NoResponseServer noResponseServer = new NoResponseServer();
|
|
noResponseServer.start();
|
|
return noResponseServer;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A small TCP server that emulates the Day & Time service for tests
|
|
* purposes. See http://en.wikipedia.org/wiki/Daytime_Protocol This server
|
|
* uses an anonymous port - NOT the standard port 13. We don't guarantee
|
|
* that its behavior exactly matches the RFC - the only purpose of this
|
|
* server is to have something that responds to nio tests...
|
|
*/
|
|
static final class DayTimeServer extends AbstractTcpServer {
|
|
|
|
public DayTimeServer() {
|
|
this(0L);
|
|
}
|
|
|
|
public DayTimeServer(long linger) {
|
|
super(linger);
|
|
}
|
|
|
|
@Override
|
|
protected TcpConnectionThread createConnection(Socket s) {
|
|
return new DayTimeServerConnection(s);
|
|
}
|
|
|
|
@Override
|
|
protected void addConnection(TcpConnectionThread connection) {
|
|
// do nothing - the connection just write the date and terminates.
|
|
}
|
|
|
|
@Override
|
|
protected void removeConnection(TcpConnectionThread connection) {
|
|
// do nothing - we're not adding connections to the list...
|
|
}
|
|
|
|
private final class DayTimeServerConnection extends TcpConnectionThread {
|
|
|
|
public DayTimeServerConnection(Socket socket) {
|
|
super(socket);
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
final OutputStream out = socket.getOutputStream();
|
|
lingerIfRequired();
|
|
out.write(new Date(System.currentTimeMillis())
|
|
.toString().getBytes("US-ASCII"));
|
|
out.flush();
|
|
} catch (IOException io) {
|
|
// fall through to finally
|
|
} finally {
|
|
if (!socket.isClosed()) {
|
|
try {
|
|
socket.close();
|
|
} catch (IOException x) {
|
|
System.err.println(
|
|
"Failed to close echo connection socket");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static DayTimeServer startNewServer()
|
|
throws IOException {
|
|
return startNewServer(0);
|
|
}
|
|
|
|
public static DayTimeServer startNewServer(long linger)
|
|
throws IOException {
|
|
final DayTimeServer daytimeServer = new DayTimeServer(linger);
|
|
daytimeServer.start();
|
|
return daytimeServer;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An abstract class for implementing small UDP Servers for the nio tests
|
|
* purposes. Disclaimer: This is a naive implementation that uses the old
|
|
* networking APIs (not those from {@code java.nio.*}) and shamelessly
|
|
* extends/creates Threads instead of using an executor service.
|
|
*/
|
|
static abstract class AbstractUdpServer extends AbstractServer
|
|
implements Runnable, Closeable {
|
|
|
|
protected final long linger; // #of ms to wait before responding
|
|
protected final InetAddress bindAddress; //local address to bind to; can be null.
|
|
private Thread acceptThread; // thread waiting for packets
|
|
private DatagramSocket serverSocket; // the server socket
|
|
private boolean started = false; // whether the server is started
|
|
Throwable error = null;
|
|
|
|
/**
|
|
* Creates a new abstract UDP server.
|
|
*
|
|
* @param linger the amount of time the server should wait before
|
|
* responding to requests.
|
|
*/
|
|
protected AbstractUdpServer(long linger) {
|
|
this(linger, null);
|
|
}
|
|
|
|
/**
|
|
* Creates a new abstract UDP server.
|
|
*
|
|
* @param linger the amount of time the server should wait before
|
|
* responding to requests.
|
|
* @param bindAddress the address to bind to. If {@code null}, will
|
|
* bind to InetAddress.getLocalHost();
|
|
*/
|
|
protected AbstractUdpServer(long linger, InetAddress bindAddress) {
|
|
this.linger = linger;
|
|
this.bindAddress = bindAddress;
|
|
}
|
|
|
|
/**
|
|
* The local port to which the server is bound.
|
|
*
|
|
* @return The local port to which the server is bound.
|
|
* @exception IllegalStateException is thrown if the server is not
|
|
* started.
|
|
*/
|
|
@Override
|
|
public final synchronized int getPort() {
|
|
if (!started) {
|
|
throw new IllegalStateException("Not started");
|
|
}
|
|
return serverSocket.getLocalPort();
|
|
}
|
|
|
|
/**
|
|
* The local address to which the server is bound.
|
|
*
|
|
* @return The local address to which the server is bound.
|
|
* @exception IllegalStateException is thrown if the server is not
|
|
* started.
|
|
*/
|
|
@Override
|
|
public final synchronized InetAddress getAddress() {
|
|
if (!started) {
|
|
throw new IllegalStateException("Not started");
|
|
}
|
|
return serverSocket.getLocalAddress();
|
|
}
|
|
|
|
/**
|
|
* Tells whether the server is started.
|
|
*
|
|
* @return true if the server is started.
|
|
*/
|
|
public final synchronized boolean isStarted() {
|
|
return started;
|
|
}
|
|
|
|
/**
|
|
* Creates a new datagram socket.
|
|
*
|
|
* @param port local port to bind to.
|
|
* @param address local address to bind to.
|
|
* @return a new bound server socket ready to listen for packets.
|
|
* @throws IOException if the socket cannot be created or bound.
|
|
*/
|
|
protected DatagramSocket newDatagramSocket(int port,
|
|
InetAddress address)
|
|
throws IOException {
|
|
return new DatagramSocket(port, address);
|
|
}
|
|
|
|
/**
|
|
* Starts listening for connections.
|
|
*
|
|
* @throws IOException if the server socket cannot be created or bound.
|
|
*/
|
|
public final synchronized void start() throws IOException {
|
|
if (started) {
|
|
return;
|
|
}
|
|
InetAddress lh = bindAddress == null ? InetAddress.getLocalHost() : bindAddress;
|
|
final DatagramSocket socket =
|
|
newDatagramSocket(0, lh);
|
|
serverSocket = socket;
|
|
acceptThread = new Thread(this);
|
|
acceptThread.setDaemon(true);
|
|
acceptThread.start();
|
|
started = true;
|
|
}
|
|
|
|
/**
|
|
* Calls {@code Thread.sleep(linger);}
|
|
*/
|
|
protected final void lingerIfRequired() {
|
|
if (linger > 0) {
|
|
try {
|
|
Thread.sleep(linger);
|
|
} catch (InterruptedException x) {
|
|
Thread.interrupted();
|
|
final DatagramSocket socket = serverSocket();
|
|
if (socket != null && !socket.isClosed()) {
|
|
System.err.println("Thread interrupted...");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
final synchronized DatagramSocket serverSocket() {
|
|
return this.serverSocket;
|
|
}
|
|
|
|
final synchronized boolean send(DatagramSocket socket,
|
|
DatagramPacket response) throws IOException {
|
|
if (!socket.isClosed()) {
|
|
socket.send(response);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The main receive loop.
|
|
*/
|
|
@Override
|
|
public final void run() {
|
|
final DatagramSocket sSocket = serverSocket();
|
|
try {
|
|
final int size = Math.max(1024, sSocket.getReceiveBufferSize());
|
|
if (size > sSocket.getReceiveBufferSize()) {
|
|
sSocket.setReceiveBufferSize(size);
|
|
}
|
|
while (isStarted() && !Thread.interrupted() && !sSocket.isClosed()) {
|
|
final byte[] buf = new byte[size];
|
|
final DatagramPacket packet =
|
|
new DatagramPacket(buf, buf.length);
|
|
lingerIfRequired();
|
|
sSocket.receive(packet);
|
|
//System.out.println("Received packet from: "
|
|
// + packet.getAddress()+":"+packet.getPort());
|
|
handle(sSocket, packet);
|
|
}
|
|
} catch (Exception x) {
|
|
error = x;
|
|
} finally {
|
|
synchronized (this) {
|
|
if (!sSocket.isClosed()) {
|
|
sSocket.close();
|
|
}
|
|
if (started && this.serverSocket == sSocket) {
|
|
started = false;
|
|
this.serverSocket = null;
|
|
this.acceptThread = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Represents an UDP request received by the server.
|
|
*/
|
|
protected abstract class UdpRequestThread extends Thread {
|
|
|
|
protected final DatagramPacket request;
|
|
protected final DatagramSocket socket;
|
|
|
|
protected UdpRequestThread(DatagramSocket socket, DatagramPacket request) {
|
|
this.socket = socket;
|
|
this.request = request;
|
|
this.setDaemon(true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a new UdpRequestThread to handle a DatagramPacket received
|
|
* through a DatagramSocket.
|
|
*
|
|
* @param socket the socket through which the request was received.
|
|
* @param request the datagram packet received through the socket.
|
|
* @return a new UdpRequestThread to handle the request received through
|
|
* a DatagramSocket.
|
|
*/
|
|
protected abstract UdpRequestThread createConnection(DatagramSocket socket,
|
|
DatagramPacket request);
|
|
|
|
/**
|
|
* Creates and starts a new UdpRequestThread to handle the received
|
|
* datagram packet.
|
|
*
|
|
* @param socket the socket through which the request was received.
|
|
* @param request the datagram packet received through the socket.
|
|
*/
|
|
private synchronized void handle(DatagramSocket socket,
|
|
DatagramPacket request) {
|
|
UdpRequestThread c = createConnection(socket, request);
|
|
// c can be null if the request requires no response.
|
|
if (c != null) {
|
|
c.start();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Close the server socket.
|
|
*
|
|
* @throws IOException
|
|
*/
|
|
@Override
|
|
public synchronized void close() throws IOException {
|
|
if (serverSocket != null && !serverSocket.isClosed()) {
|
|
serverSocket.close();
|
|
}
|
|
if (acceptThread != null) {
|
|
acceptThread.interrupt();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A small UDP Server that emulates the discard service for tests purposes.
|
|
* See http://en.wikipedia.org/wiki/Discard_Protocol This server uses an
|
|
* anonymous port - NOT the standard port 9. We don't guarantee that its
|
|
* behavior exactly matches the RFC - the only purpose of this server is to
|
|
* have something that responds to nio tests...
|
|
*/
|
|
static final class UdpDiscardServer extends AbstractUdpServer {
|
|
|
|
public UdpDiscardServer() {
|
|
this(0L);
|
|
}
|
|
|
|
public UdpDiscardServer(long linger) {
|
|
super(linger);
|
|
}
|
|
|
|
@Override
|
|
protected UdpRequestThread createConnection(DatagramSocket socket,
|
|
DatagramPacket request) {
|
|
// no response required
|
|
return null;
|
|
}
|
|
|
|
public static UdpDiscardServer startNewServer() throws IOException {
|
|
return startNewServer(0);
|
|
}
|
|
|
|
public static UdpDiscardServer startNewServer(long linger) throws IOException {
|
|
final UdpDiscardServer discardServer = new UdpDiscardServer(linger);
|
|
discardServer.start();
|
|
return discardServer;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A small UDP Server that emulates the echo service for tests purposes. See
|
|
* http://en.wikipedia.org/wiki/Echo_Protocol This server uses an anonymous
|
|
* port - NOT the standard port 7. We don't guarantee that its behavior
|
|
* exactly matches the RFC - the only purpose of this server is to have
|
|
* something that responds to nio tests...
|
|
*/
|
|
static final class UdpEchoServer extends AbstractUdpServer {
|
|
|
|
public UdpEchoServer() {
|
|
this(0L);
|
|
}
|
|
|
|
public UdpEchoServer(long linger) {
|
|
this(linger, null);
|
|
}
|
|
|
|
public UdpEchoServer(long linger, InetAddress bindAddress) {
|
|
super(linger, bindAddress);
|
|
}
|
|
|
|
@Override
|
|
protected UdpEchoRequest createConnection(DatagramSocket socket,
|
|
DatagramPacket request) {
|
|
return new UdpEchoRequest(socket, request);
|
|
}
|
|
|
|
private final class UdpEchoRequest extends UdpRequestThread {
|
|
|
|
public UdpEchoRequest(DatagramSocket socket, DatagramPacket request) {
|
|
super(socket, request);
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
lingerIfRequired();
|
|
final DatagramPacket response =
|
|
new DatagramPacket(request.getData(),
|
|
request.getOffset(), request.getLength(),
|
|
request.getAddress(), request.getPort());
|
|
send(socket, response);
|
|
} catch (IOException io) {
|
|
System.err.println("Failed to send response: " + io);
|
|
io.printStackTrace(System.err);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static UdpEchoServer startNewServer() throws IOException {
|
|
return startNewServer(0);
|
|
}
|
|
|
|
public static UdpEchoServer startNewServer(long linger) throws IOException {
|
|
return startNewServer(0, InetAddress.getLocalHost());
|
|
}
|
|
|
|
public static UdpEchoServer startNewServer(long linger, InetAddress bindAddress) throws IOException {
|
|
final UdpEchoServer echoServer = new UdpEchoServer(linger, bindAddress);
|
|
echoServer.start();
|
|
return echoServer;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A small UDP server that emulates the Day & Time service for tests
|
|
* purposes. See http://en.wikipedia.org/wiki/Daytime_Protocol This server
|
|
* uses an anonymous port - NOT the standard port 13. We don't guarantee
|
|
* that its behavior exactly matches the RFC - the only purpose of this
|
|
* server is to have something that responds to nio tests...
|
|
*/
|
|
static final class UdpDayTimeServer extends AbstractUdpServer {
|
|
|
|
public UdpDayTimeServer() {
|
|
this(0L);
|
|
}
|
|
|
|
public UdpDayTimeServer(long linger) {
|
|
super(linger);
|
|
}
|
|
|
|
@Override
|
|
protected UdpDayTimeRequestThread createConnection(DatagramSocket socket,
|
|
DatagramPacket request) {
|
|
return new UdpDayTimeRequestThread(socket, request);
|
|
}
|
|
|
|
private final class UdpDayTimeRequestThread extends UdpRequestThread {
|
|
|
|
public UdpDayTimeRequestThread(DatagramSocket socket,
|
|
DatagramPacket request) {
|
|
super(socket, request);
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
lingerIfRequired();
|
|
final byte[] data = new Date(System.currentTimeMillis())
|
|
.toString().getBytes("US-ASCII");
|
|
final DatagramPacket response =
|
|
new DatagramPacket(data, 0, data.length,
|
|
request.getAddress(), request.getPort());
|
|
send(socket, response);
|
|
} catch (IOException io) {
|
|
System.err.println("Failed to send response: " + io);
|
|
io.printStackTrace(System.err);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static UdpDayTimeServer startNewServer() throws IOException {
|
|
return startNewServer(0);
|
|
}
|
|
|
|
public static UdpDayTimeServer startNewServer(long linger)
|
|
throws IOException {
|
|
final UdpDayTimeServer echoServer = new UdpDayTimeServer(linger);
|
|
echoServer.start();
|
|
return echoServer;
|
|
}
|
|
}
|
|
}
|