-
Notifications
You must be signed in to change notification settings - Fork 0
/
Server.cs
229 lines (182 loc) · 8.35 KB
/
Server.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
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace SocketHelper;
// Socket info: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket?view=net-8.0
// modified from: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socketasynceventargs?view=net-8.0
// Implements the connection logic for a socket server
// After accepting a connection, the server posts a ReceiveAsync to the connection
class SocketServer {
SocketSettings settings;
// represents a large reusable set of buffers for all socket operations
BufferManager m_bufferManager;
// read, write (don't alloc buffer space for accepts)
const int opsToPreAlloc = 2;
// the socket used to listen for incoming connection requests
Socket listenSocket;
// pool of reusable SocketAsyncEventArgs objects for write, read and accept socket operations
protected SocketAsyncEventArgsPool m_readWritePool;
// the total number of clients connected to the server
protected int m_numConnectedSockets;
Semaphore m_maxNumberAcceptedClients;
// Create a server instance, initialize and start it
public SocketServer(SocketSettings theSettings) {
int numConnections = theSettings.MaxConnections;
int receiveBufferSize = theSettings.BufSize;
settings = theSettings;
m_numConnectedSockets = 0;
// allocate buffers such that the maximum number of sockets can have one outstanding read and
//write posted to the socket simultaneously
m_bufferManager = new BufferManager(receiveBufferSize * numConnections * opsToPreAlloc, receiveBufferSize);
m_readWritePool = new SocketAsyncEventArgsPool(numConnections);
m_maxNumberAcceptedClients = new Semaphore(numConnections, numConnections);
Init();
Start(settings.EndPoint);
}
// Initializes the server by preallocating reusable buffers and
// context objects. These objects do not need to be preallocated
// or reused, but it is done this way to illustrate how the API can
// easily be used to create reusable objects to increase server performance.
public void Init() {
// Allocates one large byte buffer which all I/O operations use a piece of.
// This gaurds against memory fragmentation
m_bufferManager.InitBuffer();
// preallocate pool of SocketAsyncEventArgs objects
SocketAsyncEventArgs readWriteEventArg;
for (int i = 0; i < settings.MaxConnections; i++) {
//Pre-allocate a set of reusable SocketAsyncEventArgs
readWriteEventArg = new SocketAsyncEventArgs();
readWriteEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
// assign a byte buffer from the buffer pool to the SocketAsyncEventArg object
m_bufferManager.SetBuffer(readWriteEventArg);
// add SocketAsyncEventArg to the pool
m_readWritePool.Push(readWriteEventArg);
}
}
// Starts the server and begin listening for incoming connection requests.
public void Start(IPEndPoint localEndPoint) {
// create the socket which listens for incoming connections
listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listenSocket.Bind(localEndPoint);
// start the server with a listen backlog of 100 connections
// *** TODO: move listen parameter to server settings ***
listenSocket.Listen(100);
// post accepts on the listening socket
SocketAsyncEventArgs acceptEventArg = new SocketAsyncEventArgs();
acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);
StartAccept(acceptEventArg);
}
// Begins an operation to accept a connection request from the client
public void StartAccept(SocketAsyncEventArgs acceptEventArg) {
// loop while the method completes synchronously
bool willRaiseEvent = false;
while (!willRaiseEvent) {
m_maxNumberAcceptedClients.WaitOne();
// socket must be cleared since the context object is being reused
acceptEventArg.AcceptSocket = null;
willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg);
if (!willRaiseEvent) {
ProcessAccept(acceptEventArg);
}
}
}
// This method is the callback method associated with Socket.AcceptAsync
// operations and is invoked when an accept operation is complete
void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e) {
ProcessAccept(e);
// Accept the next connection request
StartAccept(e);
}
// This method is called whenever a receive or send operation is completed on a socket
void IO_Completed(object sender, SocketAsyncEventArgs e) {
// determine which type of operation just completed and call the associated handler
switch (e.LastOperation) {
case SocketAsyncOperation.Receive:
ProcessReceive(e);
break;
case SocketAsyncOperation.Send:
ProcessSend(e);
break;
default:
throw new ArgumentException("The last operation completed on the socket was not a receive or send");
}
}
protected virtual void ProcessAccept(SocketAsyncEventArgs e) {
Interlocked.Increment(ref m_numConnectedSockets);
Console.WriteLine("Client connection accepted. There are {0} clients connected to the server", m_numConnectedSockets);
// Get the socket for the accepted client connection and put it into the
// ReadEventArg object user token
SocketAsyncEventArgs readEventArgs = m_readWritePool.Pop();
readEventArgs.UserToken = e.AcceptSocket;
// As soon as the client is connected, post a receive to the connection
bool willRaiseEvent = e.AcceptSocket.ReceiveAsync(readEventArgs);
if (!willRaiseEvent) {
ProcessReceive(readEventArgs);
}
}
// This method is invoked when an asynchronous receive operation completes.
// If the remote host closed the connection, then the socket is closed.
// If data was received then the data is processed according to the client type.
protected void ProcessReceive(SocketAsyncEventArgs e) {
// check if the remote host closed the connection
if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success) {
Socket socket = (Socket)e.UserToken;
// process data
ProcessData(e);
// read the next block of data sent from the client
bool willRaiseEvent = socket.ReceiveAsync(e);
if (!willRaiseEvent) {
ProcessReceive(e);
}
} else {
CloseClientSocket(e);
}
}
// process data received from ReceiveAsync
// Server displays received data to the console
// override this method to perform customized processing
protected virtual void ProcessData(SocketAsyncEventArgs e) {
string response;
response = Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred);
Console.WriteLine("");
Console.WriteLine("Received from port {0}: {1}", ((IPEndPoint)((Socket)e.UserToken).RemoteEndPoint).Port, response);
Console.WriteLine("");
}
// This method is invoked when an asynchronous send operation completes.
// If the remote host closed the connection, then the socket is closed,
// otherwise the SocketAsyncEventArgs is returned to the pool for reuse.
private void ProcessSend(SocketAsyncEventArgs e) {
if (e.SocketError == SocketError.Success) {
// respond to send completion
AfterSend(e);
// return SocketAsyncEventArgs to pool for reuse
m_readWritePool.Push(e);
} else {
CloseClientSocket(e);
}
}
// respond to the completion of SendAsync
protected virtual void AfterSend(SocketAsyncEventArgs e) {
// Server does not respond to the completion of a async send
// override this method to perform customized processing
}
protected void CloseClientSocket(SocketAsyncEventArgs e) {
Socket socket = (Socket)e.UserToken;
// decrement the counter keeping track of the total number of clients connected to the server
Interlocked.Decrement(ref m_numConnectedSockets);
ProcessClose(socket);
// close the socket associated with the client
try {
socket.Shutdown(SocketShutdown.Send);
}
// throws if client process has already closed
catch (Exception) { }
socket.Close();
// Free the SocketAsyncEventArg so they can be reused by another client
m_readWritePool.Push(e);
m_maxNumberAcceptedClients.Release();
}
protected virtual void ProcessClose(Socket socket) {
Console.WriteLine("A client disconnected from the server. There are {0} clients connected to the server", m_numConnectedSockets);
}
}