C#完成端口IOCP:(02) 封装NetworkStream支持IOCP操作

前一篇文章我们对SocketAsyncEventArgs进行了简单的封装:https://blog.csdn.net/moasp/article/details/120271060

现在我们实现一个继承NetworkStream的类,重写NetworkStreamBeginReadBeginWrite方法,实现IOCP操作。
主要就是用了前篇文章封装的TcpSocketAsyncEventArgs

0、实现BeginRead/BeginWrite

当调用ReadAsync时,就会执行重写的BeginRead逻辑,实现IOCP操作。

/// <summary>
/// 实现异步IOCP读取数据
/// </summary>
/// <param name="buffer">缓冲区</param>
/// <param name="offset">数据在缓冲区的位置</param>
/// <param name="size">准备读取的数据大小</param>
/// <param name="callback">回调方法</param>
/// <param name="state">用户状态</param>
/// <returns></returns>
public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state)
{
    //从栈中弹出一个TcpSocketAsyncEventArgs用来读取数据
    //参考:https://github.com/hooow-does-it-work/iocp-sharp/blob/main/TcpSocketEventargs.cs
    TcpSocketAsyncEventArgs e = TcpSocketAsyncEventArgs.Pop();

    //实现一个IAsyncResult
    //参考:https://github.com/hooow-does-it-work/iocp-sharp/blob/main/IocpReadWriteResult.cs
    IocpReadWriteResult asyncResult = new IocpReadWriteResult(callback, state, buffer, offset, size);

    try
    {
        //读取数据,完成后调用回调函数AfterRead
        //将TcpSocketAsyncEventArgs和IocpReadWriteResult传递给回调函数。
        //ReadWriteArgs是一个简单的封装,保存上面两个参数。
        //private class ReadWriteArgs
        //{
        //    public TcpSocketAsyncEventArgs TcpSocketAsyncEventArgs;
        //    public IocpReadWriteResult AsyncResult;
        //    public ReadWriteArgs(TcpSocketAsyncEventArgs e, IocpReadWriteResult asyncResult)
        //    {
        //        TcpSocketAsyncEventArgs = e;
        //        AsyncResult = asyncResult;
        //    }

        //    ~ReadWriteArgs()
        //    {
        //        TcpSocketAsyncEventArgs = null;
        //        AsyncResult = null;
        //    }
        //}
        e.ReadAsync(Socket, buffer, offset, size, AfterRead, new ReadWriteArgs(e, asyncResult));

        return asyncResult;
    }
    catch(SocketException ex)
    {
        asyncResult.SetFailed(ex);

        //回收TcpSocketAsyncEventArgs
        TcpSocketAsyncEventArgs.Push(e);
        return asyncResult;
    }
    catch
    {
        asyncResult.Dispose();
        //回收TcpSocketAsyncEventArgs
        TcpSocketAsyncEventArgs.Push(e);
        throw;
    }
}

/// <summary>
/// 基础流读取到数据后
/// </summary>
/// <param name="bytesReceived"></param>
/// <param name="errorCode"></param>
/// <param name="state"></param>
private void AfterRead(int bytesReceived, int errorCode, object state)
{
    ReadWriteArgs args = state as ReadWriteArgs;
    IocpReadWriteResult result = args.AsyncResult;

    if (errorCode != 0)
    {
        //如果IOCP返回了错误代码,设置IAsyncResult为失败状态
        result.SetFailed(new SocketException(errorCode));
    }
    else
    {
        //成功读取数据,反馈给IAsyncResult
        result.BytesTransfered = bytesReceived;
        result.CallUserCallback();
    }

    //回收TcpSocketAsyncEventArgs
    TcpSocketAsyncEventArgs.Push(args.TcpSocketAsyncEventArgs);
}

/// <summary>
/// 结束读取数据
/// </summary>
/// <param name="asyncResult"></param>
/// <returns>读取数据的大小</returns>
public override int EndRead(IAsyncResult asyncResult)
{
    if (asyncResult is not IocpReadWriteResult result) throw new InvalidOperationException("asyncResult");

    //如果同步调用了EndRead,等待IAsyncResult完成
    //不建议同步调用。
    if (result.IsCompleted) result.AsyncWaitHandle.WaitOne();

    //失败,抛出异常
    if (result.Exception != null) throw result.Exception;

    //返回读取到的数据
    return result.BytesTransfered;
}

BeginWrite的逻辑同BeginRead一样,不赘述了。
整个类的完整实现参考:https://github.com/hooow-does-it-work/iocp-sharp/blob/main/IocpNetworkStream.cs

1、测试

TcpSocketAsyncEventArgsReadAsyncWriteAsync方法和IocpNetworkStreamBeginReadBeginWrite方法增加控制台输出,查看效果。
下面代码通过向csdn服务器的80端口发送一个简单的HTTP请求,来测试异步。


//实例化Socket
Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

//连接服务器
client.Connect("www.csdn.net", 80);

//实例化IocpNetworkStream
NetworkStream stream = new IocpNetworkStream(client, true);

//向服务器发送个Http请求头
string header = "GET / HTTP/1.1\r\nHost: www.csdn.net\r\nConnection: close\r\n\r\n";
byte[] streamBuffer = Encoding.ASCII.GetBytes(header);

//使用异步方法,Stream内部会调用我们重写的BeginWrite、EndWrite方法生成异步任务
stream.WriteAsync(streamBuffer, 0, streamBuffer.Length).Wait();

//随便从服务器读取点数据
byte[] buffer = new byte[4096];

//使用异步方法,Stream内部会调用我们重写的BeginRead、EndRead方法生成异步任务
var task = stream.ReadAsync(buffer, 0, buffer.Length);
task.Wait();
stream.Close();

//输出从服务器返回的内容
string response = Encoding.UTF8.GetString(buffer, 0, task.Result);

Console.WriteLine(response);

在这里插入图片描述
可以看到控制台输出,上面部分四行是分别调用了对应的异步方法。
下面返回了HTTP响应,CSDN会强制跳转SSL,所以响应了301 Moved Permanently状态。

2、总结

程序用到的IocpReadWriteResult类实现了IAsyncResult接口给BeginXXX和EndXXX使用。
.Net核心的内部也有类似的实现,并且我也作了一些参考。
封装完成后,正常使用流的各种方法即可,下游应用程序无需关心实现细节。

IocpNetworkStream实现ISocketBasedStream接口,暴露出了BaseSocket给下游程序使用,注意安全

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
最近有项目要做一个高性能网络服务器,去网络上搜到到的都是C++版本而且是英文或者简单的DEMO,所以自己动手写了C# 的DEMO。 网络上只写接收到的数据,没有说怎么处理缓冲区数据,本DEMO简单的介绍如何处理接收到的数据。简单易用,希望对大家有用. 1、在C#中,不用去面对完成端口操作系统内核对象,Microsoft已经为我们提供了SocketAsyncEventArgs类,它封装IOCP的使用。请参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socketasynceventargs.aspx?cs-save-lang=1&cs-lang=cpp#code-snippet-1。 2、我的SocketAsyncEventArgsPool类使用List对象来存储对客户端来通信的SocketAsyncEventArgs对象,它相当于直接使用内核对象时的IoContext。我这样设计比用堆栈来实现的好处理是,我可以在SocketAsyncEventArgsPool池中找到任何一个与服务器连接的客户,主动向它发信息。而用堆栈来实现的话,要主动给客户发信息,则还要设计一个结构来存储已连接上服务器的客户。 3、对每一个客户端不管还发送还是接收,我使用同一个SocketAsyncEventArgs对象,对每一个客户端来说,通信是同步进行的,也就是说服务器高度保证同一个客户连接上要么在投递发送请求,并等待;或者是在投递接收请求,等待中。本例只做echo服务器,还未考虑由服务器主动向客户发送信息。 4、SocketAsyncEventArgs的UserToken被直接设定为被接受的客户端Socket。 5、没有使用BufferManager 类,因为我在初始化时给每一个SocketAsyncEventArgsPool中的对象分配一个缓冲区,发送时使用Arrary.Copy来进行字符拷贝,不去改变缓冲区的位置,只改变使用的长度,因此在下次投递接收请求时恢复缓冲区长度就可以了!如果要主动给客户发信息的话,可以new一个SocketAsyncEventArgs对象,或者在初始化中建立几个来专门用于主动发送信息,因为这种需求一般是进行信息群发,建立一个对象可以用于很多次信息发送,总体来看,这种花销不大,还减去了字符拷贝和消耗。 6、测试结果:(在我的笔记本上时行的,我的本本是T420 I7 8G内存) 100客户 100,000(十万次)不间断的发送接收数据(发送和接收之间没有Sleep,就一个一循环,不断的发送与接收) 耗时3004.6325 秒完成 总共 10,000,000 一千万次访问 平均每分完成 199,691.6 次发送与接收 平均每秒完成 3,328.2 次发送与接收 整个运行过程中,内存消耗在开始两三分种后就保持稳定不再增涨。 看了一下对每个客户端的延迟最多不超过2秒。
1、在C#中,不用去面对完成端口操作系统内核对象,Microsoft已经为我们提供了SocketAsyncEventArgs类,它封装IOCP的使用。请参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socketasynceventargs.aspx?cs-save-lang=1&cs-lang=cpp#code-snippet-1。 2、我的SocketAsyncEventArgsPool类使用List对象来存储对客户端来通信的SocketAsyncEventArgs对象,它相当于直接使用内核对象时的IoContext。我这样设计比用堆栈来实现的好处理是,我可以在SocketAsyncEventArgsPool池中找到任何一个与服务器连接的客户,主动向它发信息。而用堆栈来实现的话,要主动给客户发信息,则还要设计一个结构来存储已连接上服务器的客户。 3、对每一个客户端不管还发送还是接收,我使用同一个SocketAsyncEventArgs对象,对每一个客户端来说,通信是同步进行的,也就是说服务器高度保证同一个客户连接上要么在投递发送请求,并等待;或者是在投递接收请求,等待中。本例只做echo服务器,还未考虑由服务器主动向客户发送信息。 4、SocketAsyncEventArgs的UserToken被直接设定为被接受的客户端Socket。 5、没有使用BufferManager 类,因为我在初始化时给每一个SocketAsyncEventArgsPool中的对象分配一个缓冲区,发送时使用Arrary.Copy来进行字符拷贝,不去改变缓冲区的位置,只改变使用的长度,因此在下次投递接收请求时恢复缓冲区长度就可以了!如果要主动给客户发信息的话,可以new一个SocketAsyncEventArgs对象,或者在初始化中建立几个来专门用于主动发送信息,因为这种需求一般是进行信息群发,建立一个对象可以用于很多次信息发送,总体来看,这种花销不大,还减去了字符拷贝和消耗。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Anlige

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值