Windows 下 C++ 和 C# 中的套接字无法正常工作

Posted

技术标签:

【中文标题】Windows 下 C++ 和 C# 中的套接字无法正常工作【英文标题】:Sockets in C++ and C# under Windows not working properly 【发布时间】:2012-01-10 13:12:31 【问题描述】:

我正在使用 UDP 套接字将 C++ 应用程序与 C# 应用程序通信。

在 C# -> C++ 方向上,一切似乎都运行良好,但反过来却让我发疯。

通信确实有效,但消息在 C# 应用程序中变得很晚(例如延迟 2 秒),即使它们每帧都发送(它是一个 3D 应用程序),并且接收代码每 10 毫秒执行一次。

我需要实时,所以这是一个非常痛苦的问题。您认为这可能与丢包有关吗?那他们为什么不迷路呢?

编辑:

用于同步数据的 C# 应用代码:

    public void RecibeDatos()
    
       if (MessageReceived && U != null && S != null)
       
          MessageReceived = false;
          //Console.WriteLine("listening for messages");
          U.BeginReceive(ReceiveCallback, S);
       
    

    public void ReceiveCallback(IAsyncResult ar)
    
        UdpClient u = ((UdpState)(ar.AsyncState)).U;
        IPEndPoint e = ((UdpState)(ar.AsyncState)).E;

        receivedBytes = u.EndReceive(ar, ref e);
        //int currentProtocol = (int) numero;
        //ResetSignal = reset > 0;

        //Console.WriteLine("Received: " + currentProtocol);
        MessageReceived = true;
    

发送数据的C++代码:

    float indiceFloat[1];
    indiceFloat[0] = indice_protocolo_actual;
    sender->setBuffer((void *)indiceFloat, sizeof(indiceFloat));
    sender->sync();

J_Enviar(发送者)类的同步方法:

    void J_Enviar::sync( void )
    
        if(!_initialized) init();

        if( _buffer == 0L )
        
            fprintf( stderr, "Broadcaster::sync() - No buffer\n" );
            return;
        

    #if defined (WIN32) && !defined(__CYGWIN__)
        unsigned int size = sizeof( SOCKADDR_IN );
        sendto( _so, (const char *)_buffer, _buffer_size, 0, (struct sockaddr *)&saddr, size );
        int err = WSAGetLastError ();
        if (err!=0) 
            fprintf( stderr, "Broadcaster::sync() - error %d\n",err );
    #else
        unsigned int size = sizeof( struct sockaddr_in );
        sendto( _so, (const void *)_buffer, _buffer_size, 0, (struct sockaddr *)&saddr, size );
    #endif

    

为接收 C# 端提供完整的 SocketManager 代码:

using System;
using System.Net;
using System.Net.Sockets;

namespace WpfApplication1

public class SocketManager

    private static SocketManager _instance = null;
    static readonly object Padlock = new object();

    private IPEndPoint E;
    private UdpClient U;
    private UdpState S;

    private byte[] receivedBytes;


    public static bool MessageReceived = true;

    private SocketManager()
    

    

    public byte[] ReceivedBytes
    
        get  return receivedBytes; 
    

    public static SocketManager Instance
    
        get
        
            lock(Padlock)
            
                return _instance ?? (_instance = new SocketManager());
            
        
    

    public void CreateReceivingSocket(IPAddress a, int puerto)
    

        if(E==null || (E.Address != a && E.Port != puerto))
        
            E = new IPEndPoint(a, puerto);
            U = new UdpClient(puerto);
            S = new UdpState  E = E, U = U ;
        

    


    public void ReceiveCallback(IAsyncResult ar)
    
        UdpClient u = ((UdpState)(ar.AsyncState)).U;
        IPEndPoint e = ((UdpState)(ar.AsyncState)).E;

        receivedBytes = u.EndReceive(ar, ref e);
        //int currentProtocol = (int) numero;
        //ResetSignal = reset > 0;

        //Console.WriteLine("Received: " + currentProtocol);
        MessageReceived = true;
    

    public void RecibeDatos()
    
        if (MessageReceived && U != null && S != null)
        
            MessageReceived = false;
            //Console.WriteLine("listening for messages");
            U.BeginReceive(ReceiveCallback, S);
        
    

    public void CloseConnection()
    
        if (E != null)
        
            E.Port = 5502;
            E = null;
        
        if (U != null)
            U.Close();

    


public class UdpState

    public IPEndPoint E;
    public UdpClient U;


这是我的 dispatchertimerclick,它使程序每 10 毫秒接收一次:

        _dispatcherTimer.Tick += DispatcherTimerTick;
        _dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 1);
        _dispatcherTimer.Start();

    private void DispatcherTimerTick(object sender, EventArgs e)
    
        _exp1Class.sendData();
        _sm.RecibeDatos();
        byte[] recibidos = _sm.ReceivedBytes;
        if (recibidos != null)
        
            float numero = BitConverter.ToSingle(recibidos, 0);

            _exp1Class.CurrentProtocol = (int) numero;
        
    

【问题讨论】:

你做错了什么,你没有提供任何细节/代码来找出问题可能与什么有关。 使用Wireshark 之类的程序检查原始包。这将帮助您确定实际延迟可能在哪里。 您能否提供用于在 C# 端接收的代码?您还可以从运行 Wireshark 跟踪中受益,以证明数据包级别发生了什么。 已编辑以提供代码。希望这就足够了,如果不是,我可以发布更多代码。现在将检查 Wireshank :) 好的,Wireshark 证明数据正在正确发送,没有延迟。是接收端工作不正常,那么…… 【参考方案1】:

我看不到您何时开始您的第一个 BeginReceive。 (啊,我认为它是从您的第一个计时器滴答声开始的?)它应该在您准备好接收数据后立即启动。其次,您的 ReceiveCallback 应该获取接收到的数据并将其放入某种队列中,然后立即再次调用 BeginReceive。否则,您将等待下一个数据帧的到达,直到前一个数据帧被消耗。最后,注意线程问题,因为线程计时器和 UDP 回调都将在与应用程序主线程不同的线程上运行。

您的代码完全有效的唯一原因是因为您甚至在收到任何回调之前就预先初始化了 MessageReceived = true。当第一个滴答发生时,对 RecibeDatos 的调用会调用 BeginReceive,因为该 bool 设置为 true。

将 BeginReceive 想象为“当您有来自网络的一些数据时给我回电”。您无需使用计时器轮询网络。 (如果您的应用程序需要,您可以选择通过计时器使用该数据,但我们暂时将其放在一边)。

以下是粗略的步骤:

首先,在启动(或启用、运行等)时,您应该调用 BeginReceive。现在,您将在数据到达时收到通知。

其次,当回调发生时,您使用 EndReceive 完成数据字节的读取。该数据通常会被缓冲或以其他方式分派。然后,您应该再次调用 BeginReceive(在回调中),以便在下一组数据可用时,您将再次收到通知。它变成了一种异步循环。

问题是如何处理您正在阅读的数据。您可能会考虑将数据放入队列中,然后让 Timer 将这些数据帧从队列中弹出以进行处理。请注意,数据可能会在您的 Ticks 之间多次到达。

【讨论】:

我在每个计时器滴答声中调用函数 RecibeDatos()。你的意思是我应该从回调本身调用 BeginReceive? @kelmer 你还需要澄清吗? 对不起,我出城几天了!感谢您的回答。当您说队列时,您的意思是像使用堆栈或其他东西来存储数据,然后以严格的顺序弹出它们,例如?这不会使程序与正在发送的数据失去同步吗? 嗯,队列将是 FIFO(先进先出),这意味着您将按顺序处理它们。如果您可以在回调中足够快地处理 UDP 数据,则不需要使用队列。如果您进一步详细说明您尝试使用的“协议”,这可能会有所帮助。您应该知道 UDP 是“不可靠的”,因此希望您考虑到其中一些可能会丢失、乱序或重复。

以上是关于Windows 下 C++ 和 C# 中的套接字无法正常工作的主要内容,如果未能解决你的问题,请参考以下文章

返回一个二维数组(在 C# 和 C++ 中)

将数据从 C++ 发送到 C# [重复]

如何在 C# 中的同一线程上收听标准输入和套接字?

C# 中的无符号字符使用哪种数据类型?

通过 C++ 调用 C# 服务

交叉编译的 c# 套接字程序,适用于 win,但不适用于 mac