C# SocketAsyncEventArgs 高性能Socket代码

class Program
{
//This variable determines the number of
//SocketAsyncEventArg objects put in the pool of objects for receive/send.
//The value of this variable also affects the Semaphore.
//This app uses a Semaphore to ensure that the max # of connections
//value does not get exceeded.
//Max # of connections to a socket can be limited by the Windows Operating System
//also.
public const Int32 maxNumberOfConnections = 3000;

//If this port # will not work for you, it's okay to change it.
public const Int32 port = 4444;

//You would want a buffer size larger than 25 probably, unless you know the
//data will almost always be less than 25. It is just 25 in our test app.
public const Int32 testBufferSize = 25;

//This is the maximum number of asynchronous accept operations that can be
//posted simultaneously. This determines the size of the pool of
//SocketAsyncEventArgs objects that do accept operations. Note that this
//is NOT the same as the maximum # of connections.
public const Int32 maxSimultaneousAcceptOps = 10;

//The size of the queue of incoming connections for the listen socket.
public const Int32 backlog = 100;

//For the BufferManager
public const Int32 opsToPreAlloc = 2; // 1 for receive, 1 for send

//allows excess SAEA objects in pool.
public const Int32 excessSaeaObjectsInPool = 1;

//This number must be the same as the value on the client.
//Tells what size the message prefix will be. Don't change this unless
//you change the code, because 4 is the length of 32 bit integer, which
//is what we are using as prefix.
public const Int32 receivePrefixLength = 4;
public const Int32 sendPrefixLength = 4;

public static Int32 mainTransMissionId = 10000;
public static Int32 startingTid; //
public static Int32 mainSessionId = 1000000000;

public static List listOfDataHolders;

// To keep a record of maximum number of simultaneous connections
// that occur while the server is running. This can be limited by operating
// system and hardware. It will not be higher than the value that you set
// for maxNumberOfConnections.
public static Int32 maxSimultaneousClientsThatWereConnected = 0;

static void Main(String[] args)
{
try
{
// Get endpoint for the listener.
IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, port);

WriteInfoToConsole(localEndPoint);

//This object holds a lot of settings that we pass from Main method
//to the SocketListener. In a real app, you might want to read
//these settings from a database or windows registry settings that
//you would create.
SocketListenerSettings theSocketListenerSettings =
new SocketListenerSettings(maxNumberOfConnections,
excessSaeaObjectsInPool, backlog, maxSimultaneousAcceptOps,
receivePrefixLength, testBufferSize, sendPrefixLength, opsToPreAlloc,
localEndPoint);

//instantiate the SocketListener.
SocketListener socketListener = new SocketListener(theSocketListenerSettings);
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
}
}
class SocketListener
{
//Buffers for sockets are unmanaged by .NET.
//So memory used for buffers gets "pinned", which makes the
//.NET garbage collector work around it, fragmenting the memory.
//Circumvent this problem by putting all buffers together
//in one block in memory. Then we will assign a part of that space
//to each SocketAsyncEventArgs object, and
//reuse that buffer space each time we reuse the SocketAsyncEventArgs object.
//Create a large reusable set of buffers for all socket operations.
BufferManager theBufferManager;

// the socket used to listen for incoming connection requests
Socket listenSocket;

//A Semaphore has two parameters, the initial number of available slots
// and the maximum number of slots. We'll make them the same.
//This Semaphore is used to keep from going over max connection #.
//(It is not about controlling threading really here.)
Semaphore theMaxConnectionsEnforcer;

//an object that we pass in and which has all the settings the listener needs
SocketListenerSettings socketListenerSettings;

PrefixHandler prefixHandler;
MessageHandler messageHandler;

// pool of reusable SocketAsyncEventArgs objects for accept operations
SocketAsyncEventArgsPool poolOfAcceptEventArgs;
// pool of reusable SocketAsyncEventArgs objects for
//receive and send socket operations
SocketAsyncEventArgsPool poolOfRecSendEventArgs;

//_______________________________________________________________________________
// Constructor.
public SocketListener(SocketListenerSettings theSocketListenerSettings)
{
this.socketListenerSettings = theSocketListenerSettings;
this.prefixHandler = new PrefixHandler();
this.messageHandler = new MessageHandler();

//Allocate memory for buffers. We are using a separate buffer space for
//receive and send, instead of sharing the buffer space, like the Microsoft
//example does.
this.theBufferManager = new BufferManager(this.socketListenerSettings.BufferSize
* this.socketListenerSettings.NumberOfSaeaForRecSend
* this.socketListenerSettings.OpsToPreAllocate,
this.socketListenerSettings.BufferSize
* this.socketListenerSettings.OpsToPreAllocate);

this.poolOfRecSendEventArgs = new
SocketAsyncEventArgsPool(this.socketListenerSettings.NumberOfSaeaForRecSend);

this.poolOfAcceptEventArgs = new
SocketAsyncEventArgsPool(this.socketListenerSettings.MaxAcceptOps);

// Create connections count enforcer
this.theMaxConnectionsEnforcer = new
Semaphore(this.socketListenerSettings.MaxConnections,
this.socketListenerSettings.MaxConnections);

//Microsoft's example called these from Main method, which you
//can easily do if you wish.
Init();
StartListen();
}

//____________________________________________________________________________
// initializes the server by preallocating reusable buffers and
// context objects (SocketAsyncEventArgs objects).
//It is NOT mandatory that you preallocate them or reuse them. But, but it is
//done this way to illustrate how the API can
// easily be used to create reusable objects to increase server performance.

internal void Init()
{
// Allocate one large byte buffer block, which all I/O operations will
//use a piece of. This guards against memory fragmentation.
this.theBufferManager.InitBuffer();

// preallocate pool of SocketAsyncEventArgs objects for accept operations
for (Int32 i = 0; i < this.socketListenerSettings.MaxAcceptOps; i++)
{
// add SocketAsyncEventArg to the pool
this.poolOfAcceptEventArgs.Push(
CreateNewSaeaForAccept(poolOfAcceptEventArgs));
}

//The pool that we built ABOVE is for SocketAsyncEventArgs objects that do
// accept operations.
//Now we will build a separate pool for SAEAs objects
//that do receive/send operations. One reason to separate them is that accept
//operations do NOT need a buffer, but receive/send operations do.
//ReceiveAsync and SendAsync require
//a parameter for buffer size in SocketAsyncEventArgs.Buffer.
// So, create pool of SAEA objects for receive/send operations.
SocketAsyncEventArgs eventArgObjectForPool;

Int32 tokenId;

for (Int32 i = 0; i < this.socketListenerSettings.NumberOfSaeaForRecSend; i++)
{
//Allocate the SocketAsyncEventArgs object for this loop,
//to go in its place in the stack which will be the pool
//for receive/send operation context objects.
eventArgObjectForPool = new SocketAsyncEventArgs();

// assign a byte buffer from the buffer block to
//this particular SocketAsyncEventArg object
this.theBufferManager.SetBuffer(eventArgObjectForPool);

tokenId = poolOfRecSendEventArgs.AssignTokenId() + 1000000;

//Attach the SocketAsyncEventArgs object
//to its event handler. Since this SocketAsyncEventArgs object is
//used for both receive and send operations, whenever either of those
//completes, the IO_Completed method will be called.
eventArgObjectForPool.Completed += new
EventHandler(IO_Completed);

//We can store data in the UserToken property of SAEA object.
DataHoldingUserToken theTempReceiveSendUserToken = new
DataHoldingUserToken(eventArgObjectForPool, eventArgObjectForPool.Offset,
eventArgObjectForPool.Offset + this.socketListenerSettings.BufferSize,
this.socketListenerSettings.ReceivePrefixLength,
this.socketListenerSettings.SendPrefixLength, tokenId);

//We'll have an object that we call DataHolder, that we can remove from
//the UserToken when we are finished with it. So, we can hang on to the
//DataHolder, pass it to an app, serialize it, or whatever.
theTempReceiveSendUserToken.CreateNewDataHolder();

eventArgObjectForPool.UserToken = theTempReceiveSendUserToken;

// add this SocketAsyncEventArg object to the pool.
this.poolOfRecSendEventArgs.Push(eventArgObjectForPool);
}

//____________________________________________________________________________
// This method is called when we need to create a new SAEA object to do
//accept operations. The reason to put it in a separate method is so that
//we can easily add more objects to the pool if we need to.
//You can do that if you do NOT use a buffer in the SAEA object that does
//the accept operations.
internal SocketAsyncEventArgs CreateNewSaeaForAccept(SocketAsyncEventArgsPool pool)
{
//Allocate the SocketAsyncEventArgs object.
SocketAsyncEventArgs acceptEventArg = new SocketAsyncEventArgs();

//SocketAsyncEventArgs.Completed is an event, (the only event,)
//declared in the SocketAsyncEventArgs class.
//See http://msdn.microsoft.com/en-us/library/
// system.net.sockets.socketasynceventargs.completed.aspx.
//An event handler should be attached to the event within
//a SocketAsyncEventArgs instance when an asynchronous socket
//operation is initiated, otherwise the application will not be able
//to determine when the operation completes.
//Attach the event handler, which causes the calling of the
//AcceptEventArg_Completed object when the accept op completes.
acceptEventArg.Completed +=
new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);

AcceptOpUserToken theAcceptOpToken = new
AcceptOpUserToken(pool.AssignTokenId() + 10000);

acceptEventArg.UserToken = theAcceptOpToken;

return acceptEventArg;

// accept operations do NOT need a buffer.
//You can see that is true by looking at the
//methods in the .NET Socket class on the Microsoft website. AcceptAsync does
//not require a parameter for buffer size.
}

//____________________________________________________________________________
// This method starts the socket server such that it is listening for
// incoming connection requests.
internal void StartListen()
{
// create the socket which listens for incoming connections
listenSocket = new
Socket(this.socketListenerSettings.LocalEndPoint.AddressFamily,
SocketType.Stream, ProtocolType.Tcp);

//bind it to the port
listenSocket.Bind(this.socketListenerSettings.LocalEndPoint);

// Start the listener with a backlog of however many connections.
//"backlog" means pending connections.
//The backlog number is the number of clients that can wait for a
//SocketAsyncEventArg object that will do an accept operation.
//The listening socket keeps the backlog as a queue. The backlog allows
//for a certain # of excess clients waiting to be connected.
//If the backlog is maxed out, then the client will receive an error when
//trying to connect.
//max # for backlog can be limited by the operating system.
listenSocket.Listen(this.socketListenerSettings.Backlog);

//Server is listening now****

// Calls the method which will post accepts on the listening socket.
//This call just occurs one time from this StartListen method.
//After that the StartAccept method will be called in a loop.
StartAccept();
}

//____________________________________________________________________________
// Begins an operation to accept a connection request from the client
internal void StartAccept()
{
//Get a SocketAsyncEventArgs object to accept the connection.
SocketAsyncEventArgs acceptEventArg;
//Get it from the pool if there is more than one in the pool.
//We could use zero as bottom, but one is a little safer.
if (this.poolOfAcceptEventArgs.Count > 1)
{
try
{
acceptEventArg = this.poolOfAcceptEventArgs.Pop();
}
//or make a new one.
catch
{
acceptEventArg = CreateNewSaeaForAccept(poolOfAcceptEventArgs);
}
}
//or make a new one.
else
{
acceptEventArg = CreateNewSaeaForAccept(poolOfAcceptEventArgs);
}

// Semaphore class is used to control access to a resource or pool of
// resources. Enter the semaphore by calling the WaitOne method, which is
// inherited from the WaitHandle class, and release the semaphore
// by calling the Release method. This is a mechanism to prevent exceeding
// the max # of connections we specified. We'll do this before
// doing AcceptAsync. If maxConnections value has been reached,
// then the thread will pause here until the Semaphore gets released,
// which happens in the CloseClientSocket method.
this.theMaxConnectionsEnforcer.WaitOne();

// Socket.AcceptAsync begins asynchronous operation to accept the connection.
// Note the listening socket will pass info to the SocketAsyncEventArgs
// object that has the Socket that does the accept operation.
// If you do not create a Socket object and put it in the SAEA object
// before calling AcceptAsync and use the AcceptSocket property to get it,
// then a new Socket object will be created for you by .NET.
bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg);

// Socket.AcceptAsync returns true if the I/O operation is pending, i.e. is
// working asynchronously. The
// SocketAsyncEventArgs.Completed event on the acceptEventArg parameter
// will be raised upon completion of accept op.
// AcceptAsync will call the AcceptEventArg_Completed
// method when it completes, because when we created this SocketAsyncEventArgs
// object before putting it in the pool, we set the event handler to do it.
// AcceptAsync returns false if the I/O operation completed synchronously.
// The SocketAsyncEventArgs.Completed event on the acceptEventArg parameter
// will NOT be raised when AcceptAsync returns false.
if (!willRaiseEvent)
{
// The code in this if (!willRaiseEvent) statement only runs
// when the operation was completed synchronously. It is needed because
// when Socket.AcceptAsync returns false,
// it does NOT raise the SocketAsyncEventArgs.Completed event.
// And we need to call ProcessAccept and pass it the SAEA object.
// This is only when a new connection is being accepted.
// Probably only relevant in the case of a socket error.
ProcessAccept(acceptEventArg);
}
}

//____________________________________________________________________________
// This method is the callback method associated with Socket.AcceptAsync
// operations and is invoked when an async accept operation completes.
//This is only when a new connection is being accepted.
//Notice that Socket.AcceptAsync is returning a value of true, and
//raising the Completed event when the AcceptAsync method completes.
private void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e)
{
//Any code that you put in this method will NOT be called if
//the operation completes synchronously, which will probably happen when
//there is some kind of socket error. It might be better to put the code
//in the ProcessAccept method.
ProcessAccept(e);
}

//____________________________________________________________________________
//The e parameter passed from the AcceptEventArg_Completed method
//represents the SocketAsyncEventArgs object that did
//the accept operation. in this method we'll do the handoff from it to the
//SocketAsyncEventArgs object that will do receive/send.
private void ProcessAccept(SocketAsyncEventArgs acceptEventArgs)
{
// This is when there was an error with the accept op. That should NOT
// be happening often. It could indicate that there is a problem with
// that socket. If there is a problem, then we would have an infinite
// loop here, if we tried to reuse that same socket.
if (acceptEventArgs.SocketError != SocketError.Success)
{
// Loop back to post another accept op. Notice that we are NOT
// passing the SAEA object here.
LoopToStartAccept();

AcceptOpUserToken theAcceptOpToken =
(AcceptOpUserToken)acceptEventArgs.UserToken;

//Let's destroy this socket, since it could be bad.
HandleBadAccept(acceptEventArgs);

//Jump out of the method.
return;
}

//Now that the accept operation completed, we can start another
//accept operation, which will do the same. Notice that we are NOT
//passing the SAEA object here.
LoopToStartAccept();

// Get a SocketAsyncEventArgs object from the pool of receive/send op
//SocketAsyncEventArgs objects
SocketAsyncEventArgs receiveSendEventArgs = this.poolOfRecSendEventArgs.Pop();

//Create sessionId in UserToken.
((DataHoldingUserToken)receiveSendEventArgs.UserToken).CreateSessionId();

//A new socket was created by the AcceptAsync method. The
//SocketAsyncEventArgs object which did the accept operation has that
//socket info in its AcceptSocket property. Now we will give
//a reference for that socket to the SocketAsyncEventArgs
//object which will do receive/send.
receiveSendEventArgs.AcceptSocket = acceptEventArgs.AcceptSocket;

//We have handed off the connection info from the
//accepting socket to the receiving socket. So, now we can
//put the SocketAsyncEventArgs object that did the accept operation
//back in the pool for them. But first we will clear
//the socket info from that object, so it will be
//ready for a new socket when it comes out of the pool.
acceptEventArgs.AcceptSocket = null;
this.poolOfAcceptEventArgs.Push(acceptEventArgs);

StartReceive(receiveSendEventArgs);
}

//____________________________________________________________________________
//LoopToStartAccept method just sends us back to the beginning of the
//StartAccept method, to start the next accept operation on the next
//connection request that this listening socket will pass of to an
//accepting socket. We do NOT actually need this method. You could
//just call StartAccept() in ProcessAccept() where we called LoopToStartAccept().
//This method is just here to help you visualize the program flow.
private void LoopToStartAccept()
{
StartAccept();
}

//____________________________________________________________________________
// Set the receive buffer and post a receive op.
private void StartReceive(SocketAsyncEventArgs receiveSendEventArgs)
{
//Set the buffer for the receive operation.
receiveSendEventArgs.SetBuffer(receiveSendToken.bufferOffsetReceive,
this.socketListenerSettings.BufferSize);

// Post async receive operation on the socket.
bool willRaiseEvent =
receiveSendEventArgs.AcceptSocket.ReceiveAsync(receiveSendEventArgs);

//Socket.ReceiveAsync returns true if the I/O operation is pending. The
//SocketAsyncEventArgs.Completed event on the e parameter will be raised
//upon completion of the operation. So, true will cause the IO_Completed
//method to be called when the receive operation completes.
//That's because of the event handler we created when building
//the pool of SocketAsyncEventArgs objects that perform receive/send.
//It was the line that said
//eventArgObjectForPool.Completed +=
// new EventHandler<SocketAsyncEventArgs>(IO_Completed);

//Socket.ReceiveAsync returns false if I/O operation completed synchronously.
//In that case, the SocketAsyncEventArgs.Completed event on the e parameter
//will not be raised and the e object passed as a parameter may be
//examined immediately after the method call
//returns to retrieve the result of the operation.
// It may be false in the case of a socket error.
if (!willRaiseEvent)
{
//If the op completed synchronously, we need to call ProcessReceive
//method directly. This will probably be used rarely, as you will
//see in testing.
ProcessReceive(receiveSendEventArgs);
}
}

//____________________________________________________________________________
// This method is called whenever a receive or send operation completes.
// Here "e" represents the SocketAsyncEventArgs object associated
//with the completed receive or send operation
void IO_Completed(object sender, SocketAsyncEventArgs e)
{
//Any code that you put in this method will NOT be called if
//the operation completes synchronously, which will probably happen when
//there is some kind of socket error.

// 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:
//This exception will occur if you code the Completed event of some
//operation to come to this method, by mistake.
throw new ArgumentException("The last operation completed on
the socket was not a receive or send");
}
}

//____________________________________________________________________________
// This method is invoked by the IO_Completed method
// when an asynchronous receive operation completes.
// If the remote host closed the connection, then the socket is closed.
// Otherwise, we process the received data. And if a complete message was
// received, then we do some additional processing, to
// respond to the client.
private void ProcessReceive(SocketAsyncEventArgs receiveSendEventArgs)
{
DataHoldingUserToken receiveSendToken =
(DataHoldingUserToken)receiveSendEventArgs.UserToken;

// If there was a socket error, close the connection. This is NOT a normal
// situation, if you get an error here.
// In the Microsoft example code they had this error situation handled
// at the end of ProcessReceive. Putting it here improves readability
// by reducing nesting some.
if (receiveSendEventArgs.SocketError != SocketError.Success)
{
receiveSendToken.Reset();
CloseClientSocket(receiveSendEventArgs);

//Jump out of the ProcessReceive method.
return;
}

// If no data was received, close the connection. This is a NORMAL
// situation that shows when the client has finished sending data.
if (receiveSendEventArgs.BytesTransferred == 0)
{
receiveSendToken.Reset();
CloseClientSocket(receiveSendEventArgs);
return;
}

//The BytesTransferred property tells us how many bytes
//we need to process.
Int32 remainingBytesToProcess = receiveSendEventArgs.BytesTransferred;

//If we have not got all of the prefix already,
//then we need to work on it here.
if (receiveSendToken.receivedPrefixBytesDoneCount <
this.socketListenerSettings.ReceivePrefixLength)
{
remainingBytesToProcess = prefixHandler.HandlePrefix(receiveSendEventArgs,
receiveSendToken, remainingBytesToProcess);

if (remainingBytesToProcess == 0)
{
// We need to do another receive op, since we do not have
// the message yet, but remainingBytesToProcess == 0.
StartReceive(receiveSendEventArgs);
//Jump out of the method.
return;
}
}

// If we have processed the prefix, we can work on the message now.
// We'll arrive here when we have received enough bytes to read
// the first byte after the prefix.
bool incomingTcpMessageIsReady = messageHandler
.HandleMessage(receiveSendEventArgs,
receiveSendToken, remainingBytesToProcess);

if (incomingTcpMessageIsReady == true)
{
// Pass the DataHolder object to the Mediator here. The data in
// this DataHolder can be used for all kinds of things that an
// intelligent and creative person like you might think of.
receiveSendToken.theMediator.HandleData(receiveSendToken.theDataHolder);

// Create a new DataHolder for next message.
receiveSendToken.CreateNewDataHolder();

//Reset the variables in the UserToken, to be ready for the
//next message that will be received on the socket in this
//SAEA object.
receiveSendToken.Reset();

receiveSendToken.theMediator.PrepareOutgoingData();
StartSend(receiveSendToken.theMediator.GiveBack());
}
else
{
// Since we have NOT gotten enough bytes for the whole message,
// we need to do another receive op. Reset some variables first.

// All of the data that we receive in the next receive op will be
// message. None of it will be prefix. So, we need to move the
// receiveSendToken.receiveMessageOffset to the beginning of the
// receive buffer space for this SAEA.
receiveSendToken.receiveMessageOffset = receiveSendToken.bufferOffsetReceive;

// Do NOT reset receiveSendToken.receivedPrefixBytesDoneCount here.
// Just reset recPrefixBytesDoneThisOp.
receiveSendToken.recPrefixBytesDoneThisOp = 0;

// Since we have not gotten enough bytes for the whole message,
// we need to do another receive op.
StartReceive(receiveSendEventArgs);
}
}

//____________________________________________________________________________
//Post a send op.
private void StartSend(SocketAsyncEventArgs receiveSendEventArgs)
{
DataHoldingUserToken receiveSendToken =
(DataHoldingUserToken)receiveSendEventArgs.UserToken;

//Set the buffer. You can see on Microsoft's page at
//http://msdn.microsoft.com/en-us/library/
// system.net.sockets.socketasynceventargs.setbuffer.aspx
//that there are two overloads. One of the overloads has 3 parameters.
//When setting the buffer, you need 3 parameters the first time you set it,
//which we did in the Init method. The first of the three parameters
//tells what byte array to use as the buffer. After we tell what byte array
//to use we do not need to use the overload with 3 parameters any more.
//(That is the whole reason for using the buffer block. You keep the same
//byte array as buffer always, and keep it all in one block.)
//Now we use the overload with two parameters. We tell
// (1) the offset and
// (2) the number of bytes to use, starting at the offset.

//The number of bytes to send depends on whether the message is larger than
//the buffer or not. If it is larger than the buffer, then we will have
//to post more than one send operation. If it is less than or equal to the
//size of the send buffer, then we can accomplish it in one send op.
if (receiveSendToken.sendBytesRemainingCount
<= this.socketListenerSettings.BufferSize)
{
receiveSendEventArgs.SetBuffer(receiveSendToken.bufferOffsetSend,
receiveSendToken.sendBytesRemainingCount);
//Copy the bytes to the buffer associated with this SAEA object.
Buffer.BlockCopy(receiveSendToken.dataToSend,
receiveSendToken.bytesSentAlreadyCount,
receiveSendEventArgs.Buffer, receiveSendToken.bufferOffsetSend,
receiveSendToken.sendBytesRemainingCount);
}
else
{
//We cannot try to set the buffer any larger than its size.
//So since receiveSendToken.sendBytesRemainingCount > BufferSize, we just
//set it to the maximum size, to send the most data possible.
receiveSendEventArgs.SetBuffer(receiveSendToken.bufferOffsetSend,
this.socketListenerSettings.BufferSize);
//Copy the bytes to the buffer associated with this SAEA object.
Buffer.BlockCopy(receiveSendToken.dataToSend,
receiveSendToken.bytesSentAlreadyCount,
receiveSendEventArgs.Buffer, receiveSendToken.bufferOffsetSend,
this.socketListenerSettings.BufferSize);

//We'll change the value of sendUserToken.sendBytesRemainingCount
//in the ProcessSend method.
}

//post asynchronous send operation
bool willRaiseEvent =
receiveSendEventArgs.AcceptSocket.SendAsync(receiveSendEventArgs);

if (!willRaiseEvent)
{
ProcessSend(receiveSendEventArgs);
}
}

//____________________________________________________________________________
// This method is called by I/O Completed() when an asynchronous send completes.
// If all of the data has been sent, then this method calls StartReceive
//to start another receive op on the socket to read any additional
// data sent from the client. If all of the data has NOT been sent, then it
//calls StartSend to send more data.
private void ProcessSend(SocketAsyncEventArgs receiveSendEventArgs)
{
DataHoldingUserToken receiveSendToken =
(DataHoldingUserToken)receiveSendEventArgs.UserToken;

receiveSendToken.sendBytesRemainingCount =
receiveSendToken.sendBytesRemainingCount
- receiveSendEventArgs.BytesTransferred;
receiveSendToken.bytesSentAlreadyCount +=
receiveSendEventArgs.BytesTransferred;

if (receiveSendEventArgs.SocketError == SocketError.Success)
{
if (receiveSendToken.sendBytesRemainingCount == 0)
{
StartReceive(receiveSendEventArgs);
}
else
{
//If some of the bytes in the message have NOT been sent,
//then we will need to post another send operation.
//So let's loop back to StartSend().
StartSend(receiveSendEventArgs);
}
}
else
{
//If we are in this else-statement, there was a socket error.
//In this example we'll just close the socket if there was a socket error
//when receiving data from the client.
receiveSendToken.Reset();
CloseClientSocket(receiveSendEventArgs);
}
}

//_______________________________________________________________________
// Does the normal destroying of sockets after
// we finish receiving and sending on a connection.
private void CloseClientSocket(SocketAsyncEventArgs e)
{
var receiveSendToken = (e.UserToken as DataHoldingUserToken);

// do a shutdown before you close the socket
try
{
e.AcceptSocket.Shutdown(SocketShutdown.Both);
}
// throws if socket was already closed
catch (Exception)
{
}

//This method closes the socket and releases all resources, both
//managed and unmanaged. It internally calls Dispose.
e.AcceptSocket.Close();

//Make sure the new DataHolder has been created for the next connection.
//If it has, then dataMessageReceived should be null.
if (receiveSendToken.theDataHolder.dataMessageReceived != null)
{
receiveSendToken.CreateNewDataHolder();
}

// Put the SocketAsyncEventArg back into the pool,
// to be used by another client. This
this.poolOfRecSendEventArgs.Push(e);

// decrement the counter keeping track of the total number of clients
//connected to the server, for testing
Interlocked.Decrement(ref this.numberOfAcceptedSockets);

//Release Semaphore so that its connection counter will be decremented.
//This must be done AFTER putting the SocketAsyncEventArg back into the pool,
//or you can run into problems.
this.theMaxConnectionsEnforcer.Release();
}

//____________________________________________________________________________
private void HandleBadAccept(SocketAsyncEventArgs acceptEventArgs)
{
var acceptOpToken = (acceptEventArgs.UserToken as AcceptOpUserToken);

//This method closes the socket and releases all resources, both
//managed and unmanaged. It internally calls Dispose.
acceptEventArgs.AcceptSocket.Close();

//Put the SAEA back in the pool.
poolOfAcceptEventArgs.Push(acceptEventArgs);
}
}

class PrefixHandler
{
public Int32 HandlePrefix(SocketAsyncEventArgs e,
DataHoldingUserToken receiveSendToken,
Int32 remainingBytesToProcess)
{
//receivedPrefixBytesDoneCount tells us how many prefix bytes were
//processed during previous receive ops which contained data for
//this message. Usually there will NOT have been any previous
//receive ops here. So in that case,
//receiveSendToken.receivedPrefixBytesDoneCount would equal 0.
//Create a byte array to put the new prefix in, if we have not
//already done it in a previous loop.
if (receiveSendToken.receivedPrefixBytesDoneCount == 0)
{
receiveSendToken.byteArrayForPrefix = new
Byte[receiveSendToken.receivePrefixLength];
}

//If this next if-statement is true, then we have received at
//least enough bytes to have the prefix. So we can determine the
//length of the message that we are working on.
if (remainingBytesToProcess >= receiveSendToken.receivePrefixLength
- receiveSendToken.receivedPrefixBytesDoneCount)
{
//Now copy that many bytes to byteArrayForPrefix.
//We can use the variable receiveMessageOffset as our main
//index to show which index to get data from in the TCP
//buffer.
Buffer.BlockCopy(e.Buffer, receiveSendToken.receiveMessageOffset
- receiveSendToken.receivePrefixLength
+ receiveSendToken.receivedPrefixBytesDoneCount,
receiveSendToken.byteArrayForPrefix,
receiveSendToken.receivedPrefixBytesDoneCount,
receiveSendToken.receivePrefixLength
- receiveSendToken.receivedPrefixBytesDoneCount);

remainingBytesToProcess = remainingBytesToProcess
- receiveSendToken.receivePrefixLength
+ receiveSendToken.receivedPrefixBytesDoneCount;

receiveSendToken.recPrefixBytesDoneThisOp =
receiveSendToken.receivePrefixLength
- receiveSendToken.receivedPrefixBytesDoneCount;

receiveSendToken.receivedPrefixBytesDoneCount =
receiveSendToken.receivePrefixLength;

receiveSendToken.lengthOfCurrentIncomingMessage =
BitConverter.ToInt32(receiveSendToken.byteArrayForPrefix, 0);

return remainingBytesToProcess;
}

//This next else-statement deals with the situation
//where we have some bytes
//of this prefix in this receive operation, but not all.
else
{
//Write the bytes to the array where we are putting the
//prefix data, to save for the next loop.
Buffer.BlockCopy(e.Buffer, receiveSendToken.receiveMessageOffset
- receiveSendToken.receivePrefixLength
+ receiveSendToken.receivedPrefixBytesDoneCount,
receiveSendToken.byteArrayForPrefix,
receiveSendToken.receivedPrefixBytesDoneCount,
remainingBytesToProcess);

receiveSendToken.recPrefixBytesDoneThisOp = remainingBytesToProcess;
receiveSendToken.receivedPrefixBytesDoneCount += remainingBytesToProcess;
remainingBytesToProcess = 0;
}

// This section is needed when we have received
// an amount of data exactly equal to the amount needed for the prefix,
// but no more. And also needed with the situation where we have received
// less than the amount of data needed for prefix.
if (remainingBytesToProcess == 0)
{
receiveSendToken.receiveMessageOffset =
receiveSendToken.receiveMessageOffset -
receiveSendToken.recPrefixBytesDoneThisOp;
receiveSendToken.recPrefixBytesDoneThisOp = 0;
}
return remainingBytesToProcess;
}
}

class MessageHandler
{
public bool HandleMessage(SocketAsyncEventArgs receiveSendEventArgs,
DataHoldingUserToken receiveSendToken,
Int32 remainingBytesToProcess)
{
bool incomingTcpMessageIsReady = false;

//Create the array where we'll store the complete message,
//if it has not been created on a previous receive op.
if (receiveSendToken.receivedMessageBytesDoneCount == 0)
{
receiveSendToken.theDataHolder.dataMessageReceived =
new Byte[receiveSendToken.lengthOfCurrentIncomingMessage];
}

// Remember there is a receiveSendToken.receivedPrefixBytesDoneCount
// variable, which allowed us to handle the prefix even when it
// requires multiple receive ops. In the same way, we have a
// receiveSendToken.receivedMessageBytesDoneCount variable, which
// helps us handle message data, whether it requires one receive
// operation or many.
if (remainingBytesToProcess + receiveSendToken.receivedMessageBytesDoneCount
== receiveSendToken.lengthOfCurrentIncomingMessage)
{
// If we are inside this if-statement, then we got
// the end of the message. In other words,
// the total number of bytes we received for this message matched the
// message length value that we got from the prefix.

// Write/append the bytes received to the byte array in the
// DataHolder object that we are using to store our data.
Buffer.BlockCopy(receiveSendEventArgs.Buffer,
receiveSendToken.receiveMessageOffset,
receiveSendToken.theDataHolder.dataMessageReceived,
receiveSendToken.receivedMessageBytesDoneCount,
remainingBytesToProcess);

incomingTcpMessageIsReady = true;
}
else
{
// If we are inside this else-statement, then that means that we
// need another receive op. We still haven't got the whole message,
// even though we have examined all the data that was received.
// Not a problem. In SocketListener.ProcessReceive we will just call
// StartReceive to do another receive op to receive more data.

Buffer.BlockCopy(receiveSendEventArgs.Buffer,
receiveSendToken.receiveMessageOffset,
receiveSendToken.theDataHolder.dataMessageReceived,
receiveSendToken.receivedMessageBytesDoneCount,
remainingBytesToProcess);

receiveSendToken.receiveMessageOffset =
receiveSendToken.receiveMessageOffset -
receiveSendToken.recPrefixBytesDoneThisOp;

receiveSendToken.receivedMessageBytesDoneCount += remainingBytesToProcess;
}
return incomingTcpMessageIsReady;
}
}

class BufferManager
{
// This class creates a single large buffer which can be divided up
// and assigned to SocketAsyncEventArgs objects for use with each
// socket I/O operation.
// This enables buffers to be easily reused and guards against
// fragmenting heap memory.
//
//This buffer is a byte array which the Windows TCP buffer can copy its data to.

// the total number of bytes controlled by the buffer pool
Int32 totalBytesInBufferBlock;

// Byte array maintained by the Buffer Manager.
byte[] bufferBlock;
Stack<int> freeIndexPool;
Int32 currentIndex;
Int32 bufferBytesAllocatedForEachSaea;

public BufferManager(Int32 totalBytes, Int32 totalBufferBytesInEachSaeaObject)
{
totalBytesInBufferBlock = totalBytes;
this.currentIndex = 0;
this.bufferBytesAllocatedForEachSaea = totalBufferBytesInEachSaeaObject;
this.freeIndexPool = new Stack<int>();
}

// Allocates buffer space used by the buffer pool
internal void InitBuffer()
{
// Create one large buffer block.
this.bufferBlock = new byte[totalBytesInBufferBlock];
}

// Divide that one large buffer block out to each SocketAsyncEventArg object.
// Assign a buffer space from the buffer block to the
// specified SocketAsyncEventArgs object.
//
// returns true if the buffer was successfully set, else false
internal bool SetBuffer(SocketAsyncEventArgs args)
{
if (this.freeIndexPool.Count > 0)
{
//This if-statement is only true if you have called the FreeBuffer
//method previously, which would put an offset for a buffer space
//back into this stack.
args.SetBuffer(this.bufferBlock, this.freeIndexPool.Pop(),
this.bufferBytesAllocatedForEachSaea);
}
else
{
//Inside this else-statement is the code that is used to set the
//buffer for each SAEA object when the pool of SAEA objects is built
//in the Init method.
if ((totalBytesInBufferBlock - this.bufferBytesAllocatedForEachSaea) <
this.currentIndex)
{
return false;
}
args.SetBuffer(this.bufferBlock, this.currentIndex,
this.bufferBytesAllocatedForEachSaea);
this.currentIndex += this.bufferBytesAllocatedForEachSaea;
}
return true;
}

// Removes the buffer from a SocketAsyncEventArg object. This frees the
// buffer back to the buffer pool. Try NOT to use the FreeBuffer method,
// unless you need to destroy the SAEA object, or maybe in the case
// of some exception handling. Instead, on the server
// keep the same buffer space assigned to one SAEA object for the duration of
// this app's running.
internal void FreeBuffer(SocketAsyncEventArgs args)
{
this.freeIndexPool.Push(args.Offset);
args.SetBuffer(null, 0, 0);
}
}

class DataHoldingUserToken
{
internal Mediator theMediator;
internal DataHolder theDataHolder;
internal readonly Int32 bufferOffsetReceive;
internal readonly Int32 permanentReceiveMessageOffset;
internal readonly Int32 bufferOffsetSend;
private Int32 idOfThisObject;

internal Int32 lengthOfCurrentIncomingMessage;

//receiveMessageOffset is used to mark the byte position where the message
//begins in the receive buffer. This value can sometimes be out of
//bounds for the data stream just received. But, if it is out of bounds, the
//code will not access it.
internal Int32 receiveMessageOffset;
internal Byte[] byteArrayForPrefix;
internal readonly Int32 receivePrefixLength;
internal Int32 receivedPrefixBytesDoneCount = 0;
internal Int32 receivedMessageBytesDoneCount = 0;
//This variable will be needed to calculate the value of the
//receiveMessageOffset variable in one situation. Notice that the
//name is similar but the usage is different from the variable
//receiveSendToken.receivePrefixBytesDone.
internal Int32 recPrefixBytesDoneThisOp = 0;

internal Int32 sendBytesRemainingCount;
internal readonly Int32 sendPrefixLength;
internal Byte[] dataToSend;
internal Int32 bytesSentAlreadyCount;

//The session ID correlates with all the data sent in a connected session.
//It is different from the transmission ID in the DataHolder, which relates
//to one TCP message. A connected session could have many messages, if you
//set up your app to allow it.
private Int32 sessionId;

public DataHoldingUserToken(SocketAsyncEventArgs e, Int32 rOffset, Int32 sOffset,
Int32 receivePrefixLength, Int32 sendPrefixLength, Int32 identifier)
{
this.idOfThisObject = identifier;

//Create a Mediator that has a reference to the SAEA object.
this.theMediator = new Mediator(e);
this.bufferOffsetReceive = rOffset;
this.bufferOffsetSend = sOffset;
this.receivePrefixLength = receivePrefixLength;
this.sendPrefixLength = sendPrefixLength;
this.receiveMessageOffset = rOffset + receivePrefixLength;
this.permanentReceiveMessageOffset = this.receiveMessageOffset;
}

//Let's use an ID for this object during testing, just so we can see what
//is happening better if we want to.
public Int32 TokenId
{
get
{
return this.idOfThisObject;
}
}

internal void CreateNewDataHolder()
{
theDataHolder = new DataHolder();
}

//Used to create sessionId variable in DataHoldingUserToken.
//Called in ProcessAccept().
internal void CreateSessionId()
{
sessionId = Interlocked.Increment(ref Program.mainSessionId);
}

public Int32 SessionId
{
get
{
return this.sessionId;
}
}

public void Reset()
{
this.receivedPrefixBytesDoneCount = 0;
this.receivedMessageBytesDoneCount = 0;
this.recPrefixBytesDoneThisOp = 0;
this.receiveMessageOffset = this.permanentReceiveMessageOffset;
}
}

class Mediator
{
private IncomingDataPreparer theIncomingDataPreparer;
private OutgoingDataPreparer theOutgoingDataPreparer;
private DataHolder theDataHolder;
private SocketAsyncEventArgs saeaObject;

public Mediator(SocketAsyncEventArgs e)
{
this.saeaObject = e;
this.theIncomingDataPreparer = new IncomingDataPreparer(saeaObject);
this.theOutgoingDataPreparer = new OutgoingDataPreparer();
}

internal void HandleData(DataHolder incomingDataHolder)
{
theDataHolder = theIncomingDataPreparer.HandleReceivedData
(incomingDataHolder, this.saeaObject);
}

internal void PrepareOutgoingData()
{
theOutgoingDataPreparer.PrepareOutgoingData(saeaObject, theDataHolder);
}

internal SocketAsyncEventArgs GiveBack()
{
return saeaObject;
}
}

class IncomingDataPreparer
{
private DataHolder theDataHolder;
private SocketAsyncEventArgs theSaeaObject;

public IncomingDataPreparer(SocketAsyncEventArgs e)
{
this.theSaeaObject = e;
}

private Int32 ReceivedTransMissionIdGetter()
{
Int32 receivedTransMissionId =
Interlocked.Increment(ref Program.mainTransMissionId);
return receivedTransMissionId;
}

private EndPoint GetRemoteEndpoint()
{
return this.theSaeaObject.AcceptSocket.RemoteEndPoint;
}

internal DataHolder HandleReceivedData(DataHolder incomingDataHolder,
SocketAsyncEventArgs theSaeaObject)
{
DataHoldingUserToken receiveToken =
(DataHoldingUserToken)theSaeaObject.UserToken;
theDataHolder = incomingDataHolder;
theDataHolder.sessionId = receiveToken.SessionId;
theDataHolder.receivedTransMissionId =
this.ReceivedTransMissionIdGetter();
theDataHolder.remoteEndpoint = this.GetRemoteEndpoint();
this.AddDataHolder();
return theDataHolder;
}

private void AddDataHolder()
{
lock (Program.lockerForList)
{
Program.listOfDataHolders.Add(theDataHolder);
}
}
}

class OutgoingDataPreparer
{
private DataHolder theDataHolder;

internal void PrepareOutgoingData(SocketAsyncEventArgs e,
DataHolder handledDataHolder)
{
DataHoldingUserToken theUserToken = (DataHoldingUserToken)e.UserToken;
theDataHolder = handledDataHolder;

//In this example code, we will send back the receivedTransMissionId,
// followed by the
//message that the client sent to the server. And we must
//prefix it with the length of the message. So we put 3
//things into the array.
// 1) prefix,
// 2) receivedTransMissionId,
// 3) the message that we received from the client, which
// we stored in our DataHolder until we needed it.
//That is our communication protocol. The client must know the protocol.

//Convert the receivedTransMissionId to byte array.
Byte[] idByteArray = BitConverter.GetBytes
(theDataHolder.receivedTransMissionId);

//Determine the length of all the data that we will send back.
Int32 lengthOfCurrentOutgoingMessage = idByteArray.Length
+ theDataHolder.dataMessageReceived.Length;

//So, now we convert the length integer into a byte array.
//Aren't byte arrays wonderful? Maybe you'll dream about byte arrays tonight!
Byte[] arrayOfBytesInPrefix = BitConverter.GetBytes
(lengthOfCurrentOutgoingMessage);

//Create the byte array to send.
theUserToken.dataToSend = new Byte[theUserToken.sendPrefixLength
+ lengthOfCurrentOutgoingMessage];

//Now copy the 3 things to the theUserToken.dataToSend.
Buffer.BlockCopy(arrayOfBytesInPrefix, 0, theUserToken.dataToSend,
0, theUserToken.sendPrefixLength);
Buffer.BlockCopy(idByteArray, 0, theUserToken.dataToSend,
theUserToken.sendPrefixLength, idByteArray.Length);
//The message that the client sent is already in a byte array, in DataHolder.
Buffer.BlockCopy(theDataHolder.dataMessageReceived, 0,
theUserToken.dataToSend, theUserToken.sendPrefixLength
+ idByteArray.Length, theDataHolder.dataMessageReceived.Length);

theUserToken.sendBytesRemainingCount =
theUserToken.sendPrefixLength + lengthOfCurrentOutgoingMessage;
theUserToken.bytesSentAlreadyCount = 0;
}
}

class DataHolder
{
//Remember, if a socket uses a byte array for its buffer, that byte array is
//unmanaged in .NET and can cause memory fragmentation. So, first write to the
//buffer block used by the SAEA object. Then, you can copy that data to another
//byte array, if you need to keep it or work on it, and want to be able to put
//the SAEA object back in the pool quickly, or continue with the data
//transmission quickly.
//DataHolder has this byte array to which you can copy the data.
internal Byte[] dataMessageReceived;

internal Int32 receivedTransMissionId;

internal Int32 sessionId;

//for testing. With a packet analyzer this can help you see specific connections.
internal EndPoint remoteEndpoint;
}

internal sealed class SocketAsyncEventArgsPool
{
//just for assigning an ID so we can watch our objects while testing.
private Int32 nextTokenId = 0;

// Pool of reusable SocketAsyncEventArgs objects.
Stack pool;

// initializes the object pool to the specified size.
// "capacity" = Maximum number of SocketAsyncEventArgs objects
internal SocketAsyncEventArgsPool(Int32 capacity)
{
this.pool = new Stack(capacity);
}

// The number of SocketAsyncEventArgs instances in the pool.
internal Int32 Count
{
get { return this.pool.Count; }
}

internal Int32 AssignTokenId()
{
Int32 tokenId = Interlocked.Increment(ref nextTokenId);
return tokenId;
}

// Removes a SocketAsyncEventArgs instance from the pool.
// returns SocketAsyncEventArgs removed from the pool.
internal SocketAsyncEventArgs Pop()
{
lock (this.pool)
{
return this.pool.Pop();
}
}

// Add a SocketAsyncEventArg instance to the pool.
// "item" = SocketAsyncEventArgs instance to add to the pool.
internal void Push(SocketAsyncEventArgs item)
{
if (item == null)
{
throw new ArgumentNullException("Items added to a
SocketAsyncEventArgsPool cannot be null");
}
lock (this.pool)
{
this.pool.Push(item);
}
}
}

class SocketListenerSettings
{
// the maximum number of connections the sample is designed to handle simultaneously
private Int32 maxConnections;

// this variable allows us to create some extra SAEA objects for the pool,
// if we wish.
private Int32 numberOfSaeaForRecSend;

// max # of pending connections the listener can hold in queue
private Int32 backlog;

// tells us how many objects to put in pool for accept operations
private Int32 maxSimultaneousAcceptOps;

// buffer size to use for each socket receive operation
private Int32 receiveBufferSize;

// length of message prefix for receive ops
private Int32 receivePrefixLength;

// length of message prefix for send ops
private Int32 sendPrefixLength;

// See comments in buffer manager.
private Int32 opsToPreAllocate;

// Endpoint for the listener.
private IPEndPoint localEndPoint;

public SocketListenerSettings(Int32 maxConnections,
Int32 excessSaeaObjectsInPool, Int32 backlog, Int32 maxSimultaneousAcceptOps,
Int32 receivePrefixLength, Int32 receiveBufferSize, Int32 sendPrefixLength,
Int32 opsToPreAlloc, IPEndPoint theLocalEndPoint)
{
this.maxConnections = maxConnections;
this.numberOfSaeaForRecSend = maxConnections + excessSaeaObjectsInPool;
this.backlog = backlog;
this.maxSimultaneousAcceptOps = maxSimultaneousAcceptOps;
this.receivePrefixLength = receivePrefixLength;
this.receiveBufferSize = receiveBufferSize;
this.sendPrefixLength = sendPrefixLength;
this.opsToPreAllocate = opsToPreAlloc;
this.localEndPoint = theLocalEndPoint;
}

public Int32 MaxConnections
{
get
{
return this.maxConnections;
}
}
public Int32 NumberOfSaeaForRecSend
{
get
{
return this.numberOfSaeaForRecSend;
}
}
public Int32 Backlog
{
get
{
return this.backlog;
}
}
public Int32 MaxAcceptOps
{
get
{
return this.maxSimultaneousAcceptOps;
}
}
public Int32 ReceivePrefixLength
{
get
{
return this.receivePrefixLength;
}
}
public Int32 BufferSize
{
get
{
return this.receiveBufferSize;
}
}
public Int32 SendPrefixLength
{
get
{
return this.sendPrefixLength;
}
}
public Int32 OpsToPreAllocate
{
get
{
return this.opsToPreAllocate;
}
}
public IPEndPoint LocalEndPoint
{
get
{
return this.localEndPoint;
}
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值