从来不怎么看EP(Enhancement Proposal)的我昨天突然网上冲浪看到了这篇JEP 发现还有UDS这个新奇玩意 虽说是*n?x的产物 在Windows 10貌似也有集成
UDS?
本机通信使用常规的本地回环地址(127.0.0.1,localhost) 需要走网络协议栈 需要封包、拆包、计算校验和之类的 而UDS基于文件系统 免去了一些开销
Pathname Sockets
在本机路径下指定一个可读写的socket文件(一种标识符) 供多个进程间通信
https://man7.org/linux/man-pages/man7/unix.7.html#top_of_page:~:text=of sun_path.-,Pathname sockets
Unnamed / Abstract Sockets
这里在文件系统中无法找到 以设置空字节开头的socket名称在抽象命名空间中
开始测试
既然都说了开销相对更小 那就得实测看下是不是真的那么快
UDSNTCP.cs
public class UDSNTCP
{
private byte[] _testData1;
private byte[] _testData2;
[Params(1024, 4096, 8192, 16384)]
public int DataSize;
private UnixDomainSocketEndPoint udsEndpoint;
private IPEndPoint ipEndpoint;
private Socket _udsListener, _udsServer1, _udsServer2, _udsClient1, _udsClient2;
private Socket _tcpServer, _tcpClient;
[GlobalSetup]
public void SetUp()
{
_testData1 = Encoding.UTF8.GetBytes(new string('T', DataSize));
_testData2 = Encoding.UTF8.GetBytes(new string('N', DataSize));
var socketDir = Path.Combine(Environment.CurrentDirectory, "tmp");
if (!Directory.Exists(socketDir)) Directory.CreateDirectory(socketDir);
var udsPathStr = Path.Combine(socketDir, "tmpSocket");
udsEndpoint = new UnixDomainSocketEndPoint(udsPathStr);
ipEndpoint = new IPEndPoint(IPAddress.Loopback, 5000);
#region uds setup
_udsListener = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
_udsListener.Bind(udsEndpoint);
_udsListener.Listen(2);
_udsClient1 = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
_udsClient1.Connect(udsEndpoint);
_udsClient2 = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
_udsClient2.Connect(udsEndpoint);
_udsServer1 = _udsListener.Accept();
_udsServer2 = _udsListener.Accept();
#endregion
#region tcpip region
_tcpServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_tcpServer.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, new LingerOption(false, 0));
_tcpServer.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
_tcpServer.Bind(ipEndpoint);
_tcpServer.Listen(1);
_tcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_tcpClient.Connect(ipEndpoint);
_tcpServer = _tcpServer.Accept();
#endregion
}
[GlobalCleanup]
public void CleanUp()
{
_udsClient1.Close();
_udsClient2.Close();
_udsListener.Close();
_tcpClient.Close();
_tcpServer.Close();
}
[Benchmark]
public void UDSTest()
{
_udsClient1.Send(_testData1, SocketFlags.None);
_udsClient2.Send(_testData2, SocketFlags.None);
byte[] buftd1 = new byte[_testData1.Length];
_udsServer1.Receive(buftd1);
byte[] buftd2 = new byte[_testData2.Length];
_udsServer2.Receive(buftd2);
}
[Benchmark]
public void TCPLoopbackTest()
{
_tcpClient.Send(_testData1, SocketFlags.None);
byte[] buf = new byte[_testData1.Length];
_tcpServer.Receive(buf);
}
}
Program.cs
BenchmarkRunner.Run<UDSNTCP>();
测试结果
// * Summary *
BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.5796/22H2/2022Update)
AMD Ryzen 5 5600, 1 CPU, 12 logical and 6 physical cores
.NET SDK 9.0.203
[Host] : .NET 8.0.15 (8.0.1525.16413), X64 RyuJIT AVX2 [AttachedDebugger]
DefaultJob : .NET 8.0.15 (8.0.1525.16413), X64 RyuJIT AVX2
Method | DataSize | Mean | Error | StdDev |
---|---|---|---|---|
UDSTest | 1024 | 3.535 us | 0.0245 us | 0.0229 us |
TCPLoopbackTest | 1024 | 3.960 us | 0.0159 us | 0.0149 us |
UDSTest | 4096 | 3.953 us | 0.0248 us | 0.0219 us |
TCPLoopbackTest | 4096 | 4.278 us | 0.0130 us | 0.0115 us |
UDSTest | 8192 | 4.513 us | 0.0387 us | 0.0362 us |
TCPLoopbackTest | 8192 | 4.740 us | 0.0153 us | 0.0128 us |
UDSTest | 16384 | 5.441 us | 0.0765 us | 0.0715 us |
TCPLoopbackTest | 16384 | 5.609 us | 0.0358 us | 0.0299 us |
确实 即便是在两个客户端发送数据时速度也略快于TCPIP本地回环的方式
路径长度限制?
如果路径字节过长的情况下会直接抛异常
private UnixDomainSocketEndPoint(string path, string? boundFileName)
{
ArgumentNullException.ThrowIfNull(path);
BoundFileName = boundFileName;
// Pathname socket addresses should be null-terminated.
// Linux abstract socket addresses start with a zero byte, they must not be null-terminated.
bool isAbstract = IsAbstract(path);
int bufferLength = Encoding.UTF8.GetByteCount(path);
if (!isAbstract)
{
// for null terminator
bufferLength++;
}
if (path.Length == 0 || bufferLength > s_nativePathLength)
{
throw new ArgumentOutOfRangeException(
nameof(path), path,
SR.Format(SR.ArgumentOutOfRange_PathLengthInvalid, path, s_nativePathLength));
}
// ... So On
}
#pragma warning disable CA1802 // on Unix these need to be static readonly rather than const, so we do the same on Windows for consistency
private static readonly int s_nativePathOffset = 2; // sizeof(sun_family)
private static readonly int s_nativePathLength = 108; // sizeof(sun_path)
private static readonly int s_nativeAddressSize = s_nativePathOffset + s_nativePathLength; // sizeof(sockaddr_un)
#pragma warning restore CA1802
评论区