Coverage Summary for Class: KeepAliveSocketWrapper (it.polimi.ingsw.Network)
Class |
Class, %
|
Method, %
|
Branch, %
|
Line, %
|
KeepAliveSocketWrapper |
0%
(0/1)
|
0%
(0/3)
|
0%
(0/10)
|
0%
(0/32)
|
1 package it.polimi.ingsw.Network;
2
3 import it.polimi.ingsw.Logger;
4 import it.polimi.ingsw.Server.Messages.HeartBeatMessage;
5 import it.polimi.ingsw.Server.Messages.Message;
6
7 import java.io.IOException;
8 import java.net.Socket;
9 import java.net.SocketException;
10 import java.util.Timer;
11 import java.util.concurrent.ArrayBlockingQueue;
12 import java.util.concurrent.BlockingQueue;
13 import java.util.concurrent.TimeUnit;
14
15 /**
16 * A {@link SocketWrapper} that also takes care of sending or responding to {@link HeartBeatMessage}, keeping the connection
17 * active so long as the endpoint proves to be active
18 */
19 public class KeepAliveSocketWrapper extends SocketWrapper {
20 private final BlockingQueue<Message> inputQueue;
21 private final HeartBeatSender heartBeatSender;
22 private final long keepAlivePeriod;
23 private Timer heartBeatTimer;
24
25 /**
26 * Wraps the socket around a keep alive wrapper. The socket can be active or passive, the former means the socket
27 * actively sends a {@link HeartBeatMessage} at constant intervals, the latter means the socket sends a {@link HeartBeatMessage}
28 * for every {@link HeartBeatMessage} received. <br>
29 * The sockets will automatically close after a timeout, the timeout is 3 times the fixed duration used by an active
30 * wrapper to send its heartbeats. <br>
31 * Note: server used wrappers are encouraged to use the passive wrapper while clients are encouraged to use the active version
32 * instead. <br>
33 * Note: Assuming both ends of the connection use this wrapper keeping at least one active wrapper either as the client or
34 * server will keep the connection alive until an error occurs without the need to manually send the
35 * heartbeat. The same can be said if both ends of the connection are
36 * active wrappers. The same cannot be said if both ends are passive.
37 *
38 * @param socket the socket to wrap around
39 * @param keepAlivePeriod the time (in milliseconds) it takes for an active wrapper to send a heartbeat and a third of the
40 * timeout required to close the connection and deem the endpoint unreachable
41 * @param activeHeartBeat if set to true, makes the wrapper send a {@link HeartBeatMessage} every period, if set to false makes
42 * the {@link HeartBeatMessage} be sent only in response to a {@link HeartBeatMessage} in input.
43 * @throws IOException if any issues occur while wrapping the socket.
44 */
45 public KeepAliveSocketWrapper(Socket socket, long keepAlivePeriod, boolean activeHeartBeat) throws IOException {
46 super(socket);
47 this.inputQueue = new ArrayBlockingQueue<>(10);
48 this.heartBeatSender = new HeartBeatSender(this);
49 this.heartBeatTimer = new Timer();
50 this.keepAlivePeriod = keepAlivePeriod;
51 new Thread(() -> {
52 while (true) {
53 try {
54 Message input = super.awaitMessage();
55 if (input instanceof HeartBeatMessage) {
56 this.heartBeatTimer.cancel();
57 if (!activeHeartBeat) {
58 this.sendMessage(new HeartBeatMessage());
59 }
60 this.heartBeatTimer = HeartBeatTimeoutTask.startAndGetTimer(this, keepAlivePeriod * 3);
61 } else {
62 inputQueue.put(input);
63 }
64 } catch (IOException | InterruptedException e) {
65 try {
66 this.close();
67 } catch (IOException ex) {
68 throw new RuntimeException(ex);
69 }
70 this.heartBeatSender.cancel();
71 Logger.info("closed KeepAliveSocketWrapper");
72 return;
73 }
74 }
75 }).start();
76 if (activeHeartBeat) {
77 this.heartBeatSender.start(keepAlivePeriod);
78 }
79 }
80
81 /**
82 * Blocks until a message is read or the heartbeat timeout expires
83 *
84 * @return the read message
85 * @throws IOException if the timeout runs out or if an error is encountered while fetching a message
86 */
87 @Override
88 public Message awaitMessage() throws IOException {
89 try {
90 while (true) {
91 Message polledMessage = inputQueue.poll(keepAlivePeriod, TimeUnit.MILLISECONDS);
92 if (polledMessage != null) {
93 return polledMessage;
94 } else if (this.isClosed()) {
95 throw new SocketException("SocketWrapper is closed");
96 }
97 }
98 } catch (InterruptedException e) {
99 throw new IOException(e);
100 }
101 }
102 }