Mobilized .NET* Applications

by Clayne B. Robison


Introduction

While much research has been done to determine the best native Windows* APIs to use, little has been done to assist developers in adapting their distributed .NET* Framework-based applications for mobile environments. In this paper, we present sample methods to obtain system network connection information using the classes found in the .NET Framework. The initial intention is to enable individual applications to get this information on their own-the laissez-faire approach. But the hope is that these methods will eventually be used by a system-wide mobility-aware infrastructure that will in turn make the information more accessible to subscribing applications.

For this paper, we looked specifically at version 1.1 of the .NET Framework running on desktop and laptop systems. Note that this does not include applications running on the .NET Compact Framework, although some of the same methods may apply. When working within the .NET Framework, we can access the information to provide mobility awareness via three interfaces:

  • The Common Language Runtime (CLR) API
  • The Windows Management Instrumentation (WMI) API
  • The native Windows API

 

Inasmuch as the .NET Framework is essentially a wrapper for the native Windows API, and inasmuch as the CLR System.Management namespace provides a CLR interface to WMI, this distinction between APIs is given for the sake of those applications that for one reason or another are limited in what they can do outside of the .NET Framework sandbox.


What makes an Application 'Mobilized'?

An application that is aware of mobility has the ability to function smoothly (i.e. maintain or increase user productivity) in a mobile environment. An application that can function in a mobile environment is one that is aware enough of power, network, and sleep state that it can free the user from care about these uniquely mobile states. The user can then focus instead on tasks directly related to productivity.

Typically, a mobile aware application provides four capabilities:

  • Offline Data Management
  • Seamless Connectivity
  • Multiple Platform Support
  • Power & Performance Management

 

Offline Data Management

An application that provides offline data management makes distributed data available offline. For example, a database application that provides this capability, allows a mobile user access to relevant data even when working offline. The management of this offline data implies the ability to synchronize changes made to the data while in an offline mode with the central data repository.

A system that provides offline data management needs the ability to predict what a user will need in offline mode, pre-fetch that data and then cache it. With this offline data, the application can emulate an on-line state, allowing the user to be as productive as if the main copy of the data were available. Once the user reconnects to the network, the two copies of the data must be reconciled through a synchronization process. Thus, making data available offline is simple; but managing that data and maintaining coherency is the more significant problem.

Seamless Connectivity

Software that pro vides seamless connectivity enables the user to never worry about connection state. This might be as simple as detecting the presence of a route to the server, or choosing between multiple service providers and then establishing a connection. The goal is to allow the user to spend more time on real work, not on network management. Once a connection to the server is detected, the software senses the rate at which data can be transferred and makes intelligent decisions based on that data rate. For example, upon reconnect to a network an application will automatically start the synchronization process without requiring user intervention.

Multiple Platform Support

Multiple platform support in this case refers not only to operating system platforms, but also to the multiple hardware platforms for which an application can be written, for example, laptop, PDA, smart phone, cell phone, etc. Ideally, the user's experience on each of these devices is identical, and there should be no need to re-learn the basic application functionality.

Power & Performance Management

A mobilized application is power friendly, and recognizes that battery, unlike AC, is a limited resource and should be conserved when possible. Like other limited resources, usage should become more restricted as the threat of resource exhaustion become eminent. For example, background threads and non-essential functionality, can be prioritized and postponed until power is more plentiful.

Also, atomic operations that should be completed once they have started (e.g. burning a CD) should first ensure that sufficient battery power remains for the task to complete.

Laissez-Faire Awareness vs. System Level Awareness vs. Adaptive Systems

Once this kind of mobilized software capability is available on a system, what happens when multiple applications request the information? Some information required to be aware of mobility is process specific, and is system-wide. Should each application be responsible for monitoring system connectivity state, or should applications subscribe to that information from a system service?

On the one end of the spectrum, a laissez-faire approach to mobile enabling allows each application to obtain the information it needs. This approach requires the most modification at the application layer, but requires no mobility awareness in the underlying system. However, when multiple applications need the same information, this approach is inefficient.

If all information for mobile awareness is provided at the system level, applications ideally can continue functioning as always connected systems because the mobile sub-system completely abstracts connection state from applications. In this model, applications must surrender control of information to a central broker, but there is duplicitous information gathering

In between these models is the architecture in which applications can subscribe as needed to the information that a mobility subsystem publishes. Applications are aware of mobility, but they are not completely responsible for adapting to it. This architecture makes the best use of system resources while allowing the greatest flexibility at the application layer.

Figure 1: Mobile Environment Transparency


Network Connection Awareness

Information about the path between client and server is critical for applications that provide seamless transitions between connected and unconnected states. We'll use the classes in the System.Net and System.Net.Sockets namespaces to illustrate how to get that information.

IPEndPoint Visibility

The first question that we need to ask before transferring data to or from a remote system is "Is the service on my target machine visible?" This means knowing not only the IP address of the machine, but the port number on which a server is listening.1 For a Web-Service, this port would be either 80 for unencrypted traffic or 443 for SSL traffic.

CLR

One way of determining the visibility of our service is simply to attempt a connection to it. If it works, we can see it. If we get an error, we can use the error code to find out why we can't see it. Using the Connect() method of the Sockets class2, it might look like this:

Figure 2: Establishing a Connection

 

try

{

	s.Connect(ipEndPoint); bEndPointVisible = true;

	//If no exception is thrown, we have a connection

	bEndPointVisible = true;

}

catch(SocketException se) 

{

	bEndPointVisible = false;

	//figure out what the problem was

	switch ( se.ErrorCode )

	{

		case 10165: 

		//...

	}

}

 

Using the error codes, we can determine what the problem is.

Table 1: Possible Error Codes for the Socket.Connect() method.

Error code Meaning
WSANOTINITIALISED A successful WSAStartup call must occur before using this function.
WSAENETDOWN The network subsystem has failed.
WSAEADDRINUSE The socket's local address is already in use and the socket was not marked to allow address reuse with SO_REUSEADDR. This error usually occurs when executing bind, but could be delayed until this function if the bind was to a partially wildcard address (involving ADDR_ANY) and if a specific address needs to be committed at the time of this function.
WSAEINTR The blocking Windows Socket 1.1 call was canceled through WSACancelBlockingCall.
WSAEINPROGRESS A blocking Windows Sockets 1.1 call is in progress, or the service provider is still processing a callback function.
WSAEALREADY A nonblocking connect call is in progress on the specified socket.

Note In order to preserve backward compatibility, this error is reported as WSAEINVAL to Windows Sockets 1.1 applications that link to either Winsock.dll or Wsock32.dll.
WSAEADDRNOTAVAIL The remote address is not a valid address (such as ADDR_ANY).
WSAEAFNOSUPPORT Addresses in the specified family cannot be used with this socket.
WSAECONNREFUSED The attempt to connect was forcefully rejected.
WSAEFAULT The name or the namelen parameter is not a valid part of the user address space, the namelen parameter is too small, or the name parameter contains incorrect address format for the associated address family.
WSAEINVAL The parameter s is a listening socket.
WSAEISCONN The socket is already connected (connection-oriented sockets only).
WSAENETUNREACH The network cannot be reached from this host at this time.
WSAENOBUFS No buffer space is available. The socket cannot be connected.
WSAENOTSOCK The descriptor is not a socket.
WSAET IMEDOUT Attempt to connect timed out without establishing a connection.
WSAEWOULDBLOCK The socket is marked as nonblocking and the connection cannot be completed immediately.
WSAEACCES Attempt to connect datagram socket to broadcast address failed because setsockopt option SO_BROADCAST is not enabled.

 

Advantages to this method are 1) if the connection is successful, the socket is ready for use; and 2) the SocketExecption.ErrorCode property can give useful information about the cause of the error. If the server is available, the call can return almost immediately; but if the server is not available, the call can take up to 20 seconds before it times out and throws an exception. For this reason, it may be best to use the asynchronous Socket.BeginConnect() call; but even so, it will take up to 20 seconds in order to verify that the service is not visible.

Another method of using the Sockets class to detect remote system visibility is to send ICMP packets across the network to the remote machine (i.e. 'ping' the remote system). This method, however, at best can only determine whether a remote machine is on the network, and does not guarantee the visibility of the target service on that machine. Another problem with using ICMP to determine remote system visibility is that the packets are not guaranteed to reach the server. Even for applications that run inside a firewall (or through VPN tunnels) corporate IT policies may not allow ICMP traffic to be forwarded. Because of these disadvantages, we won't take the time to demonstrate custom 'ping' communication in this article.

WMI

The root/Cimv2 namespace provides two classes that allow us to determine whether an application is running on a remote system. If the application is a registered Windows service, it can be found by querying the WMI database for Win32_Service objects. If the "Status" property of our target service is equal to "Running", our service is running. Running processes that are not registered Windows services can be discovered using the Win32_Process class. However, neither of these methods allow us to determine whether our target application is ready to receive or send data. Nor does this method work when the requestor does not have sufficient access privileges on the WMI database-a most likely situation.

Provided the user has sufficient rights on their own system, we can use the Win32_PingStatus class that is available on Windows XP and Windows Server 2003 to determine the visibility of a remote machine. Obtaining an instance of this class is analogous to executing the "ping" utility from a command line. For example:

Figure 3: Using WMI to 'Ping' a remote machine

ManagementObjectSearcher mos = 

    new ManagementObjectSearcher ("rootCimV2", 

    string.Format("SELECT * FROM Win32_PingStatus "

                  "WHERE Address="{0}"",

    strIPAddress));

foreach ( ManagementObject mo in mos.Get() )

{

    // MgmtClassGen.exe generated class

    PingStatus ps = new PingStatus(mo); 

    switch (ps.StatusCode)

    {

        case PingStatus.StatusCodeValues.Success:

	//do stuff

	//...

    }

}

 

This directs the WMI ping provider to send an ICMP packet to the machine at address strIPAddress. The disadvantages of using ICMP are discussed above.

Windows API

By calling into the unmanaged Windows API, we can take advantage of numerous functions that can help us determine the visibility of a remote machine. Especially helpful are the functions in the IP Helper API found in the Platform SDK. The GetBestInterface function tells us the index of the best interface to use to reach a specified address:

Figure 4: Using the GetBestInterface Function

DWORD dwBestIfIndex = 0xFFFF;

DWORD retVal = GetBestInterface(inet_addr(szIPAddr), &dwBestIfIndex);

if ( retVal == NO_ERROR )

{

    printf("Best Interface to IP Address %s = %x.", 

           szIPAddr, dwBestIfIndex);

}

else

{

    printf("Error Occured: %x .", retVal);

}

 

When the destination machine is visible, NO_ERROR is returned and the interface index is valid; when no network interface is present, this function returns an error. But when an interface is present, this function can produce a false positive: the return value is NO_ERROR and the Index number is valid even though the remote machine is not visible. In addition to being somewhat unreliable, as is the case with ICMP, most of these functions can only help us determine whether a remote machine is available and do not allow us to determine whether an application is running on that machine.

1* These two pieces of data-IP Address and Port number-are encapsulated in the System.Net.IPEndPoint class, hence the section heading "IPEndPoint Visibility."

2* The CLR also provides TCPClient and UDPClient classes that further simplify the use of these protocols. Our discussion here focuses on Sockets, but the same methodology can be used in these classes as well.


IPEndPoint Visibility Changes

The next two questions that distributed application developers must ask are related to changes in the visibility of IPEndPoints: "How can I tell if my remote system is no longer available so I can stop trying to transfer data to it?" and "How can I tell if my remote system has appeared so I can start communicating with it?" If we combine these two questions with the two possible data transfer situations that we might be in at any time-we are either transferring data, or we aren't-we come up with a matrix:

Currently Transferring Data?
IPEndPoint Change Type Yes No
Appearance Irrelevant Relevant
Disappearance Relevant Irrelevant

 

Logically, only two of these situations are relevant. If we are currently transferring data, the sudden appearance of our target IP EndPoint is not likely: we are currently transferring data to/from it. However, we do want to know whether it has disappeared. Conversely, if we are not currently transferring data, we probably don't care whether our target EndPoint disappears. However, we might get data to send, and therefore we want to be notified when an EndPoint becomes visible.

CLR

In reality, what we are looking for in the first situation is an event to notify us that the EndPoint to which we are currently transferring information is no longer visible. No such event exists as such in the CLR, but we could certainly roll our own using the SocketException that gets thrown when our active Socket.Send() or Socket.Receive() call is disconnected3:

Figure 5: Using SocketException to Detect IP Endpoint Disappearance

Socket s = new Socket(...);


//Establish Socket connection

try

{

    bConnected = true;

    s.Send(sBuffer);

    s.Receive(rBuffer);

}

catch(SocketException se)

{

    bConnected = false; 

    //determine what went wrong

    switch( se.ErrorCode )

    {

        case 10165:

        //...

    }

}

 

Regarding the answer to the second question-has my remote service appeared? - it would be nice if we could register for an event to notify us when our IPEndPoint was available. Here's our event handler:

Figure 6: Our EndPoint Available Event Handler

//This method gets called when our IPEndPoint is available

public void EndPointAvailableEventHandler ( 

    EndPointAvailableEventArgs args )

{

    //args contains the connected socket

    args.getSocket().Send(…);

}

 

Here's the method we call to register for the event:

Figure 7: Registering for the EndPoint Available Event

//Call this to register for the IPEndPointAvailable event

public void NotifyIPEndPointAvailable(

    IPEndPoint RemoteEndPoint,

    EndPointAvailableEventHandler callbackFunction )

{

    this.m_remoteEndPoint = RemoteEndPoint;

    this.m_callbackFunction = callbackFunction;

}

 

After we've registered, we send the monitor thread going and wait for notification:

Figure 8: Polling for Connection State

//Call this from a separate thread

public void PollForEndPoint(int iPollingInterval)

{

    bool bConnected = false;

    while ( m_bAlive && !bConnected )

    {

        //make sure we start out disconnected

	Socket s = new Socket (...);

	
	try

	{

	    s.Connect( m_remoteEndPoint );

	    if( this.m_callbackFunction != null )

	    {

	        m_callbackFunction( 

                    new EndPointAvailableEventArgs( s ) );

	    }

	    bConnected = true;

	}

	catch (SocketException se )

	{

	    Thread.Sleep( iPollingInterval ); //try again in a bit

	}

    }

}

 

The advantage of this method is that we know for sure that the service on our remote machine (and not just the machine) is either up or down. The disadvantage is that we have to tweak the Socket object in order to get the status change information within a reasonable amount of time. By tweaking the send and receive buffer sizes, and by turning off the Nagle algorithm, we can be notified almost immediately when our service disappears in the middle of a transfer, but we do sacrifice some network efficiency. And to get the same immediacy on our service appearance event, we really should use the asynchronous BeginConnect() method and timeout sooner than the sockets sub-system default:

Figure 9: Tweaking the Connection Timeout

//Call this from a separate thread

public void PollForEndPoint(int iPollingInterval, int iTimeOut)

{

    bool bConnected = false;

    while ( m_bAlive && !bConnected )

    {

        try 

        {

	    //WaitOne() calls must block until Set() is called

            m_autoResetEvent.Reset();

	    Socket s = new Socket(...);

	    
	    //Delegate the blocking connection

            //call to another thread

	    s.BeginConnect( remoteEndPoint, 

	        new AsyncCallback( myAsyncConnectionCallback ),

		s ); //Give that thread our socket object


            //Give the other thread 5 seconds to make a connection

            if ( m_autoResetEvent.WaitOne( iTimeOut, false ) )

            {

                //The socket connected in time: 

                //we have a valid socket

                if( this.m_callbackFunction != null )

                {

                    m_callbackFunction( 

                        new EndPointAvailableEventArgs( s ) );

                }

                bConnected = true;

            }

            else

            {

                //We timed out and we don't have a valid socket

                //try again in a bit

                Thread.Sleep( iPollingInterval );

            }

        }

        catch (SocketException se )

        {

            //Figure out what happened

        }

        }

}

 

WMI

The MSNdis_StatusMediaConnect event in the root/WMI namespace is theoretically generated whenever an NDIS adapter's media moves from a disconnected to a connected state (e.g. when the Ethernet cable is plugged in, or when the Wireless card de tects a signal). However, aside from the fact that the event is not completely reliable (it doesn't detect the appearance of PPP adapters at all), the event is generated before any protocol binding takes place and real work can be done. This is fine in environments where the protocol binding follows closely on the heels of the media connection (e.g. environments with static IP addresses), but the event can be only used as a cue in environments where the protocol binging lags behind the media connections (e.g. DHCP environments). This method also does not guarantee IP EndPoint availability-only the existence of a media link between the local system and its nearest network node.

The MSNdis_StatusMediaDisconnect event detects when an NDIS adapter's media moves from a connected to a disconnected state. This is a more useful (albeit just as unreliable) an event because one can guarantee that if the media is disconnected, the protocol is also unbound, and the IP EndPoint on the other end of the pipe is no longer visible. Hence, registering for the MSNdis_StatusMediaDisconnect event can be useful when we are transferring data and want to know when the connection disappears.

If we use MgmtClassGen.exe to generate a C# wrapper for the MSNdis_StatusMediaDisconnect event, registering for the MSNdis events in C# looks like this:

Figure 10: Registering for the Event

try

{

    //query WMI for the event watcher

    m_MediaDisconnectWatcher = 

        new ManagementEventWatcher("rootwmi",
 
        "SELECT * FROM MSNdis_StatusMediaDisconnect");

			
    // Register for the event

    m_MediaDisconnectWatcher.EventArrived += 

        new EventArrivedEventHandler(

            this.MediaDisconnectEventHandler);

			
    //start the event thread

    m_MediaDisconnectWatcher.Start();

}

catch (ManagementException me)

{

    if ( m_WMIDisconnectWatcher != null )

    {

        m_WMIDisconnectWatcher.Stop();

        m_WMIDisconnectWatcher= null;

    }

}

 

Figure 11: The Event Handler

public void MediaDisconnectEventHandler(object o, 

    EventArrivedEventArgs e)

{

    //Use the NewEvent property to make a 

    //StatusMediaDisconnect object

    StatusMediaDisconnect smd = 

        new StatusMediaDisconnect(e.NewEvent);


    //figure out which adapter was disconnected

    string strDisconnectedAdapterName = smd.InstanceName;


    //...

}

 

3* Assuming we are using a connection-oriented protocol like TCP.


Effective Data Rate

The effective data rate refers to the speed at which real data is being transferred to a remote IP Endpoint. On an Ethernet connection with a link speed of 100 Mbps, in the unlikely event that there were no transport overhead or packet collisions, the best effective data rate would be 12.5 MBps. Link speed, like the speed limit on an interstate freeway, is useful in determining the ideal nature of the immediate segment of a journey; but it is not very helpful when there is a lot of traffic, and when the segment beyond our immediate ken has a much lower speed limit.

CLR

As with IP EndPoint information, the Socket class provides all the information we need to calculate the effective data rate of our connection. If we extend the Socket class with a DataRateAwareSocket class, we can easily track how many bytes are transferred, and how long it takes to transfer them. For example:

Figure12: A DataRateAwareSocket Send() method

//Our new Send method

public new int Send (byte[] buffer)

{

    DateTime start = DateTime.Now; //Start the clock

    int retVal = base.Send( buffer );

    DateTime stop = DateTime.Now; //stop the clock

    TimeSpan elapsedTime = stop - start;

    double rate = (double)retVal / elapsedTime.TotalSeconds;


    Monitor.Enter( m_dCumulativeSendRate ); //Synchronize access

    //BAD algorithm, but you get the idea

    m_dCumulativeSendRate = ( m_dCumulativeSendRate + rate ) / 2; 

    Monitor.Exit( m_dCumulativeSendRate );

    return retVal;

}


public double SendRate

{

    get

    {

        //Calculation is already done, just return the value

	double retVal;

	Monitor.Enter( m_dCumulativeSendRate );

	retVal = m_dCumulativeSendRate;

	Monitor.Exit( m_dCumulativeSendRate );

	return retVal;

    }

}

 

We can make the data transferring threads more efficient if we just accumulate the bytes transferred in the transfer operation and perform calculations only when they the querying thread wants the data rate:

Figure 13: A More Efficient Method

//more efficient

public new int Send (byte[] buffer)

{

    int retVal = base.Send( buffer );

    TotalSentBytes += retVal;

    return retVal;

}


//Don't call this on the same thread that is sending data!

public double SampleSendRate(TimeSpan sampleLength)

{

    int iStartByteCount = TotalSentBytes;

			
    //sleep while data is sent

    Thread.Sleep(sampleLength);

    int iEndByteCount = TotalSentBytes;

    int iSentBytes = iEndByteCount - iStartByteCount;

			
    //rate = bytes/second

    return iSentBytes / sampleLength.TotalSeconds;

}

 

Because we have more than one thread using the TotalBytes property, we need to make it thread-safe:

Figure 14: Our Thread-safe TotalBytes Property

//The object we are going to synchronize on must be a reference type

private Int32 m_iTotalSentBytes = new Int32(); 


//allow threadsafe access

private int TotalSentBytes

{

    get

    {

        int retVal;

	Monitor.Enter( m_iTotalSentBytes ); //Synchronize access

	retVal = m_iTotalSentBytes;

	Monitor.Exit( m_iTotalSentBytes );

	return retVal;

    }

    set

    {

        Monitor.Enter( m_iTotalSentBytes ); //Synchronize access

	m_iTotalSentBytes += value; 

	Monitor.Exit( m_iTot

alSentBytes );
			
    }

}

 

A few caveats here. First, this method does introduce some sampling overhead, compounded by our use of synchronization objects. Second, because of the Nagle algorithm4 and by the size of underlying send buffer, our Send() method above may return before the data is actually sent across the network media. One work around would be to disable the Nagle algorithm and optimize the send buffer size while sampling is taking place; this, of course, would mean that the sampled data rate is not a completely accurate measure of the effective data rate.

Finally, as is the case with road traffic, it would be nice to know the effective data rate before we start transferring data. The method above, however, requires at least some data to be transferred before a measurement can be taken. Since that is probably what we are trying to do anyway, this is no big deal. Should the data rate be too slow for an effective transfer, we can abort the process; if our distributed application has the ability to resume the transfer, we have at lease made forward progress while determining our data rate.

We can work around some of these problems by creating our own ping functionality using sockets. This would alleviate the sampling overhead and allow us to estimate the effective data rate between us and our target machine before we transfer any of our own data. However, because network traffic requires system resources that may be constrained, and because ICMP replies can be generated by network hardware and drivers, this method is guaranteed to reflect the working state of the remote system on the data rate. And, of course, this method is useless in ICMP-unfriendly network environments.

WMI

If we really want to use ICMP, using the Win32_PingStatus object is much simpler. If we modify the WQL query we used above to include a buffer size, we can time the ManagementObjectSearcher.Get() method call to see how long it took to get a response:

Figure 15: Using a Win32_PingStatus Object to Estimate Effective Data Rate

ManagementObjectSearcher mos = new ManagementObjectSearcher (

    "rootCimV2", 

    string.Format("SELECT * FROM Win32_PingStatus "

        "WHERE Address="{0}" AND BufferSize={1}", 

    strIPAddress,

    iBufferSize ) );

this.StartClock();

ManagementObjectCollection moc = mos.Get();

this.StopClock();

foreach ( ManagementObject mo in moc )

{

    //Class generated by MgmtClassGen.exe

    PingStatus ps = new PingStatus(mo); 

    if ( ps.StatusCode == PingStatus.StatusCodeValues.Success )

    {

        UInt32 uiByteCount = ps.ReplySize + ps.BufferSize;

        double rate = uiByteCount/this.ElapsedTime.TotalSeconds;

	//Alternatively, use the ResponseTime property of 

        //PingStatus

	//double rate = uiByteCount/ps.ResponseTime;


        //ICMP send and reply are sequential;

        //we want a full-duplex data rate

	//Assuming that traffic is flowing 

        //the same speed in both directions

	double fullDuplexRate = rate * 2;

    }

}

 

As mentioned above, using the ICMP protocol to determine our effective data rate doesn't require us to send any of ou r own data in order to determine the transfer rate, and there is no sampling overhead. Using WMI to access the information that ICMP can provide allows us the same functionality as our own ICMP sockets implementation, without all of the coding/validation overhead. But we are tied to the platforms that support the Win32_Ping Status class5.

4* See Jones, Anthony and Jim Ohlund, Network Programming for Microsoft Windows, p. 303-304 for more information on the Nagle Algorithm.

5* At the time of writing, Microsoft Windows XP, and Microsoft Windows Server 2003.


Effective Data Rate Changes

If we can detect changes in the effective data rate between two endpoints, then users can subscribe to high and low thresholds and we can notify them when these thresholds are crossed. The two most likely uses of this information are: 1) An application has a large transfer that it does not want to begin until the Effective data rate to its target endpoint rises above a certain threshold; and 2) an application that is currently transferring data wishes to be notified when the effective data rate of the transfer drops below a certain threshold. Notice that for the first scenario, non-application data must be used to measure the data rate because the user wants the effective data rate in order to begin a transfer. In the second scenario, however, the application specific data can be used to measure the data rate. Therefore, our discussion needs to answer the question in two scenarios: when a transfer is already taking place, and when a transfer is pending.

CLR

When data is already being transferred, we can use the Send() and Receive() methods on the extended Socket() class illustrated above to track the data rate. Let's provide delegates and threshold subscription methods:

Figure 16: A Sample Threshold Subscription Method

//The threshold event delegate

public delegate void DataRateThresholdEventHandler(

    object sender,
 
    DataRateThresholdEventArgs e);


public DataRateAwareSocket: Socket

{

    //....

    protected double m_dSendLowThreshold;// KB/sec

    protected event DataRateThresholdEventHandler

        SendDataRateLowThresholdEvent;

    //....

	
    //Allow them to subscribe to the event and specify a threshold

    public void SubscribeSendRateBelowThreshold(

        double dKBytesPerSecond, 

        DataRateThresholdEventHandler eventHandler)

    {

        this.SendDataRateLowThresholdEvent += eventHandler;

        this.m_dSendLowThreshold = dKBytesPerSecond ;

    }
	
}

 

We then begin our own thread to use the SampleSendRate() method we defined above, compare the rate with the threshold, and notify the user when the data rate crosses the threshold:

Figure 17: Sample, Evaluate, and Notify

//Don't call on the same thread that is sending

protected void SampleAndEvaluateSendRate()

{
		
    while (m_bAlive)

    {

        //sample the send rate

        double rat

e = this.SampleSendRate(

        new TimeSpan( 0,0,0,0, this.m_iSampleLength ) );


        //if it is below the send threshold, raise an event

        if ( rate < this.m_dSendLowThreshold )

        {

            if ( this.SendDataRateLowThresholdEvent != null )

	    {

	        SendDataRateLowThresholdEvent( this, 

                new DataRateThresholdEventArgs( rate ) );

            }

        }

        Thread.Sleep(this.m_iPollingInterval);

    }

}

 

If we need notification that the data rate has risen above a threshold before we transfer data, we have to be a little more creative. Because we logically have to transfer some data in order to calculate a data rate greater than 0, we have two choices:

  • Transfer non-application data, or
  • Transfer application data.

 

A good choice for non-application data is ICMP data. To do this, we modify our SampleSendRate() method above to use either our custom Socket ICMP functionality, or the WMI Win32_PingStatus class. Another option would be to inject traffic measuring data into the sockets that have already been established, but that would require the receiver to have the ability to filter out and discard non-application traffic.

However, if we have the option to use application data in our SampleSendRate() method, notifying subscribers that the data rate has risen above a threshold simply involves reversing the logic of the SampleAndEvaluateSendRate() method above.


Adapter Selection

One final consideration remains with regard to enabling .NET based applications to function ideally in the mobile network environment. When there is more than one active network adapter in the system, how do we determine which is the best one? In most situations would come down to which connection has the highest effective data rate, and for many applications, the best solution may be to bind to and utilize all available local IP addresses that can see a remote endpoint. But there may be more considerations such as the existence IPSec or other security mechanism on the connection, and in some situations, a WLAN connection may be more desirable (because it is persistent between desk and meeting room) than a LAN connection, even though the latter has the better data rate. Socket methods that are called without binding to a local IP address allow the underlying system to determine the best connection, but if the underlying system's definition of best connection contradicts the application's definition, there needs to be a way to override that choice using the Socket.Bind() call6. Obviously the definition of 'best adapter' is a system specific policy issue that is beyond the scope of this paper, but mapping IP addresses to its underlying adapter is crucial to making this kind of decision.

CLR

From within the CLR, the only information available is the list of IP addresses provided in the IPHostEntry class. These IP addresses can each be used in a Socket.Bind() call that will bind all subsequent socket calls to the adapter bound to that IP address. However, the CLR provides no information about the underlying adapter.

WMI

Using the root/cimv2 namespace's Win32_NetworkAdapterConfiguration objects, we can take the IP Address provided by the IPHostEntry class and find out which adapter it is bound to7.

Windo ws

The IPHelper API in the Windows Platform SDK provides helpful methods to query the system route and network adapter tables. This API also provides events that report changes to these tables.

6* And what of clients or servers that have multiple IP addresses aliased to the same adapter?

7* The root/wmi namespace’s MSNdis_LinkSpeed object can provide information about the current link speed of the adapters on the system.


Conclusion

In summary, in order for an application to maximize user productivity in the mobile computing world, it must be aware and take advantage of network connection state. Mobilized software must be able to detect and react to the visibility of target services, as well as changes in that visibility; it must also be able to determine the effective data rate to the remote service, as well as changes to that data rate. In some instances, determining the mapping of IP addresses to underlying adapters is also helpful in determining the best connection when there is a choice. While there is some information that cannot be obtained without using either the WMI or the Windows API, most of the information required for network connection awareness can be gleaned using methods provided by the CLR.


References

Jones, Anthony and Jim Ohlund, Network Programming for Microsoft Windows.

Developer Centers

 


Download the PDF (279KB)

 

Categorie:
Per informazioni complete sulle ottimizzazioni del compilatore, consultare l'Avviso sull'ottimizzazione