我有一个用 C# 编写的 BBS 门,我想在 Linux 上工作。.NET 框架的 Socket 类不支持打开现有的套接字句柄,因此需要为 Windows 和 Linux 实现不同的解决方法。
对于 Linux,我查看了 Socket.cs 文件,发现这发生在 DuplicateAndClose() 中:
var si = new SocketInformation ();
si.Options =
(islistening ? SocketInformationOptions.Listening : 0) |
(connected ? SocketInformationOptions.Connected : 0) |
(blocking ? 0 : SocketInformationOptions.NonBlocking) |
(useoverlappedIO ? SocketInformationOptions.UseOnlyOverlappedIO : 0);
si.ProtocolInformation = Mono.DataConverter.Pack ("iiiil",
(int)address_family,
(int)socket_type,
(int)protocol_type,
isbound ? 1 : 0,
(long)socket);
socket = (IntPtr) (-1);
return si;
由于我无法从 Windows 访问 Mono.DataConverter,因此我也查看了它的源代码并想出了这个:
SocketInformation SI = new SocketInformation();
SI.Options = SocketInformationOptions.Connected;
SI.ProtocolInformation = new byte[24];
Int32 AF = (Int32)AddressFamily.InterNetwork;
Int32 ST = (Int32)SocketType.Stream;
Int32 PT = (Int32)ProtocolType.Tcp;
Int32 Bound = 0;
Int64 Socket = (Int64)ASocketHandle;
unsafe
{
fixed (byte* target = &SI.ProtocolInformation[0])
{
uint* source = (uint*)⁡
*((uint*)target) = *source;
}
fixed (byte* target = &SI.ProtocolInformation[4])
{
uint* source = (uint*)&ST;
*((uint*)target) = *source;
}
fixed (byte* target = &SI.ProtocolInformation[8])
{
uint* source = (uint*)&PT;
*((uint*)target) = *source;
}
fixed (byte* target = &SI.ProtocolInformation[12])
{
uint* source = (uint*)&Bound;
*((uint*)target) = *source;
}
fixed (byte* target = &SI.ProtocolInformation[16])
{
long* source = (long*)&Socket;
*((long*)target) = *source;
}
}
所以现在我已经填充了 SocketInformation,我应该能够做到这一点:
Socket S = new Socket(SI);
S.Send(new byte[] { 65, 66, 67, 68 });
远程用户应该看到 ABCD。但是,对 Send 的调用会引发异常,并显示消息“描述符不是套接字”。
所以起初我认为我想做的事情是不可能的,但后来我尝试调用 send():
Socket S = new Socket(SI);
send(S.Handle, new byte[] { 65, 66, 67, 68 }, 4, SocketFlags.None);
其中 send() 声明为:
[DllImport("libc")]
private extern static int send(IntPtr sock, byte[] buf, int count, SocketFlags flags);
它工作得很好!
因此,如果对 send() 的 pinvoked 调用对描述符来说很好,那么我做错了什么让托管 S.Send() 调用失败说它不是套接字描述符?我假设它与我填充 SocketInformation 的方式有关,但如果没有别的,句柄似乎已正确填充,因为我可以将它与 pinvoked send() 一起使用...
编辑:进一步深入研究 Mono 源代码后,我可能已经找到了问题所在:
S.Send() 将调用 Send_internal(),而后者又会调用 _wapi_send()。这是最终调用 send() 的地方,但在此之前:
if (_wapi_handle_type (handle) != WAPI_HANDLE_SOCKET) {
WSASetLastError (WSAENOTSOCK);
return(SOCKET_ERROR);
}
所以我猜 _wapi_handle_type 不会返回 WAPI_HANDLE_SOCKET (可能是因为句柄是传入的,而不是由进程创建的,所以它不知道句柄类型),所以这就是我神秘的“描述符不是套接字“错误来自。
猜猜这是一个很长的镜头,但是有其他人遇到过这个问题吗?除了使用套接字 i/o 完全不受管理之外,是否有解决方案?