侧边栏壁纸
博主头像
komi

Bona Fides

  • 累计撰写 15 篇文章
  • 累计创建 28 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

进程间通信 - UDS和TCPIP Loopback

komi
2025-05-02 / 0 评论 / 0 点赞 / 15 阅读 / 930 字

从来不怎么看EP(Enhancement Proposal)的我昨天突然网上冲浪看到了这篇JEP 发现还有UDS这个新奇玩意 虽说是*n?x的产物 在Windows 10貌似也有集成

UDS?

A Unix domain socket (UDS), a.k.a. local socket, a.k.a. inter-process communication (IPC) socket, is a communication endpoint for exchanging data between processes executing in the same Unix or Unix-like operating system.
The name, Unix domain socket, refers to the domain argument value AF_UNIX that is passed to the function that creates a socket system resource. The same communication domain is also selected by AF_LOCAL.

本机通信使用常规的本地回环地址(127.0.0.1,localhost) 需要走网络协议栈 需要封包、拆包、计算校验和之类的 而UDS基于文件系统 免去了一些开销

jep380

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名称在抽象命名空间中

https://man7.org/linux/man-pages/man7/unix.7.html#top_of_page:~:text=an abstract socket,with filesystem pathnames.

开始测试

既然都说了开销相对更小 那就得实测看下是不是真的那么快

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本地回环的方式

路径长度限制?

如果路径字节过长的情况下会直接抛异常
2025-05-03 144825

UnixDomainSocketEndPoint.cs

        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
        }

UnixDomainSocketEndPoint.cs

#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
0

评论区