1.NetworkDirect定义
NetworkDirect是一种基于RDMA的用户编程模式编程接口规范,这套规范能实现本端和远端(客户端和服务端)的零拷贝数据传输、内核旁路的数据发送和接受等RDMA功能。其中,RDMA功能由具备RDMA能力的网络适配器提供,NetworkDirect可以基于ib、iwarp或roce三种协议中的一种,而NetworkDirect允许应用程序开发人员,通过访问和使用具备RDMA能力的网卡制造商通过提供的接口, 实现使用网卡的硬件功能。
特别说明:NetworkDirect是基于windows开发的。
2.NDSPI model
NetworkDirect Server Provider Interface
2.1 NDSPI model特点
NetworkDirect客户端用用程序使用COM与Provider进行通信。COM接口通过IUnknown::QueryInterface方法提供了灵活的可扩展性,允许Provider返回其提供的任何接口,从而向外部暴露特定的硬件特性。NetworkDirect的Provider不是真正的COM对象,NetworkDirect的Provider不会再系统中注册特定的对象。
NDSPI 不支持marshaling处理,即Provider公开的COM接口需要在应用程序的进程中自己实现实例化。可参考Winows 驱动程序框架-用户模式驱动程序框架(UMDF)所使用的驱动程序模型。
2.2 NDSPI model 提供接口
IND2Provider
表示服务提供者。提供IN2Provider::OpenAdapter方法来实例化一个IN2Adapter对象。
IND2Overlapped
overlapped IO对象的基类。
IND2Adapter
Provider硬件适配器实例的接口,其他的类或者接口都由此适配器产生。
IND2CompletionQueue
通过IND2Adapter::CreateCompletionQueue创建,完成队列(CQ)实例的接口。
IND2MemoryRegion
通过IND2Adapter::CreateMemoryRegion创建,表示内存区接口,即适配器示例上注册的本地缓冲区,方便存储发送或者接受到的数据。
IND2SharedReceiveQueue
通过IND2Adapter::CreateSharedReceiveQueue方法创建,用于队列对QPair之间聚合接受缓冲区的共享接收队列。
IND2QueuePair
通过IND2Adapter::CreateQueuePair方法创建,用于执行IO操作队列实例的接口。
IND2Connector
通过IND2Adapter::CreateConnector方法创建,用于执行IO操作队列建立连接的的实例的接口。
IND2Listener
通过IND2Adapter::CreateListener方法创建,用于监听请求的接口。
3 编程流程
根据microsoft提供的关于ND开发的官方文档,基于ND开发的关键步骤为:
3.1 打开网络编程
// 监听本地端口
WSADATA wsaData;
int ret = ::WSAStartup(MAKEWORD(2, 2), &wsaData);
if (ret != 0)
{
printf("Failed to initialize Windows Sockets: %d\n", ret);
exit(__LINE__);
}
3.2 注册Provider
调用WSCInstallProvider 来实现注册,其中调用接口时输入的参数需要满足以下特点:
dwServiceFlags1:
- XP1_GUARANTEED_DELIVERY
- XP1_GUARANTEED_ORDER
- XP1_MESSAGE_ORIENTED
- XP1_CONNECT_DATA
dwProviderFlags: PFL_HIDDEN | PFL_NETWORKDIRECT_PROVIDERiVersion: 2
iAddressFamily: AF_INET or AF_INET6
iSocketType: -1 (0xFFFFFFFF)
iProtocol: 0
iProtocolMaxOffset: 0
补充说明:为什么networkdirect 提供的例子里没有这个步骤?
3.3 实例化Provider
NDSPI应用程序支持0个,1个或多个服务提供商库。对ND Provider的管理遵循为Windows Sockets Direct Provider和传统分层服务Provider定义的既定机制。
3.3.1 查找ND Provider
计算机上有很多个Provider,通过 WSCEnumProtocols 能找到计算机上所有的Provider,通过对比实例化时的参数,找到注册过的Provider。
3.3.2 实例化ND Provider
通过实例化IND2Provider,可以确定Provider是否是ND service Provider,即,如果有实例化IND2Provider,则Provider是ND service Provider;否则,Provider不是ND service Provider。
方法:
加载DLL:使用WSAPROTOCOL_INFO 数据结构中的ProviderId,调用 WSCGetProviderPath 获取dll路径。
调用库的DllGetClassObject入口实例化IND2Provider
service Provider库需要实现一个DllCanUnloadNow入口点,以允许客户端在不再使用时卸载service Provider库。Provider还必须能处理IND2Provider的多个调用。
3.4 通过Provider获取Adapter
不管是应用端还是客户端,都需要一个ip地址,有了ip地址,可以通过IN2Provider::OpenAdapter方法来实例化一个IN2Adapter对象。
3.5 Adapter调用不同的接口实现具体的功能
3.5.1 服务端读/写数据基本流程(伪代码)
// Get the file handle for overlapped operations on this adapter.
// 通过adapter获取overlapped的文件句柄
hr = m_pAdapter->CreateOverlappedFile(&m_hAdapterFile);
if (FAILED(hr))
{
LogErrorExit(hr, "IND2Adapter::CreateOverlappedFile failed", __LINE__);
}
// 获取adapter的相关信息,方便后续操作
memset(pAdapterInfo, 0, sizeof(*pAdapterInfo));
pAdapterInfo->InfoVersion = ND_VERSION_2;
ULONG adapterInfoSize = sizeof(*pAdapterInfo);
HRESULT hr = m_pAdapter->Query(pAdapterInfo, &adapterInfoSize);
// 通过adapter创建CQ
HRESULT hr = m_pAdapter->CreateCompletionQueue(
IID_IND2CompletionQueue,
m_hAdapterFile,
depth,
0,
0,
reinterpret_cast<VOID**>(&m_pCq)
);
// 通过adapter创建Connector
HRESULT hr = m_pAdapter->CreateConnector(
IID_IND2Connector,
m_hAdapterFile,
reinterpret_cast<VOID**>(&m_pConnector)
);
// 通过adapter创建QP
HRESULT hr = m_pAdapter->CreateQueuePair(
IID_IND2QueuePair,
m_pCq,
m_pCq,
nullptr,
queueDepth,
queueDepth,
nSge,
nSge,
inlineDataSize,
reinterpret_cast<VOID**>(&m_pQp)
);
// 通过adapter创建MR
HRESULT hr = m_pAdapter->CreateMemoryRegion(
IID_IND2MemoryRegion,
m_hAdapterFile,
reinterpret_cast<VOID**>(&m_pMr)
);
// 通过MR开辟内存空间
HRESULT hr = m_pMr->Register(
pBuf,
bufferLength,
type,
&m_Ov
);
// 通过adapter创建listener
HRESULT hr = m_pAdapter->CreateListener(
IID_IND2Listener,
m_hAdapterFile,
reinterpret_cast<VOID**>(&m_pListen)
);
// 通过listener 监听连接请求
HRESULT hr = m_pListen->GetConnectionRequest(m_pConnector, &m_Ov);
// post reveive for the terminate message
// 发送准备接受数据的信息
ND2_SGE sge = { 0 };
sge.Buffer = m_pBuf;
sge.BufferLength = x_PiggyBackXfer + sizeof(struct PeerInfo);
sge.MemoryRegionToken = m_pMr->GetLocalToken();
NdTestBase::PostReceive(&sge, 1, RECV_CTXT);
// 建立连接
HRESULT hr = m_pConnector->Accept(
m_pQp,
inboundReadLimit,
outboundReadLimit,
pPrivateData,
cbPrivateData,
&m_Ov
);
// 读数据
hr = m_pQp->Read(requestContext, Sge, nSge, remoteAddress, remoteToken, flag);
// 写数据
hr = m_pQp->Write(requestContext, Sge, nSge, remoteAddress, remoteToken, flag);
3.5.2 客户端send/receive基本流程(伪代码)
// Get the file handle for overlapped operations on this adapter.
// 通过adapter获取overlapped的文件句柄
hr = m_pAdapter->CreateOverlappedFile(&m_hAdapterFile);
if (FAILED(hr))
{
LogErrorExit(hr, "IND2Adapter::CreateOverlappedFile failed", __LINE__);
}
// 获取adapter的相关信息,方便后续操作
memset(pAdapterInfo, 0, sizeof(*pAdapterInfo));
pAdapterInfo->InfoVersion = ND_VERSION_2;
ULONG adapterInfoSize = sizeof(*pAdapterInfo);
HRESULT hr = m_pAdapter->Query(pAdapterInfo, &adapterInfoSize);
// 通过adapter创建CQ
HRESULT hr = m_pAdapter->CreateCompletionQueue(
IID_IND2CompletionQueue,
m_hAdapterFile,
depth,
0,
0,
reinterpret_cast<VOID**>(&m_pCq)
);
// 通过adapter创建Connector
HRESULT hr = m_pAdapter->CreateConnector(
IID_IND2Connector,
m_hAdapterFile,
reinterpret_cast<VOID**>(&m_pConnector)
);
// 通过adapter创建QP
HRESULT hr = m_pAdapter->CreateQueuePair(
IID_IND2QueuePair,
m_pCq,
m_pCq,
nullptr,
queueDepth,
queueDepth,
nSge,
nSge,
inlineDataSize,
reinterpret_cast<VOID**>(&m_pQp)
);
// 通过adapter创建MR
HRESULT hr = m_pAdapter->CreateMemoryRegion(
IID_IND2MemoryRegion,
m_hAdapterFile,
reinterpret_cast<VOID**>(&m_pMr)
);
// 通过MR开辟内存空间
HRESULT hr = m_pMr->Register(
pBuf,
bufferLength,
type,
&m_Ov
);
// 客户端请求与服务端建立连接
hr = m_pConnector->Connect(
m_pQp,
reinterpret_cast<const sockaddr*>(&v4Dst),
sizeof(v4Dst),
inboundReadLimit,
outboundReadLimit,
pPrivateData,
cbPrivateData,
&m_Ov
);
// 判断连接是否建立
HRESULT hr = m_pConnector->CompleteConnect(&m_Ov);
// 创建MW
HRESULT hr = m_pAdapter->CreateMemoryWindow(
IID_IND2MemoryWindow,
reinterpret_cast<VOID**>(&m_pMw)
);
// mr mw buff数据绑定
if (m_pQp->Bind(context, m_pMr, m_pMw, pBuf, bufferLength, flags) != ND_SUCCESS)
{
LogErrorExit("Bind failed\n", __LINE__);
}
// 等待CQ
WaitForCompletion([&pResult](ND2_RESULT *pCompRes)
{
*pResult = *pCompRes;
}, bBlocking);
// prepare peerinfo
struct PeerInfo *peerInfo;
peerInfo = reinterpret_cast<struct PeerInfo *>(m_pBuf);
peerInfo->m_remoteAddress = reinterpret_cast<uint64_t>(m_pBuf);
peerInfo->m_remoteToken = m_pMw->GetRemoteToken();
ND2_SGE sgl[2];
sgl[0].Buffer = m_pBuf + x_ReadBase;
sgl[0].BufferLength = x_PiggyBackXfer;
sgl[0].MemoryRegionToken = m_pMr->GetLocalToken();
sgl[1].Buffer = peerInfo;
sgl[1].BufferLength = sizeof(PeerInfo);
sgl[1].MemoryRegionToken = m_pMr->GetLocalToken();
// 发送信息
HRESULT hr = m_pQp->Send(requestContext, Sge, nSge, flags);
// 数据发送完,清理数据
m_pConnector->Disconnect(&m_Ov);
m_pMr->Deregister(&m_Ov);