-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathASocket.java
More file actions
165 lines (143 loc) · 5.94 KB
/
ASocket.java
File metadata and controls
165 lines (143 loc) · 5.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package readiefur.sockets;
import java.io.EOFException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.SocketException;
import readiefur.misc.Event;
import readiefur.misc.IDisposable;
//Most of the abstract class contains more virtual members, though it is still abstract and should therefore not be instantiated on it's own.
public abstract class ASocket extends Thread implements IDisposable
{
protected final Object lock = new Object();
protected Boolean isDisposed = false;
protected Socket socket;
protected Boolean threadHasRun = false;
//The two following object streams are initialized when required, this is because they would hang until the first message was received.
//I also store them globally as opposed to creating a new one each time, as I read that only one object stream should be instantiated per stream, see: https://stackoverflow.com/questions/2393179/streamcorruptedexception-invalid-type-code-ac
protected ObjectInputStream inputStream = null;
protected ObjectOutputStream outputStream = null;
public final Event<Void> onConnect = new Event<>();
public final Event<Object> onMessage = new Event<>();
public final Event<Void> onClose = new Event<>();
public final Event<Exception> onError = new Event<>();
protected ASocket(Socket socket)
{
this.socket = socket;
}
@Override
public void Dispose()
{
//Prevent race conditions.
synchronized (lock)
{
if (isDisposed)
return;
isDisposed = true;
//Close the socket.
if (socket != null)
{
try { socket.close(); }
catch (Exception ex) { onError.Invoke(ex); }
socket = null;
//If the socket was open, we can fire the onClose event.
if (threadHasRun)
onClose.Invoke(null);
}
//While GC will close the streams when this object goes out of scope, closing them manually is always more efficient.
if (inputStream != null)
{
try { inputStream.close(); }
catch (SocketException ex) { /*Ignore, this is expected when the socket is closed from the other end.*/ }
catch (Exception ex) { onError.Invoke(ex); }
inputStream = null;
}
if (outputStream != null)
{
try { outputStream.close(); }
catch (SocketException ex) { /*See above.*/ }
catch (Exception ex) { onError.Invoke(ex); }
outputStream = null;
}
//If the thread is still running, interrupt it.
if (this.isAlive())
{
try { this.interrupt(); }
catch (Exception ex) { onError.Invoke(ex); }
}
}
}
public Boolean IsConnected()
{
return !isDisposed && socket != null && socket.isConnected();
}
@Override
public void run()
{
if (isDisposed || threadHasRun)
return;
threadHasRun = true;
//We should never reach this state with a null socket, if we do then something has gone wrong.
if (socket == null)
throw new IllegalStateException("Socket is null.");
onConnect.Invoke(null);
//While the socket is open, read messages from the input stream.
while (!isDisposed && !socket.isClosed())
{
try
{
if (inputStream == null)
{
//This will hang until a message is received.
inputStream = new ObjectInputStream(socket.getInputStream());
}
Object message = inputStream.readObject();
onMessage.Invoke(message);
}
catch (SocketException | EOFException | NullPointerException ex)
{
//The above exceptions are expected and will be ignored, they can occur for the following reasons:
//SocketException: Occurs when the client disconnects.
//EOFException: Occurs when the SERVER disconnects (as opposed to the client closing the connection).
//NullPointerException: Occurs when the client disconnects.
break;
}
catch (Exception ex)
{
//Any other exception is unexpected and should be handled.
onError.Invoke(ex);
}
}
/*In my previous tests I had this close the connection and thread as opposed to disposing of the this instance.
*The reason I don't do this anymore is because after a quick look, I found you cannot reuse threads in java.
*I could work around this by creating another wrapper instance but that can be done later if needs be.
*I will be leaving the code for closure in the deconstructor though for good practice and if I need to reimplement it again later.*/
//TL;DR: When a connection ends, we DO want to dispose of it as a socket cannot be reused.
Dispose();
}
public Socket GetSocket()
{
return socket;
}
public void SendMessage(Object message)
{
if (isDisposed || socket == null || socket.isClosed())
return;
try
{
if (outputStream == null)
outputStream = new ObjectOutputStream(socket.getOutputStream());
outputStream.writeObject(message);
}
catch (SocketException | NullPointerException ex)
{
//The above exceptions are expected and will be ignored, they can occur for the following reasons:
//SocketException: Occurs when the client disconnects.
//NullPointerException: Occurs when the client disconnects.
}
catch (Exception ex)
{
onError.Invoke(ex);
}
}
}