最近遇到 NetworkStream.ReadAsync 在 Linux 上高并发读取数据的问题,由此激发了阅读 corefx 中 System.Net.Sockets 实现源码(基于 corefx 2.2)的兴趣。

这篇随笔是阅读 NetworkStream.ReadAsync 相关源码的简单笔记,基于在 Linux 上运行的场景。

NetworkStream 继承自 System.IO.Stream ,System.IO.Stream.ReadAsync 方法签名是

public Task<int> ReadAsync(byte[] buffer, int offset, int count);

实际调用的是

public virtual Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)

上面的的方法被 NetworkStream 重写(override),调用的是 Socket 的 ReceiveAsync 方法

return _streamSocket.ReceiveAsync(
new Memory<byte>(buffer, offset, size),
SocketFlags.None,
fromNetworkStream: true,
cancellationToken).AsTask();

Socket.ReceiveAsync 的方法签名

internal ValueTask<int> ReceiveAsync(Memory<byte> buffer, SocketFlags socketFlags, bool fromNetworkStream, CancellationToken cancellationToken)

主要实现代码

AwaitableSocketAsyncEventArgs saea = LazyInitializer.EnsureInitialized(ref LazyInitializer.EnsureInitialized(ref _cachedTaskEventArgs).ValueTaskReceive);
if (saea.Reserve())
{
saea.SetBuffer(buffer);
saea.SocketFlags = socketFlags;
saea.WrapExceptionsInIOExceptions = fromNetworkStream;
var result = saea.ReceiveAsync(this);
return result;
}
else
{
// We couldn't get a cached instance, due to a concurrent receive operation on the socket.
// Fall back to wrapping APM.
return new ValueTask<int>(ReceiveAsyncApm(buffer, socketFlags));
}

通常情况下都会使用 AwaitableSocketAsyncEventArgs 异步读取数据,所以我们这里只从 saea.ReceiveAsync 往下看。

saea.ReceiveAsync 调用的是 Socket.ReceiveAsync(SocketAsyncEventArgs e)  方法,而后者调用的是 SocketAsyncEventArgs.DoOperationReceive(SafeCloseSocket handle) 。

在 Linux 上 DoOperationReceive 的实现在 SocketAsyncEventArgs.Unix.cs 中,主要代码如下

internal unsafe SocketError DoOperationReceive(SafeCloseSocket handle)
{
//...
if (_bufferList == null)
{
errorCode = handle.AsyncContext.ReceiveAsync(_buffer.Slice(_offset, _count), _socketFlags, out bytesReceived, out flags, TransferCompletionCallback);
}
else
{
errorCode = handle.AsyncContext.ReceiveAsync(_bufferListInternal, _socketFlags, out bytesReceived, out flags, TransferCompletionCallback);
} if (errorCode != SocketError.IOPending)
{
CompleteTransferOperation(bytesReceived, null, , flags, errorCode);
FinishOperationSync(errorCode, bytesReceived, flags);
} return errorCode;
}

handle.AsyncContext.ReceiveAsync 对应的 Linux 实现在 SocketAsyncContext.Unix.cs 中,调用的是 SocketAsyncContext 的 ReceiveFrom 方法,ReceiveFrom 的主要实现代码如下

public SocketError ReceiveFromAsync(Memory<byte> buffer,  SocketFlags flags, byte[] socketAddress, ref int socketAddressLen, out int bytesReceived, out SocketFlags receivedFlags, Action<int, byte[], int, SocketFlags, SocketError> callback)
{
SetNonBlocking(); SocketError errorCode;
int observedSequenceNumber;
if (_receiveQueue.IsReady(this, out observedSequenceNumber) &&
SocketPal.TryCompleteReceiveFrom(_socket, buffer.Span, flags, socketAddress, ref socketAddressLen, out bytesReceived, out receivedFlags, out errorCode))
{
return errorCode;
} BufferMemoryReceiveOperation operation = RentBufferMemoryReceiveOperation();
operation.Callback = callback;
operation.Buffer = buffer;
operation.Flags = flags;
operation.SocketAddress = socketAddress;
operation.SocketAddressLen = socketAddressLen; if (!_receiveQueue.StartAsyncOperation(this, operation, observedSequenceNumber))
{
receivedFlags = operation.ReceivedFlags;
bytesReceived = operation.BytesTransferred;
errorCode = operation.ErrorCode; ReturnOperation(operation);
return errorCode;
} bytesReceived = ;
receivedFlags = SocketFlags.None;
return SocketError.IOPending;
}

SocketPal.TryCompleteReceiveFrom 的实现代码在 SocketPal.Unix.cs 中,所调用的另一个 TryCompleteReceiveFrom 方法的签名是

public static unsafe bool TryCompleteReceiveFrom(SafeCloseSocket socket, Span<byte> buffer, IList<ArraySegment<byte>> buffers, SocketFlags flags, byte[] socketAddress, ref int socketAddressLen, out int bytesReceived, out SocketFlags receivedFlags, out SocketError errorCode)

该方法调用的是 Receive 方法

private static unsafe int Receive(SafeCloseSocket socket, SocketFlags flags, IList<ArraySegment<byte>> buffers, byte[] socketAddress, ref int socketAddressLen, out SocketFlags receivedFlags, out Interop.Error errno)

在 Receive 方法中调用了

errno = Interop.Sys.ReceiveMessage(
socket.DangerousGetHandle(),
&messageHeader,
flags,
&received);

Interop.Sys.ReceiveMessage 对应的是 Linux 本地库中的方法

internal static partial class Sys
{
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ReceiveMessage")]
internal static extern unsafe Error ReceiveMessage(IntPtr socket, MessageHeader* messageHeader, SocketFlags flags, long* received);
}

Libraries.SystemNative 对应的是哪个库呢?

它就是 System.Native.so

$ find /usr/share/dotnet/ -name System.Native.so
/usr/share/dotnet/shared/Microsoft.NETCore.App/2.2.0/System.Native.so

接下来根据 SocketError.IOPending 的情况阅读源码。

SocketAsyncEventArgs 在 DoOperationReceive 方法中调用 SocketAsyncContext.ReceiveFrom 方法时(handle.AsyncContext.ReceiveAsync)传递了 TransferCompletionCallback 参数值,在异步操作时是通过这个 callback 读取 socket 数据的,对应的方法是 TransferCompletionCallbackCore 。

private void TransferCompletionCallbackCore(int bytesTransferred, byte[] socketAddress, int socketAddressSize, SocketFlags receivedFlags, SocketError socketError)
{
CompleteTransferOperation(bytesTransferred, socketAddress, socketAddressSize, receivedFlags, socketError); CompletionCallback(bytesTransferred, receivedFlags, socketError);
}

TransferCompletionCallbackCore 中进一步调用 CompletionCallback

private void CompletionCallback(int bytesTransferred, SocketFlags flags, SocketError socketError)
{
if (socketError == SocketError.Success)
{
FinishOperationAsyncSuccess(bytesTransferred, flags);
}
else
{
if (_currentSocket.CleanedUp)
{
socketError = SocketError.OperationAborted;
} FinishOperationAsyncFailure(socketError, bytesTransferred, flags);
}
}

在 CompletionCallback 中当 SocketError.Success 时进一步调用 FinishOperationAsyncSuccess

internal void FinishOperationAsyncSuccess(int bytesTransferred, SocketFlags flags)
{
FinishOperationSyncSuccess(bytesTransferred, flags); // Raise completion event.
if (_context == null)
{
OnCompleted(this);
}
else
{
ExecutionContext.Run(_context, s_executionCallback, this);
}
}

从上面的代码可以看出实际调用的也是 FinishOperationSyncSuccess ,异步与同步读取数据最终调用的是同一个方法。

corefx 源码学习:NetworkStream.ReadAsync 是如何从 Socket 异步读取数据的的更多相关文章

  1. corefx 源码学习:SqlClient 是如何同步建立 Socket 连接的

    在昨天的技术周会上发现 EnyimMemcached 中建立 Socket 连接的代码有问题,今天坐车的时候在手机上阅读 .net core 2.2 的 SqlClient 中同步建立 Socket ...

  2. netty源码解解析(4.0)-14 Channel NIO实现:读取数据

     本章分析Nio Channel的数据读取功能的实现. Channel读取数据需要Channel和ChannelHandler配合使用,netty设计数据读取功能包括三个要素:Channel, Eve ...

  3. .NET Core 2.1 源码学习:看 SocketsHttpHandler 如何在异步方法中连接 Socket

    在 .NET Core 2.1 中,System.Net.Sockets 的性能有了很大的提升,最好的证明是 Kestrel 与 HttpClient 都改为使用 System.Net.Sockets ...

  4. [算法2-数组与字符串的查找与匹配] (.NET源码学习)

    [算法2-数组与字符串的查找与匹配] (.NET源码学习) 关键词:1. 数组查找(算法)   2. 字符串查找(算法)   3. C#中的String(源码)   4. 特性Attribute 与内 ...

  5. Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结

    2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...

  6. jQuery源码学习感想

    还记得去年(2015)九月份的时候,作为一个大四的学生去参加美团霸面,结果被美团技术总监教育了一番,那次问了我很多jQuery源码的知识点,以前虽然喜欢研究框架,但水平还不足够来研究jQuery源码, ...

  7. MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)

    前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...

  8. MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)

    前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...

  9. MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)

    前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...

随机推荐

  1. HtmlParser基础教程 分类: C_OHTERS 2014-05-22 11:33 1649人阅读 评论(1) 收藏

    1.相关资料 官方文档:http://htmlparser.sourceforge.net/samples.html API:http://htmlparser.sourceforge.net/jav ...

  2. Java fork join ForkJoinPool 用法例子

    本例是把一个大的数组求和的计算的大任务分解到在小范围内求和的小任务,然后把这些小任务之和加起来就是所求之结果. 技术:JDK8.0, Javafork-join模式下的RecursiveTask技术, ...

  3. Color the ball(杭电1556)

    Color the ball Time Limit : 9000/3000ms (Java/Other)   Memory Limit : 32768/32768K (Java/Other) Tota ...

  4. php如何实现万年历的开发(每日一课真是非常有效率)

    php如何实现万年历的开发(每日一课真是非常有效率) 一.总结 一句话总结: 1.判断每月有多少天: 通过data函数来判断,$days=date('t',$firstday); 2.判断每月的第一天 ...

  5. linux mysql 卸载,安装,測试全过程

    Mysql卸载 yum remove mysql mysql-server mysql-libs compat-mysql51 rm -rf /var/lib/mysql rm /etc/my.cnf ...

  6. cordova之File Transfer (Permission denied) 权限导致下载失败 - 简书

    原文:cordova之File Transfer (Permission denied) 权限导致下载失败 - 简书 在文件上传时,由于权限问题,会报错(Permission denied),安卓6. ...

  7. Android最新组件RecyclerView,替代ListView

    转载请注明出处:http://blog.csdn.net/allen315410/article/details/40379159 万众瞩目的android最新5.0版本号不久前已经正式公布了,对于我 ...

  8. 到底有多少种智能指针(smart pointer)

    最近Qt的blog总结了到底有多少种smart pointer, 下面是一个简要的介绍: 1.   QPointer :提供对指针的保护,当一个指针被删除以后,再使用不会造成野指针或者指针溢出.比如 ...

  9. Arcgis api for javascript学习笔记-控制地图缩放比例尺范围(3.2X版本与4.6版本)

    Ⅰ. 在3.X版本中,设置Map对象的 "maxScale" 和 "minScale" 属性 <!DOCTYPE html> <html> ...

  10. 自己动手编写一个VS插件(五)

    作者:朱金灿 来源:http://blog.csdn.net/clever101 继续编写VisualStudio插件.这次我编写的插件叫DevAssist(意思是开发助手).在看了前面的文章之后你知 ...