65.9K
CodeProject 正在变化。 阅读更多。
Home

可扩展 UDP 客户端服务器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (20投票s)

2014 年 12 月 26 日

CPOL

7分钟阅读

viewsIcon

117300

downloadIcon

8367

UDP服务器应用程序架构。

 

引言

本文介绍了一个可扩展的UDP套接字服务器程序。该程序使用异步套接字方法支持与多个客户端的通信。服务器的设计方式是它将接收和发送功能隔离。在这个程序中,还可以选择在单独的线程中处理您的数据。在任何情况下,服务器都不会停止监听。我构建的骨架只提供了一个架构,您可以在其中使用您的业务逻辑进行开发。

注意:- 这是一个模式,您可以使用它来实现更具可扩展性的UDP服务器。如果您需要更多帮助,请在评论区留言。

背景

我们的客户在物业上安装了实时设备用于安保。该设备能够进行入侵者检测和火灾烟雾检测。全国各地大约有5000台设备。设备包含一张SIM卡用于通信。它通过UDP通信协议使用GPRS。这些设备需要一个通信路由器,为第三方系统提供可理解的信息。

我的要求是将我的应用程序与这些设备集成。由于客户端数量众多,我需要开发一个能够与多个设备并发管理通信的服务器。一旦设备开始发送消息,就需要与设备进行通信。设备仅在以下条件下开始通信:如果任何传感器被激活并触发设备。

一旦设备在任何事件中被触发,设备就会开始向服务器发送信号。服务器的职责是响应这些请求并从设备收集数据。然后,整理这些信息并生成所需的输出。

为了开发这种类型的应用程序,性能非常关键。关键的原因是所有与服务器的通信都只在一个端口上。我们必须非常小心地管理每一个字节,以便它一次能服务尽可能多的设备。设备呼叫的高峰时间是早上8点到9点。每台设备都有一个独立的IP和端口,它只通过服务器的一个端口进行通信。

进程

为了开发这个应用程序,我的首要要求是建立设备和服务器之间的通信。这不是一个难题,我只需要配置服务器使用一个开放的IP与设备通信。从设备检索到的数据非常关键,需要进行CRC校验。我们需要在每个设备的跟踪日志中维护数据信息。

基于这个需求,我的第一个想法是我们能支持多少并发设备。我对通信模式进行了研究。首先我使用了Winsock控件。使用Winsock的原因是我能够向设备发送响应。Winsock的问题是它是一个非常笨重的对象,在超时的情况下,连接会断开,Winsock会处于空闲状态,不会响应设备。为了解决这个问题,我发现我可以销毁Winsock然后重新创建它。这确实有效,但时间限制依然存在。

然后我研究了 System.Net.Sockets.Socket 类。Socket 与设备配合良好。好消息是 Socket 支持异步通信。UDP 的一个优点是它从不阻塞发送,并为我们在服务器上节省了多个通信端口。Socket 的 BeginReceive() 和 BeginSendTo() 方法用于管理与不同设备的异步调用。

由于我们需要分多个块获取数据,所以对同一设备会有多个调用。多个设备同时调用,因此我们需要跟踪每个请求并回复给正确的设备。如果数据未在定义的时间内到达,我们需要实现重试策略。我在这个项目中使用了四个组件。

  • GUI将显示应用程序中正在进行的事件。
  • 数据收集器类保存用于处理的数据。
  • 数据处理类处理数据。
  • 实用函数。

这种方法使我能够隔离发送和接收信息,并提高了服务器的响应时间。

环境图

设备通过SIM卡与我们的会话管理器通信。一旦数据被接收到会话管理器中,数据就会传递给处理单元。处理单元包含数据收集器和数据处理模块,它们具有处理数据的功能。会话管理器是一个智能单元,能够管理传入和传出数据的流量。

 

使用代码

项目分为四个部分。

  • UDP.Server
  • UDP.Server.Utils
  • UDP.ServerComponents
  • UDP.ServerCore

以下是详细信息

  • UDP.Server:这是应用程序的起点。UDP.Server 库的工作只是视图、流量和流量相关事件的一部分。
  • UDP.ServerCore:这是项目的核心。以下功能应存在于此模块中
    • 管理UDP数据的发送和接收。
    • 作为数据包到正确数据收集器容器的路由器。
    • 数据收集后,将其传递给适当的处理模块。
    • 数据处理完成后清理数据对象。
    • 向服务器UI发送通知。
  • UDP.ServerComponents:安全传递组件包含您需要处理的数据结构。就像实体类一样,您可以在此类型中创建结构、类或枚举。这些组件中不应包含任何业务逻辑。它们只应包含结构。例如,电子邮件结构。如果您想发送电子邮件,请创建一个电子邮件结构类。创建该类的实例并在需要的地方使用它。
  • UDP.Server.Utils:正如其名,我将此项目用于所有实用工具。对于FTP连接、CRC计算和其他常用功能,我将其放在此项目中。

在这个项目中,您只会得到项目的骨架,您需要根据您的要求实现您的代码。

SessionManager 类是程序的核心。SessionManager 的职责是

  • 启动和停止服务器。
  • 在UDP上接收和发送数据。
  • 启动处理线程来处理数据。
  • 确定数据是否已传递到正确的数据收集器。

`SessionManager` 类用于管理通信相关的事项。当 `SessionManagers` 对象被实例化时,`Socket` 会被初始化。

private Socket _UdpSocket;
 
/// <summary>
/// Initialize socket 
/// </summary>
private void InitializeUdpSocket()
{ 
    _UdpSocket = new Socket(AddressFamily.InterNetwork,
    SocketType.Dgram, ProtocolType.Udp);
}

以下代码用于启动监听器。此函数开始接收UDP流量。UpdateUI 是回调方法,它将通知GUI我们已接收到的数据。

public delegate void UpdateUIDelegate(object value);
  public event UpdateUIDelegate UpdateUI;
 
 /// <summary>
/// Start listener
/// </summary>
public void StartListener()
{        
    //Assign the any IP of the machine and listen on port number 52200
    _ReceiveByteData = new byte[1072];
    _UdpSocket.Bind(new IPEndPoint(IPAddress.Any, Int32.Parse(
       SPConfigVal.GetConfigValue(SecurePassConstants.ServerPort))));
    EndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0);
    //Start receiving data
    _UdpSocket.BeginReceiveFrom(_ReceiveByteData, 0, _ReceiveByteData.Length,
    SocketFlags.None, ref newClientEP, DoReceiveFrom, _UdpSocket);
    if (UpdateUI != null)
    {
         UpdateUI(CommonFunctions.GetDateTime() + " Server Stated....");
    }
}

DoReceivedForm 方法作为异步方法使用,并从多个设备接收流量。一旦接收到数据,它会将数据传输到收集容器。数据收集后,该类返回下一个请求的数据并尽快发送请求。这种快速处理的原因是我们需要让服务器随时准备好接收新的传入请求。

/// <summary>
/// socket call back function to receive message from clients
/// </summary>
/// <param name="ar"></param>
private void DoReceiveFrom(IAsyncResult ar)
{
    try
    {
        Socket recvSock = (Socket)ar.AsyncState;
        EndPoint clientEP = new IPEndPoint(IPAddress.Any, 0);
        int msgLen = recvSock.EndReceiveFrom(ar, ref clientEP);
        byte[] objbuffer = new byte[msgLen];
        Array.Copy(_ReceiveByteData, objbuffer, msgLen);
        _ReceiveByteData = null;
       //notification of the data on UI
        if (UpdateUI != null)
        {
            UpdateUI(objbuffer);
        }
         _SendByteData = ProcessDeviceData(objbuffer, clientEP);
         SendData(clientEP, _SendByteData);
     }

设备接受的请求是一个16字节的头部。它包含我的应用程序所需的指令。processDeviceData 函数返回下一个信息的请求。

应用程序的组件ISession是数据收集器根类。IsessionStatusCallIsessionVideoWakeupStatusCallVideowakeup调用的子组件。IDeviceDataHandler是数据处理类的根。IStatusChangeDeviceDataHandlerIVideoWakeupDeviceDataHandler是视频和状态的子接口。如果您查看代码,您会发现这些类的使用。通过这些策略模式,我们可以实现设备流量通信。

interface ISession
interface ISessionStatusCall : ISession
interface ISessionVideoWakeup:ISession
interface IStatusChangeDeviceDataHandler : IDeviceDataHandler
interface IVideoWakeupDeviceDataHandler : IDeviceDataHandler

Socket.IOControl 属性在性能方面扮演着非常重要的角色。

当路由器接收到一个需要分片但IP头中“不分片”(DF)标志已打开的数据报时,就会发生ICMP不可达错误。此错误可被需要确定到目的路径中最小MTU的程序使用——这称为“路径MTU发现”机制。

以下是避免此问题的代码。

public const int SIO_UDP_CONNRESET = -1744830452;

然后将低级I/O控制设置为忽略这些消息

 

var client = new UdpClient(endpoint);
client.Client.IOControl(IOControlCode)SIO_UDP_CONNRESET,
new byte[] {0, 0, 0, 0 },null);

 

关注点

在编写此代码时,我了解到您可以编写一个能够在一个端口上处理多个请求的 UDP 服务器。使用 UDP 的好处是它是无状态的,因此无需管理确认信息。数据跟踪日志功能也用于在处理后写入接收到的数据。关于 UDP 的有趣之处在于,有时您需要从设备请求两次数据,而 UDP 会给您两个数据包:一个是我们之前请求的,另一个是您之后请求的。

© . All rights reserved.