
zmq.io.net.tcp.TcpConnecter Maven / Gradle / Ivy
package zmq.io.net.tcp;
import java.io.IOException;
import java.net.SocketAddress;
import java.nio.channels.SocketChannel;
import zmq.Options;
import zmq.Own;
import zmq.SocketBase;
import zmq.ZError;
import zmq.io.IOObject;
import zmq.io.IOThread;
import zmq.io.SessionBase;
import zmq.io.StreamEngine;
import zmq.io.net.Address;
import zmq.io.net.StandardProtocolFamily;
import zmq.poll.IPollEvents;
import zmq.poll.Poller;
import zmq.util.Utils;
// If 'delay' is true connecter first waits for a while, then starts
// connection process.
public class TcpConnecter extends Own implements IPollEvents
{
// ID of the timer used to delay the reconnection.
protected static final int RECONNECT_TIMER_ID = 1;
protected final IOObject ioObject;
// Address to connect to. Owned by session_base_t.
private final Address addr;
// Underlying socket.
private SocketChannel fd;
private Poller.Handle handle;
// If true, connecter is waiting a while before trying to connect.
protected final boolean delayedStart;
// True if a timer has been started.
private boolean timerStarted;
// Reference to the session we belong to.
private final SessionBase session;
// Current reconnect ivl, updated for backoff strategy
private int currentReconnectIvl;
// Socket
private final SocketBase socket;
public TcpConnecter(IOThread ioThread, SessionBase session, final Options options, final Address addr,
boolean delayedStart)
{
super(ioThread, options);
ioObject = new IOObject(ioThread, this);
this.addr = addr;
fd = null;
this.delayedStart = delayedStart;
timerStarted = false;
this.session = session;
currentReconnectIvl = this.options.reconnectIvl;
assert (this.addr != null);
// assert (NetProtocol.tcp.equals(this.addr.protocol())); // not always true, as ipc is emulated by tcp
socket = session.getSocket();
}
@Override
protected void destroy()
{
assert (!timerStarted);
assert (handle == null);
assert (fd == null);
ioObject.unplug();
}
@Override
protected void processPlug()
{
ioObject.plug();
if (delayedStart) {
addReconnectTimer();
}
else {
startConnecting();
}
}
@Override
protected void processTerm(int linger)
{
if (timerStarted) {
ioObject.cancelTimer(RECONNECT_TIMER_ID);
timerStarted = false;
}
if (handle != null) {
ioObject.removeHandle(handle);
handle = null;
}
if (fd != null) {
close();
}
super.processTerm(linger);
}
@Override
public void connectEvent()
{
ioObject.removeHandle(handle);
handle = null;
SocketChannel channel = connect();
if (channel == null) {
// Handle the error condition by attempt to reconnect.
close();
addReconnectTimer();
return;
}
try {
TcpUtils.tuneTcpSocket(channel);
TcpUtils.tuneTcpKeepalives(
channel,
options.tcpKeepAlive,
options.tcpKeepAliveCnt,
options.tcpKeepAliveIdle,
options.tcpKeepAliveIntvl);
}
catch (IOException e) {
throw new ZError.IOException(e);
}
// remember our fd for ZMQ_SRCFD in messages
// socket.setFd(channel);
// Create the engine object for this connection.
StreamEngine engine;
try {
engine = new StreamEngine(channel, options, addr.toString());
}
catch (ZError.InstantiationException e) {
// TODO V4 socket.eventConnectDelayed(addr.toString(), -1);
return;
}
this.fd = null;
// Attach the engine to the corresponding session object.
sendAttach(session, engine);
// Shut the connecter down.
terminate();
socket.eventConnected(addr.toString(), channel);
}
@Override
public void timerEvent(int id)
{
assert (id == RECONNECT_TIMER_ID);
timerStarted = false;
startConnecting();
}
// Internal function to start the actual connection establishment.
private void startConnecting()
{
// Open the connecting socket.
try {
boolean rc = open();
// Connect may succeed in synchronous manner.
if (rc) {
handle = ioObject.addFd(fd);
connectEvent();
}
// Connection establishment may be delayed. Poll for its completion.
else {
handle = ioObject.addFd(fd);
ioObject.setPollConnect(handle);
socket.eventConnectDelayed(addr.toString(), -1);
}
}
catch (RuntimeException | IOException e) {
// Handle any other error condition by eventual reconnect.
if (fd != null) {
close();
}
addReconnectTimer();
}
}
// Internal function to add a reconnect timer
private void addReconnectTimer()
{
int rcIvl = getNewReconnectIvl();
ioObject.addTimer(rcIvl, RECONNECT_TIMER_ID);
// resolve address again to take into account other addresses
// besides the failing one (e.g. multiple dns entries).
try {
addr.resolve(options.ipv6);
}
catch (Exception ignored) {
// This will fail if the network goes away and the
// address cannot be resolved for some reason. Try
// not to fail as the event loop will quit
}
socket.eventConnectRetried(addr.toString(), rcIvl);
timerStarted = true;
}
// Internal function to return a reconnect backoff delay.
// Will modify the currentReconnectIvl used for next call
// Returns the currently used interval
private int getNewReconnectIvl()
{
// The new interval is the current interval + random value.
int interval = currentReconnectIvl + (Utils.randomInt() % options.reconnectIvl);
// Only change the current reconnect interval if the maximum reconnect
// interval was set and if it's larger than the reconnect interval.
if (options.reconnectIvlMax > 0 && options.reconnectIvlMax > options.reconnectIvl) {
// Calculate the next interval
currentReconnectIvl = Math.min(currentReconnectIvl * 2, options.reconnectIvlMax);
}
return interval;
}
// Open TCP connecting socket.
// Returns true if connect was successful immediately.
// Returns false if async connect was launched.
private boolean open() throws IOException
{
assert (fd == null);
// Resolve the address
if (addr == null) {
throw new IOException("Null address");
}
addr.resolve(options.ipv6);
Address.IZAddress resolved = addr.resolved();
if (resolved == null) {
throw new IOException("Address not resolved");
}
SocketAddress sa = resolved.address();
if (sa == null) {
throw new IOException("Socket address not resolved");
}
// Create the socket.
if (options.selectorChooser == null) {
fd = SocketChannel.open();
}
else {
fd = options.selectorChooser.choose(resolved, options).openSocketChannel();
}
// On some systems, IPv4 mapping in IPv6 sockets is disabled by default.
// Switch it on in such cases.
// The method enableIpv4Mapping is empty. Still to be written
if (resolved.family() == StandardProtocolFamily.INET6) {
TcpUtils.enableIpv4Mapping(fd);
}
// Set the socket to non-blocking mode so that we get async connect().
TcpUtils.unblockSocket(fd);
// Set the socket buffer limits for the underlying socket.
if (options.sndbuf != 0) {
TcpUtils.setTcpSendBuffer(fd, options.sndbuf);
}
if (options.rcvbuf != 0) {
TcpUtils.setTcpReceiveBuffer(fd, options.rcvbuf);
}
// Set the IP Type-Of-Service priority for this socket
if (options.tos != 0) {
TcpUtils.setIpTypeOfService(fd, options.tos);
}
// TODO V4 Set a source address for conversations
// Connect to the remote peer.
boolean rc;
try {
rc = fd.connect(sa);
if (rc) {
// Connect was successful immediately.
}
else {
// Translate error codes indicating asynchronous connect has been
// launched to a uniform EINPROGRESS.
errno.set(ZError.EINPROGRESS);
}
}
catch (IllegalArgumentException e) {
// this will happen if sa is bad. Address validation is not documented but
// I've found that IAE is thrown in openjdk as well as on android.
throw new IOException(e.getMessage(), e);
}
return rc;
}
// Get the file descriptor of newly created connection. Returns
// null if the connection was unsuccessful.
private SocketChannel connect()
{
try {
// Async connect has finished. Check whether an error occurred
boolean finished = fd.finishConnect();
assert (finished);
return fd;
}
catch (IOException e) {
return null;
}
}
// Close the connecting socket.
protected void close()
{
assert (fd != null);
try {
fd.close();
socket.eventClosed(addr.toString(), fd);
}
catch (IOException e) {
socket.eventCloseFailed(addr.toString(), ZError.exccode(e));
}
fd = null;
}
@Override
public void inEvent()
{
// connected but attaching to stream engine is not completed. do nothing
}
@Override
public void outEvent()
{
// connected but attaching to stream engine is not completed. do nothing
}
@Override
public String toString()
{
return getClass().getSimpleName() + "[" + options.socketId + "]";
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy