//____________________________________________________________________________ // Connect to the host. // private void StartConnect(SocketAsyncEventArgs connectEventArgs) { //Cast SocketAsyncEventArgs.UserToken to our state object. ConnectOpUserToken theConnectingToken = (ConnectOpUserToken)connectEventArgs.UserToken; if (Program.watchProgramFlow == true) //for testing { Program.testWriter.WriteLine("StartConnect, connect id = " + theConnectingToken.TokenId); } //SocketAsyncEventArgs object that do connect operations on the client //are different from those that do accept operations on the server. //On the server the listen socket had EndPoint info. And that info was //passed from the listen socket to the SocketAsyncEventArgs object //that did the accept operation. //But on the client there is no listen socket. The connect socket //needs the info on the Remote Endpoint. connectEventArgs.RemoteEndPoint = this.socketClientSettings.ServerEndPoint; connectEventArgs.AcceptSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //Post the connect operation on the socket. //A local port is assigned by the Windows OS during connect op. bool willRaiseEvent = connectEventArgs.AcceptSocket.ConnectAsync(connectEventArgs); if (!willRaiseEvent) { if (Program.watchProgramFlow == true) //for testing { Program.testWriter.WriteLine("StartConnect method if (!willRaiseEvent), id = " + theConnectingToken.TokenId); } ProcessConnect(connectEventArgs); } }
//____________________________________________________________________________ // private void PushMessageArray(OutgoingMessageHolder theOutgoingMessageHolder) { SocketAsyncEventArgs connectEventArgs; //Get a SocketAsyncEventArgs object to connect with. //Get it from the pool if there is more than one. connectEventArgs = this.poolOfConnectEventArgs.Pop(); //or make a new one. if (connectEventArgs == null) { connectEventArgs = CreateNewSaeaForConnect(poolOfConnectEventArgs); } ConnectOpUserToken theConnectingToken = (ConnectOpUserToken)connectEventArgs.UserToken; if (Program.watchProgramFlow == true) //for testing { Program.testWriter.WriteLine("PushMessageArray, connect id = " + theConnectingToken.TokenId); } theConnectingToken.outgoingMessageHolder = theOutgoingMessageHolder; StartConnect(connectEventArgs); //Loop back to get message(s) for next connection. CheckStack(); }
//____________________________________________________________________________ // This method is called when an operation is completed on a socket // private void IO_Completed(object sender, SocketAsyncEventArgs e) { // determine which type of operation just completed and call the associated handler switch (e.LastOperation) { case SocketAsyncOperation.Connect: if (Program.watchProgramFlow == true) //for testing { ConnectOpUserToken theConnectingToken = (ConnectOpUserToken)e.UserToken; Program.testWriter.WriteLine("\r\nIO_Completed method In Connect, connect id = " + theConnectingToken.TokenId); } ProcessConnect(e); break; case SocketAsyncOperation.Receive: if (Program.watchProgramFlow == true) //for testing { DataHoldingUserToken receiveSendToken = (DataHoldingUserToken)e.UserToken; Program.testWriter.WriteLine("\r\nIO_Completed method In Receive, id = " + receiveSendToken.TokenId); } ProcessReceive(e); break; case SocketAsyncOperation.Send: if (Program.watchProgramFlow == true) //for testing { DataHoldingUserToken receiveSendToken = (DataHoldingUserToken)e.UserToken; Program.testWriter.WriteLine("\r\nIO_Completed method In Send, id = " + receiveSendToken.TokenId); } ProcessSend(e); break; case SocketAsyncOperation.Disconnect: if (Program.watchProgramFlow == true) //for testing { DataHoldingUserToken receiveSendToken = (DataHoldingUserToken)e.UserToken; Program.testWriter.WriteLine("\r\nIO_Completed method In Disconnect, id = " + receiveSendToken.TokenId); } ProcessDisconnectAndCloseSocket(e); break; default: { DataHoldingUserToken receiveSendToken = (DataHoldingUserToken)e.UserToken; if (Program.watchProgramFlow == true) //for testing { Program.testWriter.WriteLine("\r\nError in I/O Completed, id = " + receiveSendToken.TokenId); } throw new ArgumentException("\r\nError in I/O Completed, id = " + receiveSendToken.TokenId); } } }
//____________________________________________________________________________ // Pass the connection info from the connecting object to the object // that will do send/receive. And put the connecting object back in the pool. // private void ProcessConnect(SocketAsyncEventArgs connectEventArgs) { ConnectOpUserToken theConnectingToken = (ConnectOpUserToken)connectEventArgs.UserToken; if (connectEventArgs.SocketError == SocketError.Success) { lock (this.lockerForConnectionCount) { this.clientsNowConnectedCount++; if (this.clientsNowConnectedCount > this.maxSimultaneousClientsThatWereConnected) { this.maxSimultaneousClientsThatWereConnected++; } } SocketAsyncEventArgs receiveSendEventArgs = this.poolOfRecSendEventArgs.Pop(); receiveSendEventArgs.AcceptSocket = connectEventArgs.AcceptSocket; //Earlier, in the UserToken of connectEventArgs we put an array //of messages to send. Now we move that array to the DataHolder in //the UserToken of receiveSendEventArgs. DataHoldingUserToken receiveSendToken = (DataHoldingUserToken)receiveSendEventArgs.UserToken; receiveSendToken.theDataHolder.PutMessagesToSend(theConnectingToken.outgoingMessageHolder.arrayOfMessages); if (Program.showConnectAndDisconnect == true) { Program.testWriter.WriteLine("ProcessConnect connect id " + theConnectingToken.TokenId + " socket info now passing to\r\n sendReceive id " + receiveSendToken.TokenId + ", local endpoint = " + IPAddress.Parse(((IPEndPoint)connectEventArgs.AcceptSocket.LocalEndPoint).Address.ToString()) + ": " + ((IPEndPoint)connectEventArgs.AcceptSocket.LocalEndPoint).Port.ToString() + ". Clients connected to server from this machine = " + this.clientsNowConnectedCount); } messagePreparer.GetDataToSend(receiveSendEventArgs); StartSend(receiveSendEventArgs); //release connectEventArgs object back to the pool. connectEventArgs.AcceptSocket = null; this.poolOfConnectEventArgs.Push(connectEventArgs); if (Program.watchProgramFlow == true) //for testing { Program.testWriter.WriteLine("back to pool for connection object " + theConnectingToken.TokenId); } } //This else statement is when there was a socket error else { ProcessConnectionError(connectEventArgs); } }
//____________________________________________________________________________ // This method is called when we need to create a new SAEA object to do //connect 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 connect operations. private SocketAsyncEventArgs CreateNewSaeaForConnect(SocketAsyncEventArgsPool pool) { //Allocate the SocketAsyncEventArgs object. SocketAsyncEventArgs connectEventArg = new SocketAsyncEventArgs(); //Attach the event handler. Since we'll be using this //SocketAsyncEventArgs object to process connect ops, //what this does is cause the calling of the ConnectEventArg_Completed //object when the connect op completes. connectEventArg.Completed += new EventHandler <SocketAsyncEventArgs>(IO_Completed); ConnectOpUserToken theConnectingToken = new ConnectOpUserToken(pool.AssignTokenId() + 10000); connectEventArg.UserToken = theConnectingToken; return(connectEventArg); //You may wonder about the buffer of this object. If you //decide to use objects from one pool to do connect operations, and //a separate pool of SAEA objects to do send/receive, then //there is NO NEED to assign a buffer to this SAEA object for connects. //But, if you want to //use this SAEA object to do connect, send, receive, and disconnect, //then you will need a buffer for this object. //Working with the buffer is different in the client vs the server, due //to the way that ConnectAsync works. //You would need to think about whether to do the initial call of //BufferManager.SetBuffer here, or do it in ProcessConnect method. //If a SocketAsyncEventArg object has a buffer already set before //the ConnectAsync method is called, then the contents of the buffer will //be sent immediately upon connecting, without your calling StartSend(). //Read that sentence again. //So you could only call BufferManager.SetBuffer here if you are sure the //client will always do a send operation first, and the data will be //ready when the connection is made. If you want to have the //option of doing a receive operation first, then wait and set the buffer //after the connect operation completes. //If you decide to use that design, then you will need to call the //BufferManager's FreeBuffer method, to //null the buffer again before putting it back in the pool. FreeBuffer would //probably need to be called in the ProcessDisconnectAndCloseSocket method. }
//____________________________________________________________________________ // This method is called when we need to create a new SAEA object to do //connect 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 connect operations. private SocketAsyncEventArgs CreateNewSaeaForConnect(SocketAsyncEventArgsPool pool) { //Allocate the SocketAsyncEventArgs object. SocketAsyncEventArgs connectEventArg = new SocketAsyncEventArgs(); //Attach the event handler. Since we'll be using this //SocketAsyncEventArgs object to process connect ops, //what this does is cause the calling of the ConnectEventArg_Completed //object when the connect op completes. connectEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed); ConnectOpUserToken theConnectingToken = new ConnectOpUserToken(pool.AssignTokenId() + 10000); connectEventArg.UserToken = theConnectingToken; return connectEventArg; //You may wonder about the buffer of this object. If you //decide to use objects from one pool to do connect operations, and //a separate pool of SAEA objects to do send/receive, then //there is NO NEED to assign a buffer to this SAEA object for connects. //But, if you want to //use this SAEA object to do connect, send, receive, and disconnect, //then you will need a buffer for this object. //Working with the buffer is different in the client vs the server, due //to the way that ConnectAsync works. //You would need to think about whether to do the initial call of //BufferManager.SetBuffer here, or do it in ProcessConnect method. //If a SocketAsyncEventArg object has a buffer already set before //the ConnectAsync method is called, then the contents of the buffer will //be sent immediately upon connecting, without your calling StartSend(). //Read that sentence again. //So you could only call BufferManager.SetBuffer here if you are sure the //client will always do a send operation first, and the data will be //ready when the connection is made. If you want to have the //option of doing a receive operation first, then wait and set the buffer //after the connect operation completes. //If you decide to use that design, then you will need to call the //BufferManager's FreeBuffer method, to //null the buffer again before putting it back in the pool. FreeBuffer would //probably need to be called in the ProcessDisconnectAndCloseSocket method. }
//____________________________________________________________________________ internal void ProcessConnectionError(SocketAsyncEventArgs connectEventArgs) { ConnectOpUserToken theConnectingToken = (ConnectOpUserToken)connectEventArgs.UserToken; if (Program.watchProgramFlow == true) //for testing { Program.testWriter.WriteLine("ProcessConnectionError() id = " + theConnectingToken.TokenId + ". ERROR: " + connectEventArgs.SocketError.ToString()); } else if (Program.writeErrorsToLog == true) { Program.testWriter.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff") + " ProcessConnectionError() id = " + theConnectingToken.TokenId + ". ERROR: " + connectEventArgs.SocketError.ToString()); } Interlocked.Increment(ref this.totalNumberOfConnectionRetries); // If connection was refused by server or timed out or not reachable, then we'll keep this socket. // If not, then we'll destroy it. if ((connectEventArgs.SocketError != SocketError.ConnectionRefused) && (connectEventArgs.SocketError != SocketError.TimedOut) && (connectEventArgs.SocketError != SocketError.HostUnreachable)) { CloseSocket(connectEventArgs.AcceptSocket); } if (Program.continuallyRetryConnectIfSocketError == true) { // Since we did not send the messages, let's put them back in the stack. // We cannot leave them in the SAEA for connect ops, because the SAEA // could get pushed down in the stack and not reached. // If runLongTest == true, we reuse the same array of messages. So in that case // we do NOT need to put the array back in the BlockingStack. // But if runLongTest == false, we need to put the array of messages back in // the blocking stack, so that it will be taken out and sent. if (Program.runLongTest == true) { this.counterForLongTest.Release(); } else { this.stackOfOutgoingMessages.Push(theConnectingToken.outgoingMessageHolder); this.poolOfConnectEventArgs.Push(connectEventArgs); } } else { //it is time to release connectEventArgs object back to the pool. this.poolOfConnectEventArgs.Push(connectEventArgs); if (Program.watchProgramFlow == true) //for testing { Program.testWriter.WriteLine("back to pool for socket and SAEA " + theConnectingToken.TokenId); } Interlocked.Increment(ref this.totalNumberOfConnectionsFinished); //If we are not retrying the failed connections, then we need to //account for them here, when deciding whether we have finished //the test. if (this.totalNumberOfConnectionsFinished == this.socketClientSettings.ConnectionsToRun) { FinishTest(); } } this.theMaxConnectionsEnforcer.Release(); }