我需要你的帮助。
我有一个 Xamarin 应用程序,它使用 System.Net.UdpClient
在网络上发送多播,但它看起来很不稳定,并且在我无法控制的后台线程中崩溃了很多。所以我为什么不去低级。除了在套接字上启用广播标志的部分之外,一切似乎都很好。在 Objective-C 中,你可以这样做: setsockopt(CFSocketGetNative(cfSocket), SOL_SOCKET, SO_BROADCAST, (void *)&yes, sizeof(yes));
通过查看单声道源,您将看到 Socket 类有一个EnableBroadcast
: https://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。
DR:你认为有可能在旧的CFNetwork.framework中完成的CFSocket(虽然Xamarin.iOS(上启用广播标志吗?
我已经搞定了。我缺少像"本机"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等,它们被定义为源代码中的内部类。