iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
#️⃣

[C#] Stop Using TcpClient/UdpClient and Switch to Socket Directly

に公開

In C#, under the System.Net.Sockets namespace, there are classes named TcpClient and UdpClient. Since they appear at the top of search results when looking for how to handle TCP/UDP in C#, many people are likely using them.

However, they are essentially just wrappers around Socket, and from a modern perspective, their APIs are quite strange. Even when reading issues or PRs in dotnet/runtime, it seems the latest consensus is that these classes are not actively maintained and are no longer the recommended approach.

That said, just saying "stop using these classes" without further explanation might not be convincing enough, so let me explain in more detail.

Socket

When writing low-level communication processing in C#, you will end up using the Socket class. Because this Socket class has existed since before async/await, it contains a mix of synchronous and asynchronous APIs, resulting in a rather chaotic state.

Even more problematic is that among the asynchronous methods available, many should no longer be used in modern times because they predate APIs like Memory<T>. It is truly a class built from historical baggage...

Regarding how to distinguish these, quoting neuecc's article:

There is a clear way to identify which APIs you should use. Choose the ones that return a ValueTask.

public ValueTask ConnectAsync(string host, int port, CancellationToken cancellationToken)
public ValueTask<int> ReceiveAsync(Memory<byte> buffer, SocketFlags socketFlags, CancellationToken cancellationToken)
public ValueTask<int> SendAsync(ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, CancellationToken cancellationToken))

Be careful, as some overloads return a Task.

// These APIs should not be used
public Task ConnectAsync(string host, int port)
public Task<int> ReceiveAsync(ArraySegment<byte> buffer, SocketFlags socketFlags)
public Task<int> SendAsync(ArraySegment<byte> buffer, SocketFlags socketFlags)

ArraySegment<T> brings back memories. Nostalgic, isn't it?

Additionally, .NET 8 introduced major enhancements to TCP/UDP in Socket. This is covered in the .NET 8 article of the well-known "Performance Improvements in .NET" series, which breaks the readers' hearts with its sheer length.

https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/#sockets

While various improvements have been made, the key point is that overloads were added to bypass the unnecessary intermediary EndPoint class and use SocketAddress directly. This eliminates wasteful intermediary buffer allocations, which is expected to improve performance.

Problems with TcpClient/UdpClient

Up to this point, we have looked at how Socket has been improved, but this is where the problem lies.

TcpClient and UdpClient are essentially just wrappers around Socket, and they contain very little that can be called real implementation. Unfortunately, they have not kept up well with the improvements made to Socket (due to issues with backward compatibility or simply a lack of maintenance).

In fact, there are proposals for new APIs that do not use TcpClient.

https://github.com/dotnet/runtime/issues/63162

This proposal suggests deprecating TcpClient in favor of something brand new based on TcpListener and TcpConnection (though there is a strong opinion that it should be TcpStream since it is a stream).

In any case, TcpClient seems to be an approach that is no longer recommended. The official documentation even states:

The Socket class is highly recommended for advanced users, instead of TcpClient and TcpListener.

The situation is similar for UdpClient. In an issue regarding the UdpClient API, a contributor even said:

We definitely don't want to invest into UdpClient perf.

It seems unlikely that we will see improvements there.

Conclusion

Therefore, unless there are circumstances requiring compatibility with existing code, it seems more straightforward and performant to work directly with Socket in modern .NET development. Let's stop using TcpClient/UdpClient and start using Socket.

Discussion