前一篇文章我们对SocketAsyncEventArgs
进行了简单的封装:https://blog.csdn.net/moasp/article/details/120271060
现在我们实现一个继承NetworkStream
的类,重写NetworkStream
的BeginRead
和BeginWrite
方法,实现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、测试
在TcpSocketAsyncEventArgs
的ReadAsync
、WriteAsync
方法和IocpNetworkStream
的BeginRead
、BeginWrite
方法增加控制台输出,查看效果。
下面代码通过向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
给下游程序使用,注意安全
。