/** * When we receive a duplicate of a request, we stop it here and do not forward it to the upper * layer. If the server has already sent a response, we send it again. If the request has only * been acknowledged (but the ACK has gone lost or not reached the client yet), we resent the ACK. * If the request has neither been responded, acknowledged or rejected yet, the server has not yet * decided what to do with the request and we cannot do anything. */ @Override public void receiveRequest(Exchange exchange, Request request) { if (request.isDuplicate()) { // Request is a duplicate, so resend ACK, RST or response if (exchange.getCurrentResponse() != null) { LOGGER.fine("Respond with the current response to the duplicate request"); // Do not restart retransmission cycle super.sendResponse(exchange, exchange.getCurrentResponse()); } else if (exchange.getCurrentRequest().isAcknowledged()) { LOGGER.fine( "The duplicate request was acknowledged but no response computed yet. Retransmit ACK"); EmptyMessage ack = EmptyMessage.newACK(request); sendEmptyMessage(exchange, ack); } else if (exchange.getCurrentRequest().isRejected()) { LOGGER.fine("The duplicate request was rejected. Reject again"); EmptyMessage rst = EmptyMessage.newRST(request); sendEmptyMessage(exchange, rst); } else { LOGGER.fine( "The server has not yet decided what to do with the request. We ignore the duplicate."); // The server has not yet decided, whether to acknowledge or // reject the request. We know for sure that the server has // received the request though and can drop this duplicate here. } } else { // Request is not a duplicate exchange.setCurrentRequest(request); super.receiveRequest(exchange, request); } }
/** * If we receive an ACK or RST, we mark the outgoing request or response as acknowledged or * rejected respectively and cancel its retransmission. */ @Override public void receiveEmptyMessage(Exchange exchange, EmptyMessage message) { exchange.setFailedTransmissionCount(0); // TODO: If this is an observe relation, the current response might not // be the one that is being acknowledged. The current response might // already be the next NON notification. if (message.getType() == Type.ACK) { if (exchange.getOrigin() == Origin.LOCAL) { exchange.getCurrentRequest().setAcknowledged(true); } else { exchange.getCurrentResponse().setAcknowledged(true); } } else if (message.getType() == Type.RST) { if (exchange.getOrigin() == Origin.LOCAL) { exchange.getCurrentRequest().setRejected(true); } else { exchange.getCurrentResponse().setRejected(true); } } else { LOGGER.warning("Empty messgae was not ACK nor RST: " + message); } LOGGER.finer("Cancel retransmission"); exchange.setRetransmissionHandle(null); super.receiveEmptyMessage(exchange, message); }
/** * Computes the back-off timer and schedules the specified retransmission task. * * @param exchange the exchange * @param task the retransmission task */ protected void prepareRetransmission(Exchange exchange, RetransmissionTask task) { /* * For a new confirmable message, the initial timeout is set to a * random number between ACK_TIMEOUT and (ACK_TIMEOUT * * ACK_RANDOM_FACTOR) */ int timeout; if (exchange.getFailedTransmissionCount() == 0) { timeout = getRandomTimeout(ack_timeout, (int) (ack_timeout * ack_random_factor)); } else { timeout = (int) (ack_timeout_scale * exchange.getCurrentTimeout()); } exchange.setCurrentTimeout(timeout); ScheduledFuture<?> f = executor.schedule(task, timeout, TimeUnit.MILLISECONDS); exchange.setRetransmissionHandle(f); }
@Override public void run() { /* * Do not retransmit a message if it has been acknowledged, * rejected, canceled or already been retransmitted for the maximum * number of times. */ try { int failedCount = exchange.getFailedTransmissionCount() + 1; exchange.setFailedTransmissionCount(failedCount); if (message.isAcknowledged()) { LOGGER.finest( "Timeout: message already acknowledged, cancel retransmission of " + message); return; } else if (message.isRejected()) { LOGGER.finest("Timeout: message already rejected, cancel retransmission of " + message); return; } else if (message.isCanceled()) { LOGGER.finest("Timeout: canceled (MID=" + message.getMID() + "), do not retransmit"); return; } else if (failedCount <= max_retransmit) { LOGGER.finer( "Timeout: retransmit message, failed: " + failedCount + ", message: " + message); // Trigger MessageObservers message.retransmitting(); // MessageObserver might have canceled if (!message.isCanceled()) retransmit(); } else { LOGGER.info( "Timeout: retransmission limit reached, exchange failed, message: " + message); exchange.setTimedOut(); message.setTimedOut(true); } } catch (Exception e) { e.printStackTrace(); } }
@Override public void forwardRequest(Exchange exchange) { LOGGER.fine( "(DirectProxyCoapResolver 把请求交给资源 ForwardResource) Forward CoAP request to ProxyCoap2Coap: \n\t" + exchange.getRequest()); /* * 这里的 exchange 没有包含响应结果吧? */ proxyCoapClientResource.handleRequest(exchange); }
/** * When we receive a Confirmable response, we acknowledge it and it also counts as acknowledgment * for the request. If the response is a duplicate, we stop it here and do not forward it to the * upper layer. */ @Override public void receiveResponse(Exchange exchange, Response response) { exchange.setFailedTransmissionCount(0); exchange.getCurrentRequest().setAcknowledged(true); LOGGER.finest("Cancel any retransmission"); exchange.setRetransmissionHandle(null); if (response.getType() == Type.CON && !exchange.getRequest().isCanceled()) { LOGGER.finer("Response is confirmable, send ACK"); EmptyMessage ack = EmptyMessage.newACK(response); sendEmptyMessage(exchange, ack); } if (response.isDuplicate()) { LOGGER.fine("Response is duplicate, ignore it"); } else { super.receiveResponse(exchange, response); } }
/** * Handles any request in the given exchange. By default it responds with a 4.05 (Method Not * Allowed). Override this method if your resource handler requires advanced access to the * internal Exchange class. Most developer should be better off with overriding the called methods * {@link #handleGET(CoapExchange)}, {@link #handlePOST(CoapExchange)}, {@link * #handlePUT(CoapExchange)}, and {@link #handleDELETE(CoapExchange)}, which provide a better API * through the {@link CoapExchange} class. * * @param exchange the exchange with the request */ @Override public void handleRequest(final Exchange exchange) { Code code = exchange.getRequest().getCode(); switch (code) { case GET: handleGET(new CoapExchange(exchange, this)); break; case POST: handlePOST(new CoapExchange(exchange, this)); break; case PUT: handlePUT(new CoapExchange(exchange, this)); break; case DELETE: handleDELETE(new CoapExchange(exchange, this)); break; } }
/** Schedules a retransmission for confirmable messages. */ @Override public void sendRequest(final Exchange exchange, final Request request) { LOGGER.finer("Send request, failed transmissions: " + exchange.getFailedTransmissionCount()); if (request.getType() == null) request.setType(Type.CON); if (request.getType() == Type.CON) { prepareRetransmission( exchange, new RetransmissionTask(exchange, request) { public void retransmit() { sendRequest(exchange, request); } }); } super.sendRequest(exchange, request); }
/** * Makes sure that the response type is correct. The response type for a NON can be NON or CON. * The response type for a CON should either be an ACK with a piggy-backed response or, if an * empty ACK has already be sent, a CON or NON with a separate response. */ @Override public void sendResponse(final Exchange exchange, final Response response) { LOGGER.finer("Send response, failed transmissions: " + exchange.getFailedTransmissionCount()); // If a response type is set, we do not mess around with it. // Only if none is set, we have to decide for one here. Type respType = response.getType(); if (respType == null) { Type reqType = exchange.getCurrentRequest().getType(); if (reqType == Type.CON) { if (exchange.getCurrentRequest().isAcknowledged()) { // send separate response response.setType(Type.CON); } else { exchange.getCurrentRequest().setAcknowledged(true); // send piggy-backed response response.setType(Type.ACK); response.setMID(exchange.getCurrentRequest().getMID()); } } else { // send NON response response.setType(Type.NON); } LOGGER.finest( "Switched response message type from " + respType + " to " + response.getType() + " (request was " + reqType + ")"); } else if (respType == Type.ACK || respType == Type.RST) { response.setMID(exchange.getCurrentRequest().getMID()); } if (response.getType() == Type.CON) { LOGGER.finer("Scheduling retransmission for " + response); prepareRetransmission( exchange, new RetransmissionTask(exchange, response) { public void retransmit() { sendResponse(exchange, response); } }); } super.sendResponse(exchange, response); }
/** * This method is used to apply resource-specific knowledge on the exchange. If the request was * successful, it sets the Observe option for the response. It is important to use the * notificationOrderer of the resource here. Further down the layer, race conditions could cause * local reordering of notifications. If the response has an error code, no observe relation can * be established and if there was one previously it is canceled. When this resource allows to be * observed by clients and the request is a GET request with an observe option, the {@link * ServerMessageDeliverer} already created the relation, as it manages the observing endpoints * globally. * * @param exchange the exchange * @param response the response */ public void checkObserveRelation(Exchange exchange, Response response) { /* * If the request for the specified exchange tries to establish an observer * relation, then the ServerMessageDeliverer must have created such a relation * and added to the exchange. Otherwise, there is no such relation. * Remember that different paths might lead to this resource. */ ObserveRelation relation = exchange.getRelation(); if (relation == null) return; // because request did not try to establish a relation if (CoAP.ResponseCode.isSuccess(response.getCode())) { response.getOptions().setObserve(notificationOrderer.getCurrent()); if (!relation.isEstablished()) { relation.setEstablished(true); addObserveRelation(relation); } else if (observeType != null) { // The resource can control the message type of the notification response.setType(observeType); } } // ObserveLayer takes care of the else case }