原始码下载: MutualUdpClientSample_jb51net.rar
在开发与远程设备通讯的系统时,为了提高数据传输的效率,常常会选择UDP这个通讯协议来作为数据传输的媒介。而 .NET framework中所提供的UdpClient对象,可以帮助开发人员依照系统需求开启UDP套接字点,快速建立UDP联机来提供与远程设备通讯的功能。
这个系统架构下当增加一个不同种类的远程设备时,必须要提供一个不同的UDP套接字点,才能用来提供与不同种类远程设备通讯的功能,在远程设备种类越来越多时,系统所需要的UDP套接字点就会依照远程设备种类而增加。
在远程设备种类越来越多的情景中,为了网络管理考虑会限制系统与远程设备通讯时,必须统一使用同一个UDP套接字点来与远程设备通讯,再由封包内容、或是IP地址去判断实际连接的远程设备为何。
复制代码 代码如下:
class Program
{
static void Main(string[] args)
{
// Receiver
UdpClient udpClientA = new UdpClient(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1234));
UdpClient udpClientB = new UdpClient(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1234));
}
}
依照系统需求开发人员可能写出上列的程序代码,直接建立两个UdpClient对象来开启同一个UDP套接字点。这段程序代码内容可以通过编译程序的检查,但在按下执行之后,就会在Visual Studio之中看到SocketException的例外通知,用来告知开发人员同一个套接字点只能被开启一次,使用两个UdpClient来开启同一个套接字点是无法执行的。
有涉略过Design pattern的开发人员,在遇到资源对象只能有一个实体的情景,会想到套用Singleton Pattern来提供资源对象共享的功能。系统中UdpClient对象所开启的UDP套接字点,就是属于这种只能由一个对象所开启的资源,这个情景中在UdpClient对象上套用Singleton Pattern看起来会是个不错的选择。
复制代码 代码如下:
class Program
{
// Singleton
private static UdpClient _udpClientInstance = null;
private static UdpClient UdpClientInstance
{
get
{
if (_udpClientInstance == null)
{
_udpClientInstance = new UdpClient(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1234));
}
return _udpClientInstance;
}
}
// Main
static void Main(string[] args)
{
// Receiver
UdpClient udpClientA = Program.UdpClientInstance;
UdpClient udpClientB = Program.UdpClientInstance;
// Transmiter
UdpClient transmiter = new UdpClient(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9999));
// Send
transmiter.Send(new byte[] { 55 }, 1, new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1234));
// Receive
byte[] packet = null;
IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, IPEndPoint.MinPort);
packet = udpClientA.Receive(ref remoteEndPoint);
Console.WriteLine(string.Format("UdpClientA Receive:{0}", packet[0]));
packet = udpClientB.Receive(ref remoteEndPoint);
Console.WriteLine(string.Format("UdpClientB Receive:{0}", packet[0]));
// End
Console.ReadLine();
// Close
transmiter.Close();
udpClientB.Close();
udpClientA.Close();
}
}
将Singleton Pattern套用在系统内所使用的UdpClient物件上,可以写出上列的程序代码,系统内所使用的UdpClient对象都是取用到系统内一个静态存放的共享UdpClient对象。这段程序代码内容可以通过编译程序的检查,并且在执行时也不会出现SocketException的例外通知,因为套用Singleton Pattern让系统内只会开启UDP套接字点一次。
但进阶一点去思考UdpClient对象的封包接收功能,UdpClient对象中提供Receive方法来等待、接收远程设备传送的数据封包,收到数据封包之后再次执行Receive方法会继续等待、接收下一个数据封包。也就是说一个远程设备传送的数据封包,UdpClient只能透过Receive方法取得一次,在系统内共享同UdpClient对象,没有办法共享Receive方法所取得的数据封包。
观察上列范例的执行结果,可以发现在范例中由transmiter所传送的资料封包,在被UdpClientA透过Receive方法接收之后,UdpClientB无法接收到这个远程传送的数据封包,这也就验证范例中将Singleton Pattern套用在系统内所使用UdpClient上的方式,会发生了无法共享数据封包的问题。
为了提供系统使用同一个UDP套接字点来与远程设备通讯,再由封包内容、或是IP地址去判断实际连接的远程设备为何的功能。笔者设计一个名为MutualUdpClient的解决方案,用来在系统内共享UDP通讯联机并且共享远程设备传送的数据封包。
在MutualUdpClient这个解决方案中,套用先前部落格中所发表的Singleton Pool模式,套用这个模式让系统能够共享UdpClient联机,并且在有系统对象使用UdpClient联机时就开启共享UDP通讯联机,而在所有系统对象都不需要使用UdpClient联机才真正去关闭这个共享的UDP通讯联机。
。
套用Singleton Pool模式解决了共享UdpClient联机的功能,接着在MutualUdpClient这个解决方案中,为了共享远程设备传送的数据封包,在UdpClient与MutualUdpClient之间加入了一个RouteUdpClient对象。
RouteUdpClient对象是一个主动式的对象,在被建立之后会开启一条独立的线程,不断的接收UdpClient所接收到的数据封包,并且将接收到数据封包透过事件的方式通知每个MutualUdpClient,经由这样的流程就可以将远程设备所传送的数据封包,在每个MutualUdpClient之间共享。
而MutualUdpClient对象在收到RouteUdpClient所提供的数据封包时,会先将数据封包暂存在一个队列里,并且在MutualUdpClient对象的Receive方法被呼叫时,再从队列取出数据封包并且回传给呼叫端,用以将远程设备传送的数据封包提供给呼叫端做后续的处理。经由这样的方式,每个系统中所建立的MutualUdpClient对象就可以透过Receive方法取得,每个远程设备传送的数据封包。
*这边要特别一提的是,MutualUdpClient对象不选择事件方式来提供数据封包而采用Receive方法来提供,是为了让使用MutualUdpClient对象的开发人员,在使用对象的时候,能够得到与使用UdpClient一样的开发体验,用以减少开发时的学习时间。
处理完共享UdpClient联机、共享远程设备传送的资料封包之后,还要处理一下传送数据封包到远程设备的功能。在MutualUdpClient之中,对于传送数据封包到远程设备并没有特殊需求,所以直接使用UdpClient的Send功能就可以完成将数据封包传送到远程设备的功能。
复制代码 代码如下:
class Program
{
static void Main(string[] args)
{
// Receiver
MutualUdpClient udpClientA = new MutualUdpClient(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1234));
MutualUdpClient udpClientB = new MutualUdpClient(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1234));
// Transmiter
UdpClient transmiter = new UdpClient(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9999));
// Send
transmiter.Send(new byte[] { 55 }, 1, new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1234));
// Receive
byte[] packet = null;
IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, IPEndPoint.MinPort);
packet = udpClientA.Receive(ref remoteEndPoint);
Console.WriteLine(string.Format("UdpClientA Receive:{0}", packet[0]));
packet = udpClientB.Receive(ref remoteEndPoint);
Console.WriteLine(string.Format("UdpClientB Receive:{0}", packet[0]));
// End
Console.ReadLine();
// Close
transmiter.Close();
udpClientB.Close();
udpClientA.Close();
}
}
上列程序代码示范如何在系统中使用MutualUdpClient对象,在范例中可以看到程序代码中直接建立了两个相同UDP端点的MutualUdpClient对象,并且可以正常的执行不会出现SocketException的例外通知。而远程设备transmiter所传送的数据封包,在被UdpClientA透过Receive方法接收之后,UdpClientB依然可以透过Receive方法接收同一个资料,这也就验证了MutualUdpClient对象提供了共享通讯联机、共享数据封包的功能。
原始码下载: MutualUdpClientSample_jb51net.rar
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 小骆驼-《草原狼2(蓝光CD)》[原抓WAV+CUE]
- 群星《欢迎来到我身边 电影原声专辑》[320K/MP3][105.02MB]
- 群星《欢迎来到我身边 电影原声专辑》[FLAC/分轨][480.9MB]
- 雷婷《梦里蓝天HQⅡ》 2023头版限量编号低速原抓[WAV+CUE][463M]
- 群星《2024好听新歌42》AI调整音效【WAV分轨】
- 王思雨-《思念陪着鸿雁飞》WAV
- 王思雨《喜马拉雅HQ》头版限量编号[WAV+CUE]
- 李健《无时无刻》[WAV+CUE][590M]
- 陈奕迅《酝酿》[WAV分轨][502M]
- 卓依婷《化蝶》2CD[WAV+CUE][1.1G]
- 群星《吉他王(黑胶CD)》[WAV+CUE]
- 齐秦《穿乐(穿越)》[WAV+CUE]
- 发烧珍品《数位CD音响测试-动向效果(九)》【WAV+CUE】
- 邝美云《邝美云精装歌集》[DSF][1.6G]
- 吕方《爱一回伤一回》[WAV+CUE][454M]