You can argue that one can overcome these shortcomings by multithreading meaning that one can spawn a new thread and let that thread do the polling which then notifies the main thread of the data. This concept could work well, but even if you create a new thread it would require your main thread to share the CPU time with this new thread. Windows operating system (Windows NT /2000 /XP) provide what is called Completion Port IO model for doing overlapped ( asynchronous) IO.
The details of IO Completion port are beyond the scope of the current discussion, but to make it simple you can think of IO Completion Ports as the most efficient mechanism for doing asynchronous IO in Windows that is provided by the Operating system. Completion Port model can be applied to any kind of IO including the file read /write and serial communication.
The .NET asynchronous socket programming helper class's Socket provides the similar model.
BeginReceive
.NET framework's Socket class provides BeginReceive method to receive data asynchronously i.e., in an non-blocking manner The BeginReceive method has following signature:
public IAsyncResult BeginReceive( byte[] buffer, int offset, int size, SocketFlags
socketFlags, AsyncCallback callback, object state );
The way BeginReceive
function works is that you pass the function a buffer
, a callback function (delegate) which will be called whenever data arrives.
The last parameter, object, to the BeginReceive
can be any class derived from
object ( even null ) .
When the callback function is called it means that the BeginReceive
function
completed which means that the data has arrived.
The callback function needs to have the following signature:
void AsyncCallback( IAsyncResult ar);
As you can see the callback returns void and is passed in one parameter ,
IAsyncResult
interface , which contains the status of the asynchronous receive
operation.
The IAsyncResult
interface has several properties. The first parameter - AsyncState
- is an object which is same as the last parameter that you passed to BeginReceive()
.
The second property is AsyncWaitHandle
which we will discuss in a moment. The
third property indicates whether the receive was really asynchronous or it
finished synchronously. The important thing to follow here is that it not necessary
for an asynchronous function to always finish asynchronously - it can complete
immediately if the data is already present. Next parameter is IsComplete
which
indicates whether the operation has completed or not.
If you look at the signature of the BeginReceive
again you will note that
the function also returns IAsyncResult
. This is interesting. Just now I said
that I will talk about the second peoperty of the IAsyncResult
in a moment.
Now is that moment. The second parameter is called AsyncWaitHandle
.
The AsyncWaitHandle
is of type WaitHandle
, a class defined in the System.Threading
namespace. WaitHandle
class encapsulates a Handle
(which is a pointer to int
or handle ) and provides a way to wait for that handle to become signaled.
The class has several static methods like WaitOne
( which is similar to WaitForSingleObject
) WaitAll
( similar to WaitForMultipleObjects
with waitAll true ) , WaitAny
etc. Also there are overloads of these functions available with timeouts.
Coming back to our discussion of IAsyncResult
interface, the handle in AsyncWaitHandle
(WaitHandle
) is signalled when the receive operation completes. So if we wait
on that handle infinitely we will be able to know when the receive completed.
This means if we pass that WaitHandle to a different thread, the different
thread can wait on that handle and can notify us of the fact that the data
has arrived and so that we can read the data. So you must be wondering if we
use this mechanism why would we use callback function. We won't. Thats right.
If we choose to use this mechanism of the WaitHandle then the callback function
parameter to the BeginReceive can be null as shown here:
//m_asynResult is declared of type IAsyncResult and assumming that m_socClient
has made a connection.
m_asynResult = m_socClient.BeginReceive(m_DataBuffer,0,m_DataBuffer.Length,SocketFlags.None,null,null);
if ( m_asynResult.AsyncWaitHandle.WaitOne () )
{
int iRx = 0 ;
iRx = m_socClient.EndReceive (m_asynResult);
char[] chars = new char[iRx + 1];
System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
int charLen = d.GetChars(m_DataBuffer, 0, iRx, chars, 0);
System.String szData = new System.String(chars);
txtDataRx.Text = txtDataRx.Text + szData;
}
Even though this mechanism will work fine using multiple threads, we will for now stick to our callback mechanism where the system notifies us of the completion of asynchronous operation which is Receive in this case .
Lets say we made the call to BeginReceive
and after some time
the data arrived and our callback function got called.Now question is where's
the data? The
data is now available in the buffer that you passed as the first parameter
while making call to BeginReceive()
method . In the following example the
data will be available in m_DataBuffer :
BeginReceive(m_DataBuffer,0,m_DataBuffer.Length,SocketFlags.None,pfnCallBack,null);
But before you access the buffer you need to call EndReceive() function on the socket. The EndReceive will return the number of bytes received . Its not legal to access the buffer before calling EndReceive. To put it all together look at the following simple code:
byte[] m_DataBuffer = new byte [10];
IAsyncResult m_asynResult;
public AsyncCallback pfnCallBack ;
public Socket m_socClient;
// create the socket...
public void OnConnect()
{
m_socClient = new Socket (AddressFamily.InterNetwork,SocketType.Stream
,ProtocolType.Tcp );
// get the remote IP address...
IPAddress ip = IPAddress.Parse ("10.10.120.122");
int iPortNo = 8221;
//create the end point
IPEndPoint ipEnd = new IPEndPoint (ip.Address,iPortNo);
//connect to the remote host...
m_socClient.Connect ( ipEnd );
//watch for data ( asynchronously )...
WaitForData();
}
public void WaitForData()
{
if ( pfnCallBack == null )
pfnCallBack = new AsyncCallback (OnDataReceived);
// now start to listen for any data...
m_asynResult =
m_socClient.BeginReceive (m_DataBuffer,0,m_DataBuffer.Length,SocketFlags.None,pfnCallBack,null);
}
public void OnDataReceived(IAsyncResult asyn)
{
//end receive...
int iRx = 0 ;
iRx = m_socClient.EndReceive (asyn);
char[] chars = new char[iRx + 1];
System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
int charLen = d.GetChars(m_DataBuffer, 0, iRx, chars, 0);
System.String szData = new System.String(chars);
WaitForData();
}
The OnConnect
function makes a connection to the server and then makes a call
to WaitForData
. WaitForData
creates the callback function and makes a call
to BeginReceive
passing a global buffer and the callback function. When data
arrives the OnDataReceive
is called and the m_socClient's EndReceive
is called
which returns the number of bytes received and then the data is copied over
to a string and a new call is made to WaitForData
which will call BeginReceive
again and so on. This works fine if you have one socket in you application.
Comments