/// <summary> /// The Shutdown method is invoked to instruct the module /// to terminate the connection to the peer system. If the /// graceful flag is set to true, then the module will wait /// until all pending I/O operations are completed before /// </summary> /// <param name="graceful"></param> public void Shutdown(bool graceful) { if (_moduleState == ModuleStates.Module_State_Active) { _moduleState = ModuleStates.Module_State_Closing; if (graceful == false) { // REC: Remove all pending send operations // from the queue and return their buffers // to the buffer pool: lock (_synchTxContextQueue) { while (_txContextQueue.Count > 0) { IoContext txContext = _txContextQueue.Dequeue(); txContext._ipcBuffer.RdIndex = 0; txContext._ipcBuffer.WrIndex = 0; lock (_synchIoContextQueue) { _ioContextQueue.Enqueue(txContext); } } } // REC: Close the socket that is being // used to send data to the client: _socket.Close(); } else { } } }
/// <summary> /// The Activate method is invoked to instruct the IPC module /// to start asynchronously processing IO. /// </summary> public void Activate() { if (_moduleState == ModuleStates.Module_State_Shutdown) { _moduleState = ModuleStates.Module_State_Active; // REC: Disable the Nagle algorithm: _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); // REC: Dispatch the Event_Opened event to the // module's owner: EventHandler <VfxIpcEventArgs> tmp = EventDispatch; if (tmp != null) { tmp(this, new VfxIpcEventArgs(VfxIpcEventTypes.Event_Session_Opened)); } // REC: Initiate the first receive operation against the // socket associated with the module. As receive operations // complete, the completion handler will take care of all the // subsequent IO context generation / management: IoContext rxContext = new IoContext(_socket, new VfxMsgBlock(8192)); InitiateRecv(rxContext); } }
/// <summary> /// The InitiateSend method starts an asynchronous send /// operation using the supplied IO context. If there is /// already a send operation in progress, the IO context /// will be enqueued to the pending IO queue and will be /// sent when the current operation completes. /// </summary> /// <param name="ctx"> /// The IO context that contains the buffer to be sent. /// </param> /// <param name="internalSend"> /// Flag that indicates whether or not the send operation /// is being initiated from internal or external code. /// </param> private void InitiateSend(IoContext ctx, bool internalSend) { if (internalSend == true) { ctx._ipcSocket.BeginSend(ctx._ipcBuffer.Buffer, ctx._ipcBuffer.RdIndex, ctx._ipcBuffer.Length(), 0, CompleteSend, ctx); } else { //lock (_txContextQueue) lock (_synchTxContextQueue) { if (_txPending == false) { // REC: The pending flag gets set so that the // next call to InitiateSend can determine if // it can send or needs to queue the request: _txPending = true; ctx._ipcSocket.BeginSend(ctx._ipcBuffer.Buffer, ctx._ipcBuffer.RdIndex, ctx._ipcBuffer.Length(), 0, CompleteSend, ctx); } else { // REC: If a send is already pending, then the // request is queued for the completion handler // to pick up when the current send is done: _txContextQueue.Enqueue(ctx); } } } }
/// <summary> /// The acceptor's activate method is called to start /// accepting incoming connections. The acceptor initiates /// an asynchronous accept operation, which is then handled /// in the OnAccept method when a connection is established. /// </summary> /// <param name="ipcEndPoint"> /// The IP endpoint that the acceptor is to accept incoming /// connections on. /// </param> /// <param name="factory"> /// The session factory that the acceptor is to use when /// creating new sessions to handle the incoming connections. /// </param> public void Activate(IPEndPoint ipcEndPoint, IVfxIpcSessionFactory factory) { Socket ipcSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP); IoContext context = new IoContext(ipcSocket, ipcEndPoint); _mapEndpoints.Add(ipcEndPoint, context); _mapFactories.Add(ipcEndPoint, factory); context.IpcSocket.Bind(context.IpcEndPoint); context.IpcSocket.Listen(100); context.IpcSocket.BeginAccept(CompleteAccept, context); }
/// <summary> /// The HandleTx method is invoked to request that an instance /// of the VfxTcpModule send the data in the specified message /// block out to the peer system it is connected to. /// </summary> /// <param name="mb"> /// The message block that contains the data that is to be sent /// to the peer system the module is connected to. /// </param> public void HandleTx(VfxMsgBlock mb) { if (_moduleState == ModuleStates.Module_State_Active) { // REC: Send all of the data in the buffer: while (mb.Length() > 0) { IoContext ctx = null; lock (_synchIoContextQueue) { if (_ioContextQueue.Count > 0) { ctx = _ioContextQueue.Dequeue(); } } if (ctx != null) { if (ctx._ipcBuffer.Remaining() >= mb.Length()) { // REC: Append the contents of the buffer // to the IO context's buffer and push it // onto the outgoing queue: ctx._ipcBuffer.UnsafeAppend(mb); mb.RdIndex = mb.WrIndex; InitiateSend(ctx, false); } else { // REC: Copy as much data as possible into // the context and attempt to send that: int txLength = ctx._ipcBuffer.Remaining(); ctx._ipcBuffer.UnsafeAppend(mb, txLength); // REC: Adjust the source buffer's index: mb.RdIndex += txLength; InitiateSend(ctx, false); } } else { // REC: Wait for a buffer to become available: _eventTxBuffer.WaitOne(); } } } }
/// <summary> /// The Shutdown method is invoked to request that the /// acceptor shutdown a specific endpoint. The acceptor /// will respond to this method being invoked by shutting /// down the specified endpoint so that connections are no /// longer accepted on it. /// </summary> /// <param name="ipcEndPoint"> /// The endpoint that is to be shutdown. /// </param> public void Shutdown(IPEndPoint ipcEndPoint) { if (_mapEndpoints.ContainsKey(ipcEndPoint)) { IoContext context = _mapEndpoints[ipcEndPoint]; context.IpcSocket.Close(); _mapEndpoints.Remove(ipcEndPoint); } if (_mapFactories.ContainsKey(ipcEndPoint)) { _mapFactories.Remove(ipcEndPoint); } }
private void CompleteAccept(IAsyncResult ar) { IoContext context = ar.AsyncState as IoContext; if (context != null) { try { Socket peer = context.IpcSocket.EndAccept(ar); // REC: Locate the factory associated with this endpoint // and create the appropriate type of session instance: if (_mapFactories.ContainsKey(context.IpcEndPoint)) { IPEndPoint peerEP = peer.RemoteEndPoint as IPEndPoint; if (ValidatePeer(peerEP)) { VfxTcpModule tcpModule = new VfxTcpModule(); tcpModule.Init(peer, this.RxBuffering); // REC: Create a new instance of the session handler that // is configured for the endpoint and associated the // client IO stream with it: IVfxIpcSession session = _mapFactories[context.IpcEndPoint].CreateSession(); session.Init(tcpModule); } else { peer.Close(); } } // REC: Initiate another asynchronous connection: context.IpcSocket.BeginAccept(CompleteAccept, context); } catch (System.ObjectDisposedException) { // REC: This exception gets thrown when an asynchronous // accept fails due to the socket it was initiated from // being closed. This is part of the shutdown procedure // and should just be caught and ignored. } } }
/// <summary> /// The Init method is called to initialize the IPC module /// with an instance of a .NET socket that will be used to /// communicate with a peer system. /// </summary> /// <param name="socket"> /// The socket that the module will use for communicating /// with the peer system. /// </param> /// <param name="buffered"> /// The flag that indicates whether or not the module will /// offload incoming data onto a separate thread before it /// routes those messages up to the module's owner. /// </param> public void Init(Socket socket, bool buffered = false) { // REC: Retain a reference to the socket for use // with the send and receive operations: this._socket = socket; // REC: Configure the receive buffering flag: this._rxBuffering = buffered; // REC: Pre-allocate the transmit contexts that will // be used for sending data to the peer system: for (int i = 0; i != _maxTxContexts; i++) { IoContext txContext = new IoContext(socket, new VfxMsgBlock(8192)); _ioContextQueue.Enqueue(txContext); } // REC: If the buffered flag is set, then the module will // push incoming data onto a separate thread for subsequent // processing by the owner of the module. if (buffered == true) { // REC: Allocate an initial bunch of IO contexts that // will be used for buffering incoming data. These are // going to be reused, so they are allocated up front: for (int i = 0; i != _maxRxContexts; i++) { IoContext rxContext = new IoContext(socket, new VfxMsgBlock(8192)); _rxContextQueue.Enqueue(rxContext); } // REC: The module starts a secondary thread which will // be used to buffer incoming data as it is received from // the peer system that the module is communicating with: this._rxThread = new Thread(HandleReceive_Entrypoint); this._rxThread.Start(); } }
/// <summary> /// The Activate method is invoked to instruct the IPC module /// to start asynchronously processing IO. /// </summary> public void Activate() { if (_moduleState == ModuleStates.Module_State_Shutdown) { _moduleState = ModuleStates.Module_State_Active; // REC: Disable the Nagle algorithm: _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); // REC: Dispatch the Event_Opened event to the // module's owner: EventHandler<VfxIpcEventArgs> tmp = EventDispatch; if (tmp != null) { tmp(this, new VfxIpcEventArgs(VfxIpcEventTypes.Event_Session_Opened)); } // REC: Initiate the first receive operation against the // socket associated with the module. As receive operations // complete, the completion handler will take care of all the // subsequent IO context generation / management: IoContext rxContext = new IoContext(_socket, new VfxMsgBlock(8192)); InitiateRecv(rxContext); } }
/// <summary> /// The HandleReceive_Entrypoint method is the entrypoint for /// the buffering thread that is started by the module if the /// module's owner has enabled incoming IO buffering. /// </summary> /// <param name="context"></param> private void HandleReceive_Entrypoint(object context) { // REC: The fragment buffer is used when there is residual // data remaining from a previously processed buffer. This // is allocated at 2x the receive block size, in order for // reducing the need to "expand" the fragment buffer when // data is left over from a receive operation: VfxMsgBlock rxFragment = new VfxMsgBlock(16384); while (true) { // REC: Lock the pending receive queue and check whether or not // there are any receive blocks waiting to be processed: IoContext rxPending = null; lock (this._synchRxPendingQueue) { if (this._rxPendingQueue.Count > 0) { rxPending = this._rxPendingQueue.Dequeue(); } } if (rxPending != null) { // REC: If there is data in the fragment buffer // then we need to append the data from the incoming // receive context to it: if (rxFragment.Length() > 0) { rxFragment.Append(rxPending._ipcBuffer); // REC: Dispatch from the fragment buffer instead // of from the receive context: EventHandler <VfxIpcEventArgs> tmp = EventDispatch; if (tmp != null) { tmp(this, new VfxIpcEventArgs(rxFragment)); rxFragment.Crunch(); } // REC: Reset the pointers in the receive context // since its data has been copied to the fragment // buffer for subsequent processing: rxPending._ipcBuffer.RdIndex = rxPending._ipcBuffer.WrIndex = 0; } else { // REC: There is no fragment remaining from the previous // receive operation, so we can just dispatch directly from // the received context: EventHandler <VfxIpcEventArgs> tmp = EventDispatch; if (tmp != null) { tmp(this, new VfxIpcEventArgs(rxPending._ipcBuffer)); rxPending._ipcBuffer.Crunch(); } // REC: Determine if there is a fragment in the buffer // so that we can chain it to subsequent blocks: if (rxPending._ipcBuffer.Length() > 0) { // REC: There is a fragment of a message remaining // in the current buffer, so it has to be copied into // the fragment buffer for further processing: rxFragment.Append(rxPending._ipcBuffer); // REC: Reset the points in the pending receive context // since it has been copied into the fragment buffer: rxPending._ipcBuffer.RdIndex = 0; rxPending._ipcBuffer.WrIndex = 0; } } // REC: Put the receive context back into the queue so // that it can be used by subsequent receive operations: lock (this._rxContextQueue) { this._rxContextQueue.Enqueue(rxPending); } } else { // REC: A message block wasn't available for us on this // iteration, so just wait until one is added to the queue: this._eventRxBuffer.WaitOne(); } } }
/// <summary> /// The CompleteRecv method is invoked in response to /// an asynchronous receive operation completing. /// </summary> /// <param name="ar"></param> private void CompleteRecv(IAsyncResult ar) { // REC: Retrieve the IO context that is associated with the // original asynchronous receive operation. IoContext rxContext = ar.AsyncState as IoContext; if (rxContext != null) { try { // REC: Get the total number of bytes that were read // from the peer system by this operation: int rxBytes = rxContext._ipcSocket.EndReceive(ar); if (rxBytes > 0) { // REC: Adjust the write index in the message block: rxContext._ipcBuffer.WrIndex = rxContext._ipcBuffer.WrIndex + rxBytes; // REC: Offload the received data onto a separate thread: if (this._rxBuffering == true) { Socket rxSocket = rxContext._ipcSocket; lock (this._synchRxPendingQueue) { // REC: Push the received data onto the queue so that // it can be picked up by the secondary receive thread // for further processing: this._rxPendingQueue.Enqueue(rxContext); // REC: If the queue was empty prior to this context // being added to it, we need to signal the secondary // receive thread so that it can start processing the // data again: if (this._rxPendingQueue.Count == 1) { this._eventRxBuffer.Set(); } } // REC: Attempt to pull another context from the context // queue so that we can receive more data: IoContext rxNext; if (this._rxContextQueue.Count > 0) { rxNext = this._rxContextQueue.Dequeue(); } else { // REC: Note that this is BAD and will result in the // exhaustion of all the system's memory if there is a // continuous problem with the speed at which messages // are being consumed on the receive thread... rxNext = new IoContext(rxSocket, new VfxMsgBlock(8192)); } // REC: Initiate a new receive operation, using the next // available receive context: InitiateRecv(rxNext); } else { // REC: If receive buffering has not been enabled then the // data is dispatched directly to the module's owner: EventHandler <VfxIpcEventArgs> tmp = EventDispatch; if (tmp != null) { tmp(this, new VfxIpcEventArgs(rxContext._ipcBuffer)); // REC: Adjust the buffer to take into consideration // any data that was read by the module's owner: rxContext._ipcBuffer.Crunch(); // REC: Initiate another receive operation using the // same receive buffer that was just processed: InitiateRecv(rxContext); } } } else { _moduleState = ModuleStates.Module_State_Shutdown; // REC: This might happen as the result of the // user closing the session down. EventHandler <VfxIpcEventArgs> tmp = EventDispatch; if (tmp != null) { VfxIpcEventArgs dxArgs = new VfxIpcEventArgs(VfxIpcEventTypes.Event_Session_Closed); tmp(this, dxArgs); } } } catch (SocketException) { _moduleState = ModuleStates.Module_State_Shutdown; // REC: This might happen as the result of the // user closing the session down. EventHandler <VfxIpcEventArgs> tmp = EventDispatch; if (tmp != null) { VfxIpcEventArgs dxArgs = new VfxIpcEventArgs(VfxIpcEventTypes.Event_Session_Closed); tmp(this, dxArgs); } } catch (ObjectDisposedException) { _moduleState = ModuleStates.Module_State_Shutdown; // REC: This happens if the socket gets closed // locally rather than being closed by the peer. EventHandler <VfxIpcEventArgs> tmp = EventDispatch; if (tmp != null) { VfxIpcEventArgs dxArgs = new VfxIpcEventArgs(VfxIpcEventTypes.Event_Session_Closed); tmp(this, dxArgs); } } } }
private void InitiateRecv(IoContext rxContext) { rxContext._ipcSocket.BeginReceive(rxContext._ipcBuffer.Buffer, rxContext._ipcBuffer.WrIndex, rxContext._ipcBuffer.Remaining(), 0, CompleteRecv, rxContext); }
/// <summary> /// The CompleteSend event handler is invoked in response to an /// asynchronous send operation being completed. The system reacts /// to the completion by finalizing the asynchronous operation and /// then starting a new one. /// </summary> /// <param name="ar"></param> private void CompleteSend(IAsyncResult ar) { IoContext ctx = ar.AsyncState as IoContext; if (ctx != null) { int txBytes = ctx._ipcSocket.EndSend(ar); if (txBytes > 0) { // REC: If the system did not transmit all of the data // that was in the IO context buffer then it initiates // a new send operation against the residual content: if (txBytes < ctx._ipcBuffer.Length()) { // REC: Adjust the read index... ctx._ipcBuffer.RdIndex += txBytes; ctx._ipcBuffer.Crunch(); // REC: and try to send it again... InitiateSend(ctx, true); } else { lock (_synchIoContextQueue) { // REC: Reset the message block's // read and write indices: ctx._ipcBuffer.RdIndex = 0; ctx._ipcBuffer.WrIndex = 0; // REC: Return the buffer to the // pool so it can be used again: _ioContextQueue.Enqueue(ctx); // REC: If there were no contexts remaining // in the transmit queue when this one was // returned to it, trigger the buffer event // so that HandleTx can acquire one: if (_ioContextQueue.Count == 1) { _eventTxBuffer.Set(); } } lock (_synchTxContextQueue) { if (_txContextQueue.Count > 0) { IoContext next = _txContextQueue.Dequeue(); InitiateSend(next, true); } else { _txPending = false; // REC: If the module is being shutdown // and there are no further pending sends // left to process, generate the shutdown // event to notify the module's owner: if (_moduleState == ModuleStates.Module_State_Closing) { _moduleState = ModuleStates.Module_State_Shutdown; // REC: Close the socket to ensure that the // disconnect event is triggered: _socket.Close(); } } } } } } }
/// <summary> /// The CompleteRecv method is invoked in response to /// an asynchronous receive operation completing. /// </summary> /// <param name="ar"></param> private void CompleteRecv(IAsyncResult ar) { // REC: Retrieve the IO context that is associated with the // original asynchronous receive operation. IoContext rxContext = ar.AsyncState as IoContext; if (rxContext != null) { try { // REC: Get the total number of bytes that were read // from the peer system by this operation: int rxBytes = rxContext._ipcSocket.EndReceive(ar); if (rxBytes > 0) { // REC: Adjust the write index in the message block: rxContext._ipcBuffer.WrIndex = rxContext._ipcBuffer.WrIndex + rxBytes; // REC: Offload the received data onto a separate thread: if (this._rxBuffering == true) { Socket rxSocket = rxContext._ipcSocket; lock (this._synchRxPendingQueue) { // REC: Push the received data onto the queue so that // it can be picked up by the secondary receive thread // for further processing: this._rxPendingQueue.Enqueue(rxContext); // REC: If the queue was empty prior to this context // being added to it, we need to signal the secondary // receive thread so that it can start processing the // data again: if (this._rxPendingQueue.Count == 1) { this._eventRxBuffer.Set(); } } // REC: Attempt to pull another context from the context // queue so that we can receive more data: IoContext rxNext; if (this._rxContextQueue.Count > 0) { rxNext = this._rxContextQueue.Dequeue(); } else { // REC: Note that this is BAD and will result in the // exhaustion of all the system's memory if there is a // continuous problem with the speed at which messages // are being consumed on the receive thread... rxNext = new IoContext(rxSocket, new VfxMsgBlock(8192)); } // REC: Initiate a new receive operation, using the next // available receive context: InitiateRecv(rxNext); } else { // REC: If receive buffering has not been enabled then the // data is dispatched directly to the module's owner: EventHandler<VfxIpcEventArgs> tmp = EventDispatch; if (tmp != null) { tmp(this, new VfxIpcEventArgs(rxContext._ipcBuffer)); // REC: Adjust the buffer to take into consideration // any data that was read by the module's owner: rxContext._ipcBuffer.Crunch(); // REC: Initiate another receive operation using the // same receive buffer that was just processed: InitiateRecv(rxContext); } } } else { _moduleState = ModuleStates.Module_State_Shutdown; // REC: This might happen as the result of the // user closing the session down. EventHandler<VfxIpcEventArgs> tmp = EventDispatch; if (tmp != null) { VfxIpcEventArgs dxArgs = new VfxIpcEventArgs(VfxIpcEventTypes.Event_Session_Closed); tmp(this, dxArgs); } } } catch (SocketException) { _moduleState = ModuleStates.Module_State_Shutdown; // REC: This might happen as the result of the // user closing the session down. EventHandler<VfxIpcEventArgs> tmp = EventDispatch; if (tmp != null) { VfxIpcEventArgs dxArgs = new VfxIpcEventArgs(VfxIpcEventTypes.Event_Session_Closed); tmp(this, dxArgs); } } catch (ObjectDisposedException) { _moduleState = ModuleStates.Module_State_Shutdown; // REC: This happens if the socket gets closed // locally rather than being closed by the peer. EventHandler<VfxIpcEventArgs> tmp = EventDispatch; if (tmp != null) { VfxIpcEventArgs dxArgs = new VfxIpcEventArgs(VfxIpcEventTypes.Event_Session_Closed); tmp(this, dxArgs); } } } }
/// <summary> /// The Init method is called to initialize the IPC module /// with an instance of a .NET socket that will be used to /// communicate with a peer system. /// </summary> /// <param name="socket"> /// The socket that the module will use for communicating /// with the peer system. /// </param> /// <param name="buffered"> /// The flag that indicates whether or not the module will /// offload incoming data onto a separate thread before it /// routes those messages up to the module's owner. /// </param> public void Init(Socket socket, bool buffered=false) { // REC: Retain a reference to the socket for use // with the send and receive operations: this._socket = socket; // REC: Configure the receive buffering flag: this._rxBuffering = buffered; // REC: Pre-allocate the transmit contexts that will // be used for sending data to the peer system: for (int i = 0; i != _maxTxContexts; i++) { IoContext txContext = new IoContext(socket, new VfxMsgBlock(8192)); _ioContextQueue.Enqueue(txContext); } // REC: If the buffered flag is set, then the module will // push incoming data onto a separate thread for subsequent // processing by the owner of the module. if (buffered == true) { // REC: Allocate an initial bunch of IO contexts that // will be used for buffering incoming data. These are // going to be reused, so they are allocated up front: for (int i = 0; i != _maxRxContexts; i++) { IoContext rxContext = new IoContext(socket, new VfxMsgBlock(8192)); _rxContextQueue.Enqueue(rxContext); } // REC: The module starts a secondary thread which will // be used to buffer incoming data as it is received from // the peer system that the module is communicating with: this._rxThread = new Thread(HandleReceive_Entrypoint); this._rxThread.Start(); } }
/// <summary> /// The InitiateSend method starts an asynchronous send /// operation using the supplied IO context. If there is /// already a send operation in progress, the IO context /// will be enqueued to the pending IO queue and will be /// sent when the current operation completes. /// </summary> /// <param name="ctx"> /// The IO context that contains the buffer to be sent. /// </param> /// <param name="internalSend"> /// Flag that indicates whether or not the send operation /// is being initiated from internal or external code. /// </param> private void InitiateSend(IoContext ctx, bool internalSend) { if (internalSend == true) { ctx._ipcSocket.BeginSend(ctx._ipcBuffer.Buffer, ctx._ipcBuffer.RdIndex, ctx._ipcBuffer.Length(), 0, CompleteSend, ctx); } else { //lock (_txContextQueue) lock(_synchTxContextQueue) { if (_txPending == false) { // REC: The pending flag gets set so that the // next call to InitiateSend can determine if // it can send or needs to queue the request: _txPending = true; ctx._ipcSocket.BeginSend(ctx._ipcBuffer.Buffer, ctx._ipcBuffer.RdIndex, ctx._ipcBuffer.Length(), 0, CompleteSend, ctx); } else { // REC: If a send is already pending, then the // request is queued for the completion handler // to pick up when the current send is done: _txContextQueue.Enqueue(ctx); } } } }