最近遇到 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. Android多线程研究(8)——Java5中Futrue获取线程返回结果

    我们先来看一下ExecutorService中的执行方法: 在上一篇中我们使用了execute方法启动线程池中的线程执行,这一篇我们来看看submit方法的使用:submit提交一个返回值的任务用于执 ...

  2. Struts2完全解耦和

    jsp: <%@ page language="java" contentType="text/html; charset=UTF-8" pageEnco ...

  3. Android NDK开发之Jni的数据类型

    在前面的一篇博客<Android NDK开发简介>,我简单地说明了Android NDK开发的流程,以及其重要的一环:JNI层得开发.今天我再详细说明一下自己的学习经验. JNI是Java ...

  4. c3p0 xml配置文件模版

    <c3p0-config> <default-config> <property name="driverClass">com.mysql.jd ...

  5. CSS自己主动换行、强制不换行、强制断行、超出显示省略号

    P标签是默认是自己主动换行的,因此设置好宽度之后,可以较好的实现效果,可是近期的项目中发现,使用ajax载入数据之后.p标签内的内容没有换行,导致布局错乱,于是尝试着使用换行样式,尽管攻克了问题.可是 ...

  6. 删除vector中的重复数据(unique)

    #include <iostream> #include <vector> #include <algorithm> #include <assert.h&g ...

  7. 【a703】求逆序对(线段树的解法)

    Time Limit: 10 second Memory Limit: 2 MB 问题描述 给定一个序列a1,a2...an.如果存在i小于j 并且ai大于aj,那么我们称之为逆序对,求给定序列中逆序 ...

  8. Git之同一台电脑如何连接多个远程仓库

    Git之同一台电脑如何连接多个远程仓库 一.总结 一句话总结: 1.git使用.git目录里面的内容(ssh或用户名密码)来自动和远程仓库通信 2.在不同的仓库中配置不同的.git目录,可以连接不同的 ...

  9. 安全配置基线Linux系统

    Linux系统安全配置基线 一:共享账号检查 配置名称:用户账号分配检查,避免共享账号存在 配置要求:1.系统需按照实际用户分配账号: 2.避免不同用户间共享账号,避免用户账号和服务器间通信使用的账号 ...

  10. 微服务学习笔记(1)——使用MagicOnion实现gRPC

    原文:微服务学习笔记(1)--使用MagicOnion实现gRPC 1.什么是gRPC 官方文档:https://grpc.io/docs/guides/index.html 2.什么是MagicOn ...