-
Notifications
You must be signed in to change notification settings - Fork 1
/
TcpLib.cs
330 lines (294 loc) · 10.1 KB
/
TcpLib.cs
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
using System.Collections;
namespace TcpLib
{
/// <SUMMARY>
/// This class holds useful information for keeping track of each client connected
/// to the server, and provides the means for sending/receiving data to the remote
/// host.
/// </SUMMARY>
public class ConnectionState
{
internal Socket _conn;
internal TcpServer _server;
internal TcpServiceProvider _provider;
internal byte[] _buffer;
/// <SUMMARY>
/// Tells you the IP Address of the remote host.
/// </SUMMARY>
public EndPoint RemoteEndPoint
{
get { return _conn.RemoteEndPoint; }
}
/// <SUMMARY>
/// Returns the number of bytes waiting to be read.
/// </SUMMARY>
public int AvailableData
{
get { return _conn.Available; }
}
/// <SUMMARY>
/// Tells you if the socket is connected.
/// </SUMMARY>
public bool Connected
{
get { return _conn.Connected; }
}
/// <SUMMARY>
/// Reads data on the socket, returns the number of bytes read.
/// </SUMMARY>
public int Read(byte[] buffer, int offset, int count)
{
try
{
if (_conn.Available > 0)
return _conn.Receive(buffer, offset, count, SocketFlags.None);
else return 0;
}
catch
{
return 0;
}
}
/// <SUMMARY>
/// Sends Data to the remote host.
/// </SUMMARY>
public bool Write(byte[] buffer, int offset, int count)
{
try
{
_conn.Send(buffer, offset, count, SocketFlags.None);
return true;
}
catch (Exception ex)
{
//Console.WriteLine(ex.ToString());
return false;
}
}
/// <SUMMARY>
/// Ends connection with the remote host.
/// </SUMMARY>
public void EndConnection()
{
if (_conn != null && _conn.Connected)
{
_conn.Shutdown(SocketShutdown.Both);
_conn.Close();
}
_server.DropConnection(this);
}
}
/// <SUMMARY>
/// Allows to provide the server with the actual code that is goint to service
/// incoming connections.
/// </SUMMARY>
public abstract class TcpServiceProvider : ICloneable
{
/// <SUMMARY>
/// Provides a new instance of the object.
/// </SUMMARY>
public virtual object Clone()
{
throw new Exception("Derived clases must override Clone method.");
}
/// <SUMMARY>
/// Gets executed when the server accepts a new connection.
/// </SUMMARY>
public abstract void OnAcceptConnection(ConnectionState state);
/// <SUMMARY>
/// Gets executed when the server detects incoming data.
/// This method is called only if OnAcceptConnection has already finished.
/// </SUMMARY>
public abstract void OnReceiveData(ConnectionState state);
/// <SUMMARY>
/// Gets executed when the server needs to shutdown the connection.
/// </SUMMARY>
public abstract void OnDropConnection(ConnectionState state);
}
public class TcpServer
{
private System.Windows.Forms.Form _window;
private GeolocationTCP.Locator _locator;
private int _port;
private Socket _listener;
private TcpServiceProvider _provider;
private ArrayList _connections;
private int _maxConnections = 100;
private AsyncCallback ConnectionReady;
private WaitCallback AcceptConnection;
private AsyncCallback ReceivedDataReady;
/// <SUMMARY>
/// Initializes server. To start accepting connections call Start method.
/// </SUMMARY>
public TcpServer(TcpServiceProvider provider, int port)
{
_provider = provider;
_port = port;
_listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream,
ProtocolType.Tcp);
_connections = new ArrayList();
ConnectionReady = new AsyncCallback(ConnectionReady_Handler);
AcceptConnection = new WaitCallback(AcceptConnection_Handler);
ReceivedDataReady = new AsyncCallback(ReceivedDataReady_Handler);
}
public void SetWindow(System.Windows.Forms.Form w)
{
_window = w;
}
public void SetLocator(GeolocationTCP.Locator loc)
{
_locator = loc;
}
/// <SUMMARY>
/// Start accepting connections.
/// A false return value tell you that the port is not available.
/// </SUMMARY>
public bool Start()
{
try
{
_listener.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), _port));
_listener.Listen(100);
_listener.BeginAccept(ConnectionReady, null);
return true;
}
catch
{
return false;
}
}
/// <SUMMARY>
/// Callback function: A new connection is waiting.
/// </SUMMARY>
private void ConnectionReady_Handler(IAsyncResult ar)
{
lock (this)
{
if (_listener == null) return;
Socket conn = _listener.EndAccept(ar);
if (_connections.Count >= _maxConnections)
{
//Max number of connections reached.
string msg = "SE001: Server busy";
conn.Send(Encoding.UTF8.GetBytes(msg), 0, msg.Length, SocketFlags.None);
conn.Shutdown(SocketShutdown.Both);
conn.Close();
}
else
{
//Start servicing a new connection
ConnectionState st = new ConnectionState();
st._conn = conn;
st._server = this;
st._provider = (TcpServiceProvider)_provider.Clone();
_locator.SetProvider(st._provider);
st._buffer = new byte[4];
_connections.Add(st);
//Queue the rest of the job to be executed latter
ThreadPool.QueueUserWorkItem(AcceptConnection, st);
}
//Resume the listening callback loop
_listener.BeginAccept(ConnectionReady, null);
}
}
/// <SUMMARY>
/// Executes OnAcceptConnection method from the service provider.
/// </SUMMARY>
private void AcceptConnection_Handler(object state)
{
ConnectionState st = state as ConnectionState;
try { st._provider.OnAcceptConnection(st); }
catch
{
//report error in provider... Probably to the EventLog
}
//Starts the ReceiveData callback loop
if (st._conn.Connected)
st._conn.BeginReceive(st._buffer, 0, 0, SocketFlags.None,
ReceivedDataReady, st);
}
/// <SUMMARY>
/// Executes OnReceiveData method from the service provider.
/// </SUMMARY>
private void ReceivedDataReady_Handler(IAsyncResult ar)
{
ConnectionState st = ar.AsyncState as ConnectionState;
st._conn.EndReceive(ar);
//Im considering the following condition as a signal that the
//remote host droped the connection.
if (st._conn.Available == 0) DropConnection(st);
else
{
try { st._provider.OnReceiveData(st); }
catch
{
//report error in the provider
}
//Resume ReceivedData callback loop
if (st._conn.Connected)
st._conn.BeginReceive(st._buffer, 0, 0, SocketFlags.None,
ReceivedDataReady, st);
}
}
/// <SUMMARY>
/// Shutsdown the server
/// </SUMMARY>
public void Stop()
{
lock (this)
{
_listener.Close();
_listener = null;
//Close all active connections
foreach (object obj in _connections)
{
ConnectionState st = obj as ConnectionState;
try { st._provider.OnDropConnection(st); }
catch
{
//some error in the provider
}
st._conn.Shutdown(SocketShutdown.Both);
st._conn.Close();
}
_connections.Clear();
}
}
/// <SUMMARY>
/// Removes a connection from the list
/// </SUMMARY>
internal void DropConnection(ConnectionState st)
{
lock (this)
{
st._conn.Shutdown(SocketShutdown.Both);
st._conn.Close();
if (_connections.Contains(st))
_connections.Remove(st);
}
}
public int MaxConnections
{
get
{
return _maxConnections;
}
set
{
_maxConnections = value;
}
}
public int CurrentConnections
{
get
{
lock (this) { return _connections.Count; }
}
}
}
}