Background
This article introduces Microsoft's new peer-to-peer Collaboration technology in Windows Vista. Collaboration enables a new generation of serverless applications, and provides:
- automatic discovery of peers,
- automatic update of a peer's presence and availability,
- management of contacts and invitations, and
- discovery and matching of application capabilities.
Naming and Discovery
Peer Name Resolution Protocol (PNRP) is a serverless DNS that exists in today's versions of Windows. It's used by peer-to-peer applications to register their presence and discover other peers on the local network or Internet. PNRP capabilities are vast, however, it relies heavily on functioning IPv6 infrastructure and IPv4 to IPv6 tunneling. Windows Vista includes new improvements in this area, but is the topic for another article.
To simplify discover on a subnet, Microsoft has added the People Near Me functionality to Windows Vista. Applications must Sign-In to alert others of their presence and capabilities. Once signed in, an application can publish data to indicate what applications are installed or running on the local computer, or what data is needed to launch an application. Applications can also use the underlying collaboration API to enumerate other peers on the subnet and discover their application capabilities. People Near Me provides a simple way for a user to discover people in the same office or meeting room to create ad-hoc, real-time work groups to share resources and data. It remains to be seen if this functionality will be available in a future Windows XP service pack.
Contacts
Once you have discovered someone with whom you want to have collaboration over time, you can save them as a contact. The Collaboration API includes methods to manage contacts discovered on the subnet and store them in the Windows Address Book (WAB). It also provides APIs to create invitations to collaborate using a common, registered application. Finally, APIs are provided to monitor a contact's presence similar to the way a contact's status can be monitored in an Instant Messenger application.
Application Capabilities
An application can use the collaboration APIs during installation to register its capabilities and/or parameters needed to launch an application. Other users with the same application installed or running can discover and use this information to collaborate.
This new functionality combined with existing peer-to-peer graphing and grouping technology will allow developers to create new and innovative applications.
Introduction
Microsoft's entire Peer-to-Peer technology is exposed through the latest Platform SDK as C/C++ API calls. However, the code in this article shows these APIs being used from .NET managed code using C#. The sample application includes a PeerCollab
class that implements the SignIn
and SignOut
methods to control the presence of People Near Me on the local subnet. Also, delegates are provided to show changes in the presence of other People Near Me on the same subnet. While the PeerCollab class hides the details of using Microsoft's peer-to-peer collaboration APIs (in order to simplify the programming model), the flow of unmanaged calls is outlined below.
Startup
Before any other peer-to-peer collaboration APIs can be called, PeerCollabStartup
must be called to initialize the service. Likewise, PeerCollabShutdown
must be called before the application exits. To ensure this occurs, a static singleton class is provided. The PeerCollab
class includes a reference to guarantee that the singleton is created before any other collaboration APIs are used. As a result, you don't have to remember to do this. The following code shows the singleton class:
sealed class PeerCollabService
{
private PeerCollabService()
{
uint hr = PeerCollabNative.PeerCollabStartup(1);
if (hr != 0) throw new PeerCollabException(hr);
}
~PeerCollabService()
{
PeerCollabNative.PeerCollabShutdown();
}
public static readonly PeerCollabService
Instance = new PeerCollabService();
}
The PeerCollab Class
The purpose of the PeerCollab
class is to expose all the peer-to-peer collaboration functionality. However, the code download at the beginning of this article only implements the ability to sign-in, sign-out, display People Near Me, and handle events to monitor People Near Me as they come and go on the local subnet.
The PeerCollab
class constructor requires requires no parameters. In order for your application to publish its presence, it must call the SignIn
method.
SignIn
The SignIn
method takes a parameter that determines how your presence can be monitored:
NearMe
allows other applications on the same subnet to discover and monitor your presence.
Internet
allows other contacts to monitor your presence.
All
allows both subnet and internet users to monitor your presence.
None
prevents others from seeing your application.
Note that the sample application only demonstrates the NearMe
option. The following code shows how the SignIn
method calls the underlying PeerCollabSignin
API:
public void SignIn(PeerSignInOption Options)
{
uint hr =
PeerCollabNative.PeerCollabSignin(IntPtr.Zero,
Options);
if (hr != 0) throw new PeerCollabException(hr);
RegisterForEvents();
}
Note that according to Microsoft's documentation, the first parameter is supposed to take a HWND
handle to a parent window. at the time of writing this article using the February 2006 CTP of Windows Vista, this parameter cannot be used from .NET. The purpose of this parameter is to display the "acknowledge privacy" window. Until you acknowledge the privacy statement, the current Windows account cannot sign-in to People Near Me, and the sample application will log a PEER_E_PRIVACY_DECLINED
exception. Under Control Panel, Network and Internet, click on the People Near Me applet. The first time you do this, you should be presented with a window to acknowledge the privacy statement.
Once Microsoft supports the HWND
parameter from .NET, the "acknowledge privacy" window should appear for the sample application rather than immediately throwing an exception.
SignOut
The SignOut
method supports the same options as the SignIn
method, and allows partial or complete change of presence. The following code shows how the SignOut
method calls the underlying PeerCollabSignout
API.
public void SignOut(PeerSignInOption Options)
{
uint hr = PeerCollabNative.PeerCollabSignout(Options);
if (hr != 0) throw new PeerCollabException(hr);
UnregisterForEvents();
}
Note that signing in or out affects all other peer-to-peer collaboration applications running under the current Windows account. If you are planning to use collaboration from a service, you should consider creating a separate, distinct account for your service.
Status
Use the Status
property at any time to determine the current application's sign-in status. The following code shows how the Status
property calls the underlying PeerCollabGetSigninOptions
API.
public PeerSignInOption Status
{
get
{
PeerSignInOption Options;
uint hr = PeerCollabNative.PeerCollabGetSigninOptions(out Options);
if (hr != 0) throw new PeerCollabException(hr);
return Options;
}
}
People Near Me Collection
Once your application is signed in, you can use the PeopleNearMe
property to enumerate the People Near Me on the same subnet.
public PeopleNearMeCollection PeopleNearMe
{
get { return new PeopleNearMeCollection(); }
}
This property returns a PeopleNearMeCollection
collection. The collection implements the IEnumerable
interface. The Reset
method of this interface calls the underlying PeerCollabEnumPeopleNearMe
API to retrieve the current list of People Near Me on the same subnet.
public void IEnumerable.Reset()
{
uint hr;
if (hPeerEnum != IntPtr.Zero)
{
hr = PeerCollabNative.PeerEndEnumeration(hPeerEnum);
if (hr != 0) throw new PeerCollabException(hr);
hPeerEnum = IntPtr.Zero;
}
hr = PeerCollabNative.PeerCollabEnumPeopleNearMe(out hPeerEnum);
if (hr != 0) throw new PeerCollabException(hr);
uint count;
hr = PeerCollabNative.PeerGetItemCount(hPeerEnum, out count);
if (hr != 0) throw new PeerCollabException(hr);
this.count = (int)count;
index = -1;
}
Events
The Collaboration API allows an application to register and receive events when other applications' presence or capabilities change. The following code shows how the internal RegisterForEvents
method uses the PeerCollabRegisterEvent
method to register for events.
private void RegisterForEvents()
{
if (hPeerEvent != IntPtr.Zero) return;
AutoResetEvent sendEvent = new AutoResetEvent(false);
Handle = ThreadPool.RegisterWaitForSingleObject(sendEvent,
new WaitOrTimerCallback(PeerEventWorker), null, -1, false);
PEER_COLLAB_EVENT_REGISTRATION[] info =
{
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_WATCHLIST_CHANGED),
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_ENDPOINT_CHANGED),
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_ENDPOINT_PRESENCE_CHANGED),
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_ENDPOINT_APPLICATION_CHANGED),
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_ENDPOINT_OBJECT_CHANGED),
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_MY_ENDPOINT_CHANGED),
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_MY_PRESENCE_CHANGED),
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_MY_APPLICATION_CHANGED),
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_MY_OBJECT_CHANGED),
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_PEOPLE_NEAR_ME_CHANGED),
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_REQUEST_STATUS_CHANGED)
};
int size = Marshal.SizeOf(info[0]);
IntPtr infoptr = Marshal.AllocCoTaskMem(info.Length*size);
int offset = 0;
foreach (PEER_COLLAB_EVENT_REGISTRATION item in info)
{
Marshal.StructureToPtr(item, (IntPtr)(infoptr.ToInt32()+offset), false);
offset += size;
}
uint result = PeerCollabNative.PeerCollabRegisterEvent(sendEvent.Handle,
info.Length, infoptr, out hPeerEvent);
if (result != 0) Marshal.ThrowExceptionForHR((int)result);
}
The PEER_COLLAB_EVENT_REGISTRATION
data structure includes a field indicating the type of event to monitor. An array of these data structures can be passed to the register method. Much of the code above takes care of marshalling this data structure into a block of unmanaged memory.
The WaitOrTimerCallback
represents a callback method that is executed when something changes in the Collab. RegisterWaitForSingleObject
registers this delegate with the default thread pool using an infinite timeout. Finally, the event handle and data structure is passed to PeerCollabRegisterEvent
.
The following code shows how the worker thread handles incoming events.
private void PeerEventWorker(object xdata, bool timedOut)
{
while (true)
{
IntPtr evptr;
uint result =
PeerCollabNative.PeerCollabGetEventData(hPeerEvent, out evptr);
if (result == PeerCollabNative.PEER_S_NO_EVENT_DATA ||
evptr == IntPtr.Zero)
break;
if (result != 0) Marshal.ThrowExceptionForHR((int)result);
PEER_COLLAB_EVENT_DATA data =
(PEER_COLLAB_EVENT_DATA)Marshal.PtrToStructure(evptr,
typeof(PEER_COLLAB_EVENT_DATA));
IntPtr dataptr = (IntPtr)(evptr.ToInt32() +
Marshal.SizeOf(typeof(PEER_COLLAB_EVENT_DATA)));
switch (data.eventType)
{
case PEER_COLLAB_EVENT_TYPE.PEER_EVENT_REQUEST_STATUS_CHANGED:
HandleEventRequestStatusChanged(dataptr);
break;
}
PeerCollabNative.PeerFreeData(evptr);
}
}
The PeerCollabGetEventData
method is called to get each event. The loop ensures that all events are proceeded until PEER_S_NO_EVENT_DATA
is returned indicating no further events. Each event data structure is marshaled, and a switch
statement is used to handle each event type.
Event Types
While there are 11 types of events, this sample application only implements the PeopleNearMeChanged
and RequestStatusChanged
events. Future articles will demonstrate how the other events are typically used.
The PeopleNearMeChanged
event fires when a collaboration application's presence on the same subnet changes. Three types of changes can occur:
Added
occurs when a local or remote application signs into People Near Me (serverless presence).
Updated
occurs when a local or remote application changes its User Information (name or picture).
Deleted
occurs when a remote application signs out of People Near Me, or the local Windows account signs out.
The RequestStatusChanged
event fires when the status of an endpoint raises an error. It's unclear when and how this event should be used.
Using the Sample Application
The sample application only runs on Windows Vista. It automatically signs you into People Near Me, and shows you whether others on the same subnet are signed in too. The top list shows you the nick names of those who are signed in. The following code shows how this is done:
private void Form_Load(object sender, System.EventArgs e)
{
collab = new PeerCollab();
collab.RequestStatusChanged += new
Peer.Collaboration.PeerCollab.RequestStatusChangedHandler(
collab_RequestStatusChanged);
collab.PeopleNearMeChanged += new
PeerCollab.PeopleNearMeChangedHandler(
collab_PeopleNearMeChanged);
try
{
collab.SignIn(PeerSignInOption.NearMe);
foreach (PeopleNearMe pnm in collab.PeopleNearMe)
{
listBox1.Items.Add(pnm);
}
LogMessage(@"SignIn", "Completed");
}
catch (PeerCollabException ex)
{
LogMessage(@"SignIn", ex.Message);
}
}
Click on a name to see more information about each application signed into People Near Me. The bottom list contains a log of events or error messages that occur while the application is running.
If you only have one Windows Vista computer, use the Switch User feature to login as multiple users. Each user account has its own People Near Me presence on the subnet. You only need to start the sample application under one account. Under the other accounts, open Control Panel and use People Near Me to sign-in. You will notice a new icon appears in the system tray:
- indicates you are signed in to People Near Me.
- indicates you are signed out.
Right click on these icons to change the presence or your account or one of the other accounts. Also, right click and select Properties to change the User Information nickname. Changing the name will result in an Updated
event being logged by the sample application. The following code shows how the sample application handles the PeopleNearMeChanged
event.
void collab_PeopleNearMeChanged(object sender,
PeerCollabPNMChangedEventArgs e)
{
if (e.PeopleNearMe == null)
{
switch (e.Action)
{
case PeerChangeType.Deleted:
LogMessage(@"SignOut", "Me");
RefreshPeopleNearMe();
break;
case PeerChangeType.Added:
LogMessage(@"SignIn", "Me");
break;
case PeerChangeType.Updated:
LogMessage(@"Update", "Me");
break;
}
}
else
{
switch (e.Action)
{
case PeerChangeType.Added:
LogMessage(@"SignIn", e.PeopleNearMe.NickName);
AddPeopleNearMe(e.PeopleNearMe);
break;
case PeerChangeType.Deleted:
LogMessage(@"SignOut", "Someone Near Me");
RefreshPeopleNearMe();
break;
case PeerChangeType.Updated:
LogMessage(@"Update", e.PeopleNearMe.NickName);
RefreshPeopleNearMe();
break;
}
}
}
If the PeopleNearMe
property is null
, the event is a change to the local Windows account, otherwise, it's for another account (could be local or remote). For the Deleted
action, the event does not include the specific People Near Me reference. As a result, the sample application refreshes its list by enumerating the PeopleNearMe
again. This behavior may change by the time Windows Vista is released.
Point of Interest
You can monitor my ongoing progress while writing about peer-to-peer collaboration, by visiting my blog.
Links to Resources
I have found the following resources to be very useful in understanding peer-to-peer collaboration:
Conclusion
I hope you have found this article interesting. I'll be writing more articles to describe the other features of Peer-to-Peer Collaboration in the coming weeks and months.
If you have suggestions for other topics, please leave a comment. Finally, a big thanks to all those who have been voting.
History