它基本上是一个安装在多台PC上的应用程序,每个安装都维护着它自己的数据库,该数据库与其他PC同步。当他们同时启动(连接到同一网络)时。
我已经使用简单的套接字连接和自定义缓冲区对此进行了测试,但是希望使应用程序之间的通信符合公认的标准并且还要安全/健壮,而不是试图重新发明轮子。
这种app-to-app通讯的正常/标准方式是什么?我在哪里可以找到更多信息?
此外,还有哪些技术可用于在网络上宣布和查找其他应用程序?
编辑: (提炼我的问题)
下面的gimel指向的pub / sub模型似乎与我需要的一致。然而,它涵盖了很多地面和我真的不知道要带走什么&从这一切中使用。
看起来我需要在两个或更多应用找到对方后建立P2P连接 - 我该怎么做?
如果有可用的示例/教程,请指出它们。实现类似我需要的东西的小型开源项目/模块也将提供服务。
我选择的平台是Linux,但基于Windows的示例也非常实用。
编辑[09-01-06]:
我目前正在考虑以下选项:
我很感激您对这些选项的看法以及是否有任何示例。遗憾的是,我没有选择使用中央服务器或网站(除非可以保证免费和永久)。
[编辑2009-02-19]
(希望我能接受两个/三个答案!我接受的那个答案因为它提供了思路和可能性,而其他人则提供了固定但适用的解决方案。感谢所有回答的人,所有这些都有所帮助。 )
As&当我找到/实现我的解决方案时,我将更新这个问题,如果解决方案足够,我将为它创建一个sourceforge项目。 (无论如何,在一个更大的项目中,这是一个小问题。)
答案 0 :(得分:5)
请参阅Publish / Subscribe异步消息传递范例。
示例实现是Apache ActiveMQ:
Apache ActiveMQ速度快,支持许多跨语言客户端和协议,附带易于使用的企业集成模式和许多高级功能,同时完全支持JMS 1.1和J2EE 1.4。
答案 1 :(得分:4)
答案 2 :(得分:3)
我设计了一个类似于几年前你所描述的应用程序。我设计了一个在每个桌面上运行的“广告服务器”,并使用UDP将其状态广播到网络上运行的任何其他程序。现在,这有它自己的一系列问题,这取决于你打算如何运行这个应用程序...但是,这是它的工作原理的快速和肮脏......
我在端口上设置了一个“监听器”,这是通过散列数据库服务器和应用程序连接到的数据库来选择的。这将确保我从中获得广播的任何人使用与我相同的数据库,并允许应用程序的多个实例在桌面上运行(设计要求)。
然后,我设置了各种广播某些事件的“BroadcastMessage()”函数。我甚至允许使用我的API的开发人员能够使用自定义有效负载数据创建自定义事件,然后让程序为该事件注册一个监听器,该事件会在该事件进入时通知注册器,并通过它随附的数据。
例如,当应用程序启动时,它会播放“我在这里”的消息,任何人都可以听取消息,忽略它或回复消息。在“我在这里”,它包含正在运行的应用程序的IP地址,因此任何客户端都可以通过TCP连接连接到它以进行进一步的数据更新,以便交付HAD。
我选择了UDP,因为并非所有其他正在运行的实例都能看到这些广播的要求。它比任何东西都更方便......如果有人在你在同一个屏幕上时为数据库添加了一条记录,新记录就会“出现”在你的桌面上。
如果管理员在运行应用程序时更改了用户的权限,用户也不必退出并重新进入应用程序,收到更新并在那里处理,并且用户可以做他们需要的事情。
在一个只侦听这些类型的消息的线程上设置一个监听器真的很容易......如果你需要示例代码,我也可以提供它,但它是用C ++编写的,专为Windows设计,但它使用原始的wsock32.lib,所以它应该很容易转移到任何Unix平台。 (只需要输入defde DWORD,因为我经常使用它。)。
答案 3 :(得分:3)
我已经在网络管理中解决了这个问题几次。您的基本关注点似乎是“发现”,您的应用如何相互发现。
老实说,最简单的方法就是知道你的IP地址&一个掩码(大多数是类c),并尝试连接到该类c中的每台机器。
如果您默认使用C类,这意味着它几乎总是适用于大多数网络。然后,您可以允许覆盖添加到要连接的特定IP地址或其他子网的覆盖。
要发现一个C类,你只需找出你的IP地址(假设为192.168.2.77),然后迭代192.168.2中的所有内容。(1-254),尝试打开每个的连接。
我已经用多个线程完成了它(你可以一次ping所有设备并在3秒内获得好的结果。我在5分钟内用几百个线程发现了一个B类网络!),或者你可以在单个线程中从一个到另一个 - 但如果你这样做,请确保你的超时真的很低(1/2秒左右),否则它将需要永远 - 即使在1/2秒它将花一点时间来进行巡视。
您可能还希望让发现线程以较低的速度在后台运行,并始终搜索新配偶。
并缓存您的“已知良好”IP地址,以便更快地启动。
这不是一个难题,但它也不是微不足道的。只是希望做一些小小的工作。
此外,您可能希望能够添加新的IP /掩码来扫描外部子网。如果你想联系互联网上的设备,就没有办法解决这个问题(虽然一旦一台PC发现网络,它可以将地址发送给所有其他人,如果你愿意的话,这可能真的很快变大!)
答案 4 :(得分:2)
您是否希望这完全是P2P,或者您是否计划让中央服务器执行更多操作然后再作为目录?
对于通信安全性,SSL应该没问题。 Java以非常简单的方式支持这些,如果你正在使用它。这是java 6中SSL的reference
答案 5 :(得分:1)
确定。正如所承诺的,这里是我从我的应用程序中删除的一些示例代码。这不是编译和运行的预期,这是 I 如何做到这一点的一个例子。你可能不得不完全不同。另外,这是为Windows编写的,正如您将在代码中看到的,它使用Windows消息在服务器线程和主应用程序之间发送数据,但这完全取决于您计划如何使用它。我留下了一些更有趣的部分供你参考。
至于安全部分,我认为你可以处理那部分。这只是一个简单的问题,即在数据通过线路加密之前,使用一些众所周知的密码,所以我不认为我必须包含任何这些。例如,您可以看到我是如何构造数据包标头的,然后是一个通常由另一个结构组成的有效负载。因此,加密该结构,将其作为数据发送,然后在另一端解密,并将其复制到适当的结构。
// Some defines that you may see in the code, all of which are user defined...
#define ADVERTISE_SERVER 0x12345678 // Some unique ID for your advertisement server
#define ACTIVITY_NONE 0x00000000
#define ACTIVITY_LOGON 0x00000001
#define ACTIVITY_LOGOFF 0x00000002
#define ACTIVITY_RUNNING 0x00000004
#define ACTIVITY_IDLE 0x00000005
#define ACTIVITY_SPECIFIC 0x00000006
enum Advertisements {
ADVERTISE_SHUTDOWN,
ADVERTISE_MESSAGE,
ADVERTISE_DEBUG,
ADVERTISE_OVERLAPPED,
ADVERTISE_BROADCAST_IDENTITY,
ADVERTISE_IDENTITY,
ADVERTISE_PARAMETER_CHANGE
};
struct TAdvertiseServerPacket {
UINT uiAdvertisePacketType;
DWORD dwPacketLength;
bool bRequestReply;
UINT uiReplyType;
bool bOverlappedResult;
int iPacketId;
bool bBroadcast;
char GuidHash[35];
BYTE PacketData[1024];
};
struct TAdvertiseIdentity {
TCHAR ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
char szUserName[LEN_APPL_USERNAME + 1];
char szDatabase[MAX_PATH];
char szConfiguration[MAX_PATH];
char szVersion[16];
long nUserId;
char szApplication[MAX_PATH];
char szActivity[33];
UINT uiStartupIndc;
};
struct TAdvertiseMessage {
char MessageFrom[LEN_APPL_USERNAME + 1];
char MessageText[512];
};
struct TAdvertiseItemUpdate {
NMHDR pNMHDR;
long nItemId;
long nItemTypeId;
char szItemName[LEN_ITEM_NAME + 1];
bool bState;
};
struct TAdvertiseItemUpdateEx {
NMHDR pNMHDR;
long nItemId;
bool bState;
bool bBroadcast;
DWORD dwDataSize;
void *lpBuffer;
};
struct TOverlappedAdvertisement {
int iPacketId;
BYTE Data[1020];
};
DWORD WINAPI CAdvertiseServer::Go(void* tptr)
{
CAdvertiseServer *pThis = (CAdvertiseServer*)tptr;
/* Used and reused for Overlapped results, */
DWORD BufferSize = 0;
BYTE *OverlappedBuffer = NULL;
bool bOverlapped = false;
int iOverlappedId = 0;
DWORD BufferPosition = 0;
DWORD BytesRecieved = 0;
TAdvertiseItemUpdateEx *itemex = NULL;
UINT uiPacketNumber = 0;
bool Debug = false;
#ifdef _DEBUG
Debug = true;
#endif
{
DWORD dwDebug = 0;
dwDebug = GetParameter(ADVERTISE_SERVER_DEBUG); // GetParameter is part of the main program used to store running config values.
if(dwDebug > 0)
{
Debug = true;
}
}
WSAData wsaData;
WSAStartup(MAKEWORD(1,1), &wsaData);
ServerSocket = socket(PF_INET, SOCK_DGRAM, 0);
if(ServerSocket == INVALID_SOCKET)
{
CLogging Log("Client.log");
ServerSocket = NULL;
Log.Log("Could not create server advertisement socket: %d", GetLastError());
return -1;
}
sockaddr_in sin;
ZeroMemory(&sin, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(Port);
sin.sin_addr.s_addr = INADDR_ANY;
if(bind(ServerSocket, (sockaddr *)&sin, sizeof(sin)) != 0)
{
CLogging Log("Client.log");
Log.Log("Could not bind server advertisement socket on port: %d Error: %d", Port, GetLastError());
DWORD dwPort = 0;
dwPort = GetParameter(ADVERTISE_SERVER_PORT); // Again, used to set the port number, if one could not be figured out.
if(dwPort > 0)
{
return -1;
}
Port = 36221;
sin.sin_port = htons(Port);
if(bind(ServerSocket, (sockaddr *)&sin, sizeof(sin)) != 0)
{
CLogging Log("Client.log");
Log.Log("Could not bind server advertisement socket on port: %d Error: %d Could not start AdvertiseServer after two attempts. Server failed.", Port, GetLastError());
return -1;
}
}
SECURITY_ATTRIBUTES sa;
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
HANDLE mutex = CreateMutex(NULL, FALSE, "Client.Mutex"); // Used to keep and eye on the main program, if it shuts down, or dies, we need to die.
while (1)
{
TAdvertiseServerPacket ap;
sockaddr_in sin;
int fromlen = sizeof(sin);
fd_set fds;
FD_ZERO(&fds);
FD_SET(ServerSocket, &fds);
timeval tv;
tv.tv_sec = 15;
tv.tv_usec = 0;
int err = select(0, &fds, NULL, NULL, &tv);
if(err == SOCKET_ERROR)
{
CLogging Log("Client.log");
Log.Log("Advertise: Winsock error: %d", WSAGetLastError());
Beep(800, 100);
break;
}
if(err == 0)
{
if(WaitForSingleObject(mutex, 0) != WAIT_OBJECT_0)
{
continue; // Main app is still running
}
else
{
Beep(800, 100); // Main app has died, so exit our listen thread.
break;
}
}
int r = recvfrom(ServerSocket, (char *)&ap, sizeof(ap), 0, (sockaddr *)&sin, &fromlen);
if(r != sizeof(TAdvertiseServerPacket))
{
continue;
}
switch(ap.uiAdvertisePacketType)
{
// This is where you respond to all your various broadcasts, etc.
case ADVERTISE_BROADCAST_IDENTITY:
{
// None of this code is important, however you do it, is up to you.
CDataAccess db(CDataAccess::DA_NONE);
TCHAR ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
ZeroMemory(ComputerName, sizeof(ComputerName));
DWORD len = MAX_COMPUTERNAME_LENGTH;
GetComputerName(ComputerName, &len);
if(pThis->szActivity) {
CAdvertiseServer::AdvertiseIdentity(ComputerName, CDataAccess::GetLoggedInUserName(), CDataAccess::DatabaseConfiguration(), CDataAccess::DatabaseConfiguration(), ACTIVITY_SPECIFIC, pThis->szActivity, false);
} else {
CAdvertiseServer::AdvertiseIdentity(ComputerName, CDataAccess::GetLoggedInUserName(), CDataAccess::DatabaseConfiguration(), CDataAccess::DatabaseConfiguration(), ACTIVITY_RUNNING, NULL, false);
}
}
case ADVERTISE_IDENTITY:
{
TAdvertiseIdentity ident;
memcpy((void*)&ident, (void*)ap.PacketData, ap.dwPacketLength);
Listener::iterator theIterator;
theIterator = pThis->m_Listeners.find(ap.uiAdvertisePacketType);
if(theIterator == pThis->m_Listeners.end())
{
//We got an Identity Broadcast, but we're not listening for them.
continue;
}
{
itemex = new TAdvertiseItemUpdateEx;
ZeroMemory(itemex, sizeof(TAdvertiseItemUpdateEx));
memcpy((void*)&ident, ap.PacketData, ap.dwPacketLength);
itemex->pNMHDR.code = (*theIterator).first;
itemex->pNMHDR.hwndFrom = (*theIterator).second;
itemex->pNMHDR.idFrom = ADVERTISE_SERVER;
itemex->dwDataSize = sizeof(TAdvertiseIdentity);
itemex->lpBuffer = (void*)&ident;
SendMessage((*theIterator).second, WM_NOTIFY, 0, (LPARAM)itemex);
delete itemex;
}
}
case ADVERTISE_SHUTDOWN:
{
TCHAR ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
ZeroMemory(ComputerName, sizeof(ComputerName));
DWORD len = MAX_COMPUTERNAME_LENGTH;
GetComputerName(ComputerName, &len);
CString guid;
guid.Format("%s%s", CDataAccess::DatabaseConfiguration(), ComputerName);
if(stricmp(ap.GuidHash, CDataAccess::HashPassword(guid)) == 0)
{
return 1;
}
}
case ADVERTISE_MESSAGE:
{
TAdvertiseMessage msg;
memcpy((void*)&msg, (void*)ap.PacketData, ap.dwPacketLength);
CString msgtext;
msgtext.Format("Message from: %s\r\n\r\n%s", msg.MessageFrom, msg.MessageText);
::MessageBox(NULL, msgtext, "Broadcast Message", MB_ICONINFORMATION | MB_SYSTEMMODAL);
break;
}
case ADVERTISE_OVERLAPPED:
{
// I left this code in here, as it's a good example of how you can send large amounts of data over a UDP socket, should you need to do it.
BufferPosition = (1020 * ((ap.uiReplyType - 1) - 1));
if(BufferPosition > BufferSize) {
BufferPosition -= 1020;
}
TOverlappedAdvertisement item;
ZeroMemory(&item, sizeof(TOverlappedAdvertisement));
memcpy((void*)&item, (void*)ap.PacketData, ap.dwPacketLength);
if(item.iPacketId == iOverlappedId)
{
DWORD ToCopy = (sizeof(item.Data) > (BufferSize - BytesRecieved) ? BufferSize - BytesRecieved : sizeof(item.Data));
memcpy((void*)&OverlappedBuffer[BufferPosition], (void*)item.Data, ToCopy);
BytesRecieved += ToCopy;
if(BytesRecieved < BufferSize)
{
continue;
}
}
}
default:
{
// What do we do if we get an advertisement we don't know about?
Listener::iterator theIterator;
if(bOverlapped == false)
{
theIterator = pThis->m_Listeners.find(ap.uiAdvertisePacketType);
if(theIterator == pThis->m_Listeners.end())
{
continue;
}
}
// Or it could be a data packet
TCHAR ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
ZeroMemory(ComputerName, sizeof(ComputerName));
DWORD len = MAX_COMPUTERNAME_LENGTH;
GetComputerName(ComputerName, &len);
CString guid;
guid.Format("%s%s", CDataAccess::DatabaseConfiguration(), ComputerName);
bool FromUs = stricmp(ap.GuidHash, CDataAccess::HashPassword(guid)) == 0;
if(((FromUs && Debug) || !FromUs) || ap.bBroadcast)
{
if(ap.bOverlappedResult)
{
if(ap.uiReplyType == 1)
{
itemex = new TAdvertiseItemUpdateEx;
ZeroMemory(itemex, sizeof(TAdvertiseItemUpdateEx));
memcpy(itemex, ap.PacketData, ap.dwPacketLength);
OverlappedBuffer = (BYTE*)malloc(itemex->dwDataSize);
BufferSize = itemex->dwDataSize;
ZeroMemory(OverlappedBuffer, itemex->dwDataSize);
bOverlapped = true;
iOverlappedId = ap.iPacketId;
uiPacketNumber = ap.uiReplyType;
}
continue;
}
if(bOverlapped)
{
itemex->pNMHDR.code = (*theIterator).first;
itemex->pNMHDR.hwndFrom = (*theIterator).second;
itemex->pNMHDR.idFrom = ADVERTISE_SERVER;
itemex->dwDataSize = BufferSize;
itemex->lpBuffer = (void*)OverlappedBuffer;
SendMessage((*theIterator).second, WM_NOTIFY, 0, (LPARAM)itemex);
delete itemex;
free(OverlappedBuffer);
BufferSize = 0;
OverlappedBuffer = NULL;
bOverlapped = false;
iOverlappedId = 0;
BufferPosition = 0;
BytesRecieved = 0;
itemex = NULL;
uiPacketNumber = 0;
break;
}
TAdvertiseItemUpdate *item = new TAdvertiseItemUpdate;
ZeroMemory(item, sizeof(TAdvertiseItemUpdate));
memcpy(item, ap.PacketData, ap.dwPacketLength);
item->pNMHDR.code = (*theIterator).first;
item->pNMHDR.hwndFrom = (*theIterator).second;
item->pNMHDR.idFrom = ADVERTISE_SERVER;
SendMessage((*theIterator).second, WM_NOTIFY, 0, (LPARAM)item);
delete item;
}
break;
}
}
}
try {
ResetEvent(ServerMutex);
CloseHandle(pThis->ServerMutex);
closesocket(ServerSocket);
return 0;
}
catch(...) {
closesocket(ServerSocket);
return -2;
}
}
// Here's a couple of the helper functions that do the sending...
bool CAdvertiseServer::SendAdvertisement(TAdvertiseServerPacket packet)
{
TCHAR ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
ZeroMemory(ComputerName, sizeof(ComputerName));
DWORD len = MAX_COMPUTERNAME_LENGTH;
GetComputerName(ComputerName, &len);
CString guid;
guid.Format("%s%s", CDataAccess::DatabaseConfiguration(), ComputerName);
strcpy(packet.GuidHash, CDataAccess::HashPassword(guid));
bool bRetval = false;
SOCKET s = socket(PF_INET, SOCK_DGRAM, 0);
if(s != INVALID_SOCKET)
{
BOOL tru = TRUE;
setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char *)&tru, sizeof(tru));
sockaddr_in sin;
ZeroMemory(&sin, sizeof(sin));
sin.sin_family = PF_INET;
sin.sin_port = htons(Port);
sin.sin_addr.s_addr = INADDR_BROADCAST;
if(sendto(s, (char *)&packet, sizeof(packet), 0, (sockaddr *)&sin, sizeof(sin)) > 0)
{
bRetval = true;
if(packet.bRequestReply)
{
// Here is where your work comes in, in setting up a reply, or making a TCP connection back to the other client.
}
}
closesocket(s);
}
return bRetval;
}
bool CAdvertiseServer::Advertise(UINT uiAdvertisement, long nItemId, bool bState, void *lpBuffer, DWORD dwDataSize, bool bBroadcast)
{
TAdvertiseServerPacket packet;
ZeroMemory(&packet, sizeof(packet));
TAdvertiseItemUpdateEx item;
ZeroMemory(&item, sizeof(item));
UINT packetnum = 1;
packet.bOverlappedResult = true;
packet.bRequestReply = false;
packet.uiAdvertisePacketType = uiAdvertisement;
packet.dwPacketLength = sizeof(item);
packet.uiReplyType = packetnum;
packet.bBroadcast = bBroadcast;
item.nItemId = nItemId;
item.bState = bState;
item.dwDataSize = dwDataSize;
memcpy((void*)packet.PacketData, (void*)&item, sizeof(item));
packet.iPacketId = GetTickCount();
if(SendAdvertisement(packet))
{
BYTE *TempBuf = new BYTE[dwDataSize];
memcpy(TempBuf, lpBuffer, dwDataSize);
DWORD pos = 0;
DWORD BytesLeft = dwDataSize;
while(BytesLeft)
{
TOverlappedAdvertisement item;
packet.uiAdvertisePacketType = ADVERTISE_OVERLAPPED;
packet.bOverlappedResult = BytesLeft > 1020;
item.iPacketId = packet.iPacketId;
memcpy((void*)item.Data, (void*)&TempBuf[pos], (BytesLeft >= 1020 ? 1020 : BytesLeft));
memcpy((void*)packet.PacketData, (void*)&item, sizeof(item));
packet.dwPacketLength = sizeof(item);
packet.uiReplyType++;
if(SendAdvertisement(packet))
{
if(BytesLeft >= 1020)
{
BytesLeft -= 1020;
pos += 1020;
}
else
{
BytesLeft = 0;
}
}
}
delete TempBuf;
}
return true;
}
void CAdvertiseServer::Shutdown()
{
TAdvertiseServerPacket packet;
packet.uiAdvertisePacketType = ADVERTISE_SHUTDOWN;
SendAdvertisement(packet);
}
答案 6 :(得分:1)
好的 - 所以MQ和那种类型的东西听起来像是过度杀戮。
我对您的应用的理解:
在同一网络上的多台计算机上运行的桌面应用程序 - 拥有自己的数据库,需要相互发现。
为什么不:
1)UDP定期广播/监听“在同一网络上查找其他计算机” - 例如Java:http://java.sun.com/docs/books/tutorial/networking/datagrams/index.html
2)发现后使用SSL套接字进行实际通信:
http://stilius.net/java/java_ssl.php ....
http://www.exampledepot.com/egs/javax.net.ssl/Client.html
答案 7 :(得分:1)
您是否考虑使用Bittorrent类型设置?
使用的通信主体应该为您构建应用程序提供了相当坚实的基础。您只需要两个节点相互了解,然后从那里构建。我使用MonoTorrent运行一个私有(100节点)数据网络,一个RSS提要来宣告需要在哪里(Wordpress的修改版本)和在SSH隧道中执行所有操作。我有一个管理网络的中央服务器,但可以轻松地在我的100个节点中的任何一个上运行。使用动态DNS服务,如果我的服务器出现故障,第一个活动节点会将其自己的跟踪器设置为备份。
您可以使用XML文件作为消息传递方案,或修改Bittorrent网络的传输以将数据包直接传输到您的应用程序。我认为你所寻找的概念是在Bittorrent中。如果网络上没有活动主机,则启动的第一个节点将回收动态DNS条目(DynDNS具有相当容易使用的API)。 (有一个缺点......当两个跟踪器在TTL窗口内启动时,我遇到同步问题)
有很多对SSH tunneling的引用,我只是因为有趣的图表而使用这个。 SSH隧道并不是最有效的方法,但它是一个非常好的替代方法,必须以编程方式将您的通信包装在SSL隧道中。
我知道这些想法有点混乱,我希望它能帮助你指出正确的方向。 PS ...对于一个完全可移植的解决方案,您可以在Java或.Net中运行(在Mono下运行..我的AppleTV甚至运行Mono)。然后,操作系统甚至可以灵活地进行操作。
答案 8 :(得分:0)
听起来您需要分布式缓存或脱机数据库功能 - 取决于您的语言(java / c#/ ...),您可以选择各种选项...
答案 9 :(得分:0)
Amazon和Microsoft都托管了队列,您可以将其用作任意数量的连接,协作应用程序之间的集合点。 亚马逊是商业的,而不是免费的。 Microsoft's目前是免费的,但不能保证是永久的。 它解决了您正面临的问题。为连接的客户端提供发布/订阅模型。
答案 10 :(得分:0)
我可能在这里遗漏了一些东西,但我没有看到你选择的编程语言。 在基于Windows的环境中,使用.Net框架,最好的选择是使用WCF,它允许您通过简单的配置添加安全性/健壮性。 如果你想要一个不是.Net的基于Windows的计算机的解决方案,我会考虑使用MSMQ,这是一个为这些标准构建的通信框架。
答案 11 :(得分:0)
这里有关于带有WCF的P2P的好文章 http://msdn.microsoft.com/en-us/magazine/cc188685.aspx。 它提供代码,但假设.Net3,Wcf,Vista及以上