0

我需要你的帮助。

我有这个 Xamarin 应用程序,它在网络上使用 发送多播System.Net.UdpClient,但它似乎很不稳定,并且在我无法控制的后台线程中崩溃了很多。所以我虽然为什么不去低级。除了在套接字上启用广播标志的部分外,一切似乎都很好。在 Objective-C 中,你可以这样做: setsockopt(CFSocketGetNative(cfSocket), SOL_SOCKET, SO_BROADCAST, (void *)&yes, sizeof(yes));

通过查看单声道源,您会看到 Socket 类具有EnableBroadcasthttps ://github.com/mono/mono/blob/463cf3b5c1590df58fef43577b9a3273d5eece3d/mcs/class/System/System.Net.Sockets/Socket.cs#L195

这启发了这个(非常实验性的)代码:

    public class NetworkHelper
    {
        [DllImport("libc", SetLastError = true)]
        protected unsafe static extern int setsockopt(int s, int level, int optname, void* optval, uint optlen);

        public unsafe static void DoMulticast()
        {
            var socket = new CFSocket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            var optval = 1;
            var res = setsockopt(socket.Handle.ToInt32(), (int)SocketOptionLevel.Socket, (int)SocketOptionName.Broadcast, &optval, sizeof(int));

            if (res < 0)
            {
                return;
            }
        }
    }

它运行,但无论我将 setsockopt 签名更改为什么,它都会返回 -1。

TL;DR 你认为可以在旧的 CFNetwork.framework 中启用 CFSocket(虽然是 Xamarin.iOS)上的广播标志吗?

4

1 回答 1

0

我已经搞定了。我在“本机”Obj-C 代码中缺少CFSocketNativeHandle类似的东西,我猜它会将 CFNetwork->CFSocket 转换为本机文件描述符指针。

这是我的完整实现(C# 扩展):

public static class CFSocketExtensions
{
    [DllImport(Constants.CoreFoundationLibrary)]
    extern static nint CFSocketSendData(IntPtr handle, IntPtr address, IntPtr data, double timeout);

    [DllImport("libc", SetLastError = true)]
    extern static int setsockopt(CFSocketNativeHandle s, int level, int optname, IntPtr optval, int optlen);

    [DllImport(Constants.CoreFoundationLibrary)]
    extern static CFSocketNativeHandle CFSocketGetNative(IntPtr handle);

    public static void SendData(this CFSocket socket, CFSocketAddress address, byte[] data, double timeout)
    {
        using (var buffer = new CFDataBuffer(data))
        {
            var error = (CFSocketError)(long)CFSocketSendData(socket.Handle, address.Handle, buffer.Handle, timeout);
            if (error != CFSocketError.Success)
                throw new CFSocketException(error);
        }
    }

    public static bool EnableBroadcast(this CFSocket socket, bool enable = true)
    {
        int size = Marshal.SizeOf<int>();
        IntPtr pBool = Marshal.AllocHGlobal(size);
        Marshal.WriteInt32(pBool, 0, enable ? 1 : 0); // last parameter 0 (FALSE), 1 (TRUE)

        var res = setsockopt(CFSocketGetNative(socket.Handle), (int)SocketOptionLevel.Socket, (int)SocketOptionName.Broadcast, pBool, size);

        Marshal.FreeHGlobal(pBool);

        return res > -1;
    }
}

public class CFSocketAddress : CFDataBuffer
{
    public CFSocketAddress(IPEndPoint endpoint)
        : base(CreateData(endpoint))
    {
    }

    internal static IPEndPoint EndPointFromAddressPtr(IntPtr address)
    {
        using (var buffer = new CFDataBuffer(address))
        {
            if (buffer[1] == 30)
            { // AF_INET6
                int port = (buffer[2] << 8) + buffer[3];
                var bytes = new byte[16];
                Buffer.BlockCopy(buffer.Data, 8, bytes, 0, 16);
                return new IPEndPoint(new IPAddress(bytes), port);
            }
            else if (buffer[1] == 2)
            { // AF_INET
                int port = (buffer[2] << 8) + buffer[3];
                var bytes = new byte[4];
                Buffer.BlockCopy(buffer.Data, 4, bytes, 0, 4);
                return new IPEndPoint(new IPAddress(bytes), port);
            }
            else
            {
                throw new ArgumentException();
            }
        }
    }

    static byte[] CreateData(IPEndPoint endpoint)
    {
        if (endpoint == null)
            throw new ArgumentNullException("endpoint");

        if (endpoint.AddressFamily == AddressFamily.InterNetwork)
        {
            var buffer = new byte[16];
            buffer[0] = 16;
            buffer[1] = 2; // AF_INET
            buffer[2] = (byte)(endpoint.Port >> 8);
            buffer[3] = (byte)(endpoint.Port & 0xff);
            Buffer.BlockCopy(endpoint.Address.GetAddressBytes(), 0, buffer, 4, 4);
            return buffer;
        }
        else if (endpoint.AddressFamily == AddressFamily.InterNetworkV6)
        {
            var buffer = new byte[28];
            buffer[0] = 32;
            buffer[1] = 30; // AF_INET6
            buffer[2] = (byte)(endpoint.Port >> 8);
            buffer[3] = (byte)(endpoint.Port & 0xff);
            Buffer.BlockCopy(endpoint.Address.GetAddressBytes(), 0, buffer, 8, 16);
            return buffer;
        }
        else
        {
            throw new ArgumentException();
        }
    }
}

public static class CFObject
{
    [DllImport(Constants.CoreFoundationLibrary)]
    internal extern static void CFRelease(IntPtr obj);

    [DllImport(Constants.CoreFoundationLibrary)]
    internal extern static IntPtr CFRetain(IntPtr obj);
}

public class CFData : INativeObject, IDisposable
{
    internal IntPtr handle;

    public CFData(IntPtr handle)
        : this(handle, false)
    {
    }

    public CFData(IntPtr handle, bool owns)
    {
        if (!owns)
            CFObject.CFRetain(handle);
        this.handle = handle;
    }

    ~CFData()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    public IntPtr Handle
    {
        get { return handle; }
    }

    [DllImport(Constants.CoreFoundationLibrary, EntryPoint = "CFDataGetTypeID")]
    public extern static /* CFTypeID */ nint GetTypeID();

    protected virtual void Dispose(bool disposing)
    {
        if (handle != IntPtr.Zero)
        {
            CFObject.CFRelease(handle);
            handle = IntPtr.Zero;
        }
    }

    public nint Length
    {
        get { return CFDataGetLength(handle); }
    }

    [DllImport(Constants.CoreFoundationLibrary)]
    extern static /* CFIndex */ nint CFDataGetLength(/* CFDataRef */ IntPtr theData);

    public byte[] GetBuffer()
    {
        var buffer = new byte[Length];
        var ptr = CFDataGetBytePtr(handle);
        Marshal.Copy(ptr, buffer, 0, buffer.Length);
        return buffer;
    }

    [DllImport(Constants.CoreFoundationLibrary)]
    extern static /* UInt8* */ IntPtr CFDataGetBytePtr(/* CFDataRef */ IntPtr theData);

    /*
     * Exposes a read-only pointer to the underlying storage.
     */
    public IntPtr Bytes
    {
        get { return CFDataGetBytePtr(handle); }
    }

    [DllImport(Constants.CoreFoundationLibrary)]
    extern static /* CFDataRef */ IntPtr CFDataCreate(/* CFAllocatorRef */ IntPtr allocator, /* UInt8* */ IntPtr bytes, /* CFIndex */ nint length);

    public static CFData FromData(IntPtr buffer, nint length)
    {
        return new CFData(CFDataCreate(IntPtr.Zero, buffer, length), true);
    }

    [DllImport(Constants.CoreFoundationLibrary)]
    extern static /* CFDataRef */ IntPtr CFDataCreateCopy(/* CFAllocatorRef */ IntPtr allocator, /* CFDataRef */ IntPtr theData);

    public CFData Copy()
    {
        return new CFData(CFDataCreateCopy(IntPtr.Zero, Handle), true);
    }
}

public class CFDataBuffer : IDisposable
{
    byte[] buffer;
    CFData data;

    public CFDataBuffer(byte[] buffer)
    {
        this.buffer = buffer;

        /*
         * Copy the buffer to allow the native side to take ownership.
         */
        //fixed (byte* ptr = buffer)
        //  data = CFData.FromData((IntPtr)ptr, buffer.Length);

        GCHandle pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        data = CFData.FromData(pinnedBuffer.AddrOfPinnedObject(), buffer.Length);
        pinnedBuffer.Free();
    }

    public CFDataBuffer(IntPtr ptr)
    {
        data = new CFData(ptr, false);
        buffer = data.GetBuffer();
    }

    ~CFDataBuffer()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    public IntPtr Handle
    {
        get { return data.Handle; }
    }

    public byte[] Data
    {
        get { return buffer; }
    }

    public byte this[int idx]
    {
        get { return buffer[idx]; }
    }

    protected virtual void Dispose(bool disposing)
    {
        if (data != null)
        {
            data.Dispose();
            data = null;
        }
    }
}

此实现的唯一缺点是 CFSocketAddress、CFDataBuffer 等,它们在源代码中被定义为内部类。

于 2017-04-03T12:41:10.300 回答