Windows 上的 iSCSI 开发教程






4.38/5 (10投票s)
本文将帮助您将 Windows 上可用的 iSCSI 功能集成到您的 C++ 基础应用程序中。

引言
鉴于关于如何在我们的应用程序中使用 iSCSI (Small Computer System Interface over TCP/IP) 功能(当我们希望将任何网络存储添加为我们机器上的独立设备或驱动器时)的支持有限,我写了一篇文章,希望能让尝试实现此功能的人们的生活更轻松。众所周知,MSDN 提供的关于 iSCSI API 支持的文档非常有限。而且,文档本身非常复杂,对于 iSCSI 新手来说很难理解。因此,这里有一个示例代码,可以帮助您熟悉 Windows 上的 iSCSI,并以编程方式执行 iSCSI 发起程序可以执行的几乎所有任务。我用于开发的语言是 C++。以下是需要实现的功能的简要概述。
理解 Windows 上的 iSCSI 概念
如前所述,iSCSI 是 Small Computer System Interface over TCP/IP 的缩写。iSCSI 的主要好处是,只要我们的网络中有足够的 SAN/NAS 设备空间,我们就可以按需获得所需的空间。为了让 iSCSI 在我们的机器上工作,我们需要在我们的机器上安装 Microsoft iSCSI 发起程序。好消息是,Microsoft 自 VISTA 之后的版本就开始随附此发起程序,但要在 XP 上运行此代码,我们需要从此处下载发起程序,具体取决于我们使用的系统架构。
之后,我们需要网络上有一台机器安装了 iSCSI 目标,并且已配置了可以连接到此机器的共享 iSCSI 设备。可用于此目的的工具之一由 StarWind 提供,您也可以始终使用其免费版本。您可以在此处获取免费版本。现在,在配置了网络机器上的目标并在您的基础机器上安装了 Microsoft iSCSI 发起程序后,您可以通过在 iSCSI 发起程序中输入已配置 iSCSI 目标的网络机器的 IP 地址来添加目标门户,从而测试连接。添加目标门户后,目标列表也会显示在“目标”选项卡上。您可以选择其中任何一个并登录到“目标”。如果此设备或目标具有 Windows 支持的文件系统格式,则此目标会自动作为独立的逻辑磁盘附加到基础机器;否则,如果目标的格式未知,则连接会建立,但逻辑磁盘未被检测到。但是,您可以转到“计算机管理”下的“磁盘管理”部分,查看可用于基础机器的目标的未格式化空间。
我认为这个解释将消除您在尝试理解 Windows 平台上的 iSCSI 时遇到的最初困难。但真正的问题是:“如何以编程方式完成所有这些操作?”这将在下一节中进行解释。
实现
由于 iSCSI 发起程序的所有功能都可以通过“iscsidsc.dll”获得,因此我们首先必须加载该 DLL,以便利用它提供的功能。这可以像下面这样简单地完成。加载 DLL 的理想位置是加载库的类的构造函数。例如,我有一个类 CIscsi
,其构造函数和析构函数看起来像这样
public:
// This will hold the handle obtained after loading the library
HMODULE m_hMod; // Class Variable
CIscsi()
{
m_hMod= NULL;
HRESULT hr = S_OK;
/* Loading iSCSI DLL to get its pointer
/*************************************/
m_hMod = LoadLibrary(_T("iscsidsc.dll"));
if ( !m_hMod )
{
// do error handling here
::MessageBox(NULL,_T("iscsidsc.dll failed to load "),_T("Iscsi"),
MB_OK|MB_ICONINFORMATION);
}
}
~CIscsi()
{
if (m_hMod)
{
FreeLibrary (m_hMod);
m_hMod = NULL;
}
}
分步说明
现在,DLL 已加载,您必须确保以正确的方式使用“iscsidsc.dll”提供的函数。请执行以下步骤:
- 在项目的 stdafx.h 中,添加这两行:
#include "iscsierr.h" // This will include all the error codes for ISCSI #include "iscsidsc.h" // This will bring all function definitions available // with iscsidsc.dll
- 在
CIscsi
类的头文件中,添加以下语句:public: // Access specifier can be private or public depending on the need. // As I had made a DLL I wanted to expose these functions because // of which I made these public // this function will fetch the error code, // thus will allow to detect the proper errors STDMETHOD(GetErrorString)(/*[out , retval]*/ BSTR *mresult); // function used to initialize or load iscsidsc.dll STDMETHOD(InitializeISCSI)(/*[in]*/ BSTR bstDllPath ); //This function is responsible for adding a specified target portal STDMETHOD(AddIscsiTargetPortal)( BSTR bstIPAddress,BSTR bstUserName , BSTR bstPassWord ,/*[out]*/ BSTR* bstTargetList,/*[out]*/BSTR* bstStatus ); // Function used to log on to a target STDMETHOD(LogOnToTargetPortal)( BSTR bstTargetIdName, BSTR* bstStatus ); // Function used to log off from the target STDMETHOD(LogOffTargetPortal)( BSTR bstTargetIdName, BSTR* bstStatus ); // Function we will use to remove the target portal STDMETHOD(RemoveIscsiTargetPortal)( BSTR bstIPAddress,/*[out]*/ BSTR* bstStatus );
- 同样,在实现文件的顶部或头文件中的全局命名空间中添加这些语句:
#define MAX_LOADSTRING 200 WCHAR wchRetVal[4096]; //doing all function definitions here // These all are function definitions which help us getting function // pointers of the existing functions in iscsidsc.dll typedef HRESULT(__stdcall *AddTargetPortal)(TCHAR,ULONG,PISCSI_LOGIN_OPTIONS , ISCSI_SECURITY_FLAGS,PISCSI_TARGET_PORTAL); typedef HRESULT(__stdcall *GetSessionList) ( ULONG* , ULONG* , PISCSI_SESSION_INFO ); typedef HRESULT(__stdcall *LogonIscsiTarget) (TCHAR* ,BOOLEAN ,TCHAR*,ULONG , PISCSI_TARGET_PORTAL,ISCSI_SECURITY_FLAGS, PISCSI_TARGET_MAPPING, PISCSI_LOGIN_OPTIONS ,ULONG ,CHAR*,BOOLEAN, PISCSI_UNIQUE_SESSION_ID ,PISCSI_UNIQUE_CONNECTION_ID ); typedef HRESULT(__stdcall *ReportTargets)(BOOLEAN, ULONG* , TCHAR*); typedef HRESULT(__stdcall *GetDevices)(PISCSI_UNIQUE_SESSION_ID, ULONG* , PISCSI_DEVICE_ON_SESSION); typedef HRESULT(__stdcall *LogOutOfIscsiTarget)(PISCSI_UNIQUE_SESSION_ID); typedef HRESULT(__stdcall *RemoveIScsiTargetPortal) (TCHAR*,ULONG,PISCSI_TARGET_PORTAL); typedef HRESULT(__stdcall *ReportSendTargetPortals) (ULONG*, PISCSI_TARGET_PORTAL_INFO);
- 现在,在
CIscsi
类的实现文件(即 .cpp 文件)中,您将拥有头文件中声明的函数的实现,这些函数将允许我们添加目标门户,登录到指定可用目标并将其添加为系统的独立磁盘。之后,注销目标,最后从系统中分离目标门户。您可以将目标设置为持久设备(意味着即使基础机器重新启动,被添加为持久设备的目标仍会连接到系统)。这也在代码中实现。所有其他功能都可以根据类似的指南添加,前提是已系统地执行了这些步骤。要按调用顺序添加的第一个函数是“
AddIscsiTargetPortal
”,它将负责添加指定的目标门户。此函数将接受以下参数:- 类型(输入参数)“
ipaddress
”,它指定要添加的目标门户的地址。这将是一个string
,即BSTR
- 类型(输入参数)“
username
”,它也将是一个BSTR
,但仅在需要身份验证时才需要,否则将为NULL
。 - 类型(输入参数)“
password
”,同样是一个BSTR
,也仅在需要身份验证时才需要,否则也将为NULL
。 - 类型(输出参数)“
targetlist
”,由于这是一个out
参数,因此在成功添加目标门户后,它将以字符串指针的形式返回目标列表。 - 类型(输出参数)“
status
”,此参数以字符串指针的形式返回,指示函数的执行状态。
STDMETHODIMP CIscsi::AddIscsiTargetPortal( /*[in]*/BSTR bstIPAddress, /*[in]*/BSTR bstUserName ,/*[in]*/BSTR bstPassWord, /*[out]*/ BSTR* bstTargetList , BSTR* bstStatus ) { // Handling Cstring is always better for string manipulation // to include the features CString strFileName(bstIPAddress) ; // if not found do include <atlstr.h> in stdafx.h of the project CString strUserName(bstUserName); // converting username into CString CString strPassWord(bstPassWord); // converting password into CString HRESULT hr = S_OK; if (!m_hMod)// if handle to library is not found { CString strTemp1 = _T("Failed"); // returning status only //if this is used as a part of DLL *bstStatus = strTemp1.AllocSysString(); return S_FALSE; } else { HMODULE hMod = m_hMod; GetSessionList fpGetSessionlist = NULL; // making a function pointer ISCSI_SESSION_INFO m_sessionInfo[250]; if (bstIPAddress == NULL) { return S_FALSE; } else { CString strTargetList; if (((strFileName.GetString()!= NULL) && (strFileName.GetLength() > 0) ) ) { AddTargetPortal fpGetTargetPortal = NULL; // taking the process address out of the // function available in 'iscsidsc.dll' fpGetTargetPortal = ( AddTargetPortal )GetProcAddress(hMod, "AddIScsiSendTargetPortalW"); if ( fpGetTargetPortal!= NULL ) { // Initializing the login options required // this structure is required for successfully adding the target portals ISCSI_LOGIN_OPTIONS sLoginOptions ; sLoginOptions.Version = ISCSI_LOGIN_OPTIONS_VERSION; sLoginOptions.AuthType = ISCSI_NO_AUTH_TYPE; // can select on your choice out of the options available sLoginOptions.DataDigest = ISCSI_DIGEST_TYPE_NONE ; // on MSDN sLoginOptions.DefaultTime2Retain = 10; // can be of your choice sLoginOptions.DefaultTime2Wait = 10; // can be of your choice sLoginOptions.HeaderDigest =ISCSI_DIGEST_TYPE_NONE ; //None should be there sLoginOptions.InformationSpecified = ISCSI_LOGIN_OPTIONS_DEFAULT_TIME_2_RETAIN | ISCSI_LOGIN_OPTIONS_AUTH_TYPE | ISCSI_LOGIN_OPTIONS_DATA_DIGEST | ISCSI_LOGIN_OPTIONS_DEFAULT_TIME_2_WAIT | ISCSI_LOGIN_OPTIONS_HEADER_DIGEST ; sLoginOptions.LoginFlags = ISCSI_LOGIN_FLAG_MULTIPATH_ENABLED; sLoginOptions.MaximumConnections = 1; // depending on your choice sLoginOptions.UsernameLength = NULL;// 2; sLoginOptions.Username =/* NULL;*/(UCHAR*) strUserName.GetString(); sLoginOptions.Password = /*NULL;*/(UCHAR*) strPassWord.GetString(); sLoginOptions.PasswordLength = NULL ; // Initializing of target Portals are required // setting values of sTargetPortal to be given to the function pointer ISCSI_TARGET_PORTAL sTargetPortal; memset(sTargetPortal.Address ,0, 256); sTargetPortal.Socket = NULL; memset(sTargetPortal.SymbolicName ,0, 256); USHORT uSocket = 3260 ;// usually this port is used for Iscsi purposes CString cIpAddress = strFileName.GetString();// retrieving the IP address of //target portal CString cSymbolicName; wcscpy(sTargetPortal.Address, cIpAddress.GetString()); wcscpy(sTargetPortal.SymbolicName, cSymbolicName.GetString()); sTargetPortal.Socket = uSocket; hr = fpGetTargetPortal( NULL, ISCSI_ALL_INITIATOR_PORTS , &sLoginOptions,NULL ,&sTargetPortal);// calling functional pointers if( hr == S_OK ) { // calling to get the list of all available targets CString strtemp = reportZiscsiTargets(); // returning list of all targets available *bstTargetList = strtemp.AllocSysString(); CString strTemp1 = _T("Success"); // returning the status only in case if this is in a DLL *bstStatus = strTemp1.AllocSysString(); return S_OK; } else { // calling to get the list of all available targets CString strtemp = reportZiscsiTargets(); // returning list of all targets available *bstTargetList = strtemp.AllocSysString(); CString strTemp1 = _T("Failed"); // returning the status only in case if this is in a DLL *bstStatus = strTemp1.AllocSysString(); return S_FALSE; } } else { // do error handling here } } else { // calling to get the list of all available targets WCHAR* strtemp = reportZiscsiTargets(); // returning the status only in case if this is in a DLL *bstTargetList = strtemp; return S_FALSE; } } } // this function is responsible for fetching all available ISCSI targets in the // network whose target portals have been added to the machine WCHAR* CIscsi::reportZiscsiTargets() { HRESULT hr = S_OK; HMODULE hMod = m_hMod; CString strTargetList; ReportTargets fpReportTargets =NULL; CString tTargets; // getting the procedure address of ReportTargets function // available with iscsidsc.dll fpReportTargets = ( ReportTargets )GetProcAddress(hMod, "ReportIScsiTargetsW"); if(fpReportTargets != NULL) { // size can be allocated according to your need // but this is sufficient for the purpose ULONG uBuffSizeForTargets = 2048 ; TCHAR tBuff[2048]; hr = fpReportTargets(TRUE,&uBuffSizeForTargets, tBuff/*(TCHAR*)tTargets.GetString()*/); if(hr == S_OK) { for (int i = 0 ; i< uBuffSizeForTargets;i++) { if (tBuff[i] != '\0') { tTargets.AppendChar(tBuff[i]); } else if ( tBuff[i] == '\0' && tBuff[i+1] != 0 ) { tTargets.Append(_T("$*$")); } } //return (WCHAR*)tTargets.GetString(); memset(wchRetVal,0,4096); wcscpy(wchRetVal,tTargets.GetString()); // test= retStr; return wchRetVal; } else { return NULL; } } else { return NULL; } }
- 类型(输入参数)“
- 现在,您已经添加了目标门户,您需要登录到您希望将其作为独立磁盘或驱动器添加到基础机器的指定目标。这可以像下面所示那样非常简单地完成。如果 Windows 识别该格式,此函数将把 iSCSI 目标添加到机器。
// this function takes the target name which is unique and returns the // status...(status should be used only when using the function as a DLL ) STDMETHODIMP CIscsi::LogOnToTargetPortal( BSTR bstTargetIdName, BSTR* bstStatus ) { HRESULT hr = NULL; HMODULE hMod =NULL; UINT uCountForLogicalDrives = 0; if (!m_hMod) { CString strTemp = _T("Failed"); *bstStatus = strTemp.AllocSysString(); return S_FALSE; } hMod = m_hMod; CString strDriveListbeforeLogin ; CString strDriveListAfterLogin ; // get all mapped drives (in form of bits) DWORD iLogicalDrives = ::GetLogicalDrives(); for (int j=1, i=0; i< 26; i++) { // is drive bit set? if (((1 << i) & iLogicalDrives) != 0) { // Add drive to list CString str; str.Format(_T("%c"), i+'A'); strDriveListbeforeLogin.Append(str); strDriveListbeforeLogin.Append(_T(",")); j++; uCountForLogicalDrives++; }// finally we have all the drives available on the system // before we have actually established the connection } CString strTargetSelected(bstTargetIdName); LogonIscsiTarget fpLogonIscsiTarget = NULL; ISCSI_UNIQUE_SESSION_ID pUniqueSessionId; pUniqueSessionId.AdapterSpecific = NULL; pUniqueSessionId.AdapterUnique = NULL; ISCSI_UNIQUE_CONNECTION_ID pUniqueConnectionId; pUniqueConnectionId.AdapterSpecific = NULL; pUniqueConnectionId.AdapterUnique = NULL; fpLogonIscsiTarget = ( LogonIscsiTarget )GetProcAddress(hMod, "LoginIScsiTargetW"); if(fpLogonIscsiTarget != NULL) { // here there are many options available for ways to login on // MSDN ..I have chosen the most basic of them// also targets // can be added in a persistent manner too hr = fpLogonIscsiTarget((TCHAR*)strTargetSelected.GetString() /*tTargets.GetString()*/, false,NULL ,ISCSI_ANY_INITIATOR_PORT,NULL/*&sTargetPortal*/, NULL,NULL,NULL/*&sLoginOptions*/,NULL, NULL,false,&pUniqueSessionId,&pUniqueConnectionId ); if( hr == S_OK ) { CString strTemp = _T("Success"); *bstStatus = strTemp.AllocSysString(); SetCursor(LoadCursor(NULL, IDC_APPSTARTING)); // Added deliberately as windows takes some time // to add the target as a drive Sleep(10000); UINT uCountForLogicalDrivesAfterLogin = 0; SetCursor(LoadCursor(NULL, IDC_ARROW)); // get all mapped drives (in form of bits) DWORD iLogicalDrivesAfterlogin = ::GetLogicalDrives(); for (int j=1, i=0; i< 26; i++) { // is drive bit set? if (((1 << i) & iLogicalDrivesAfterlogin) != 0) { // Add drive to list CString str; str.Format(_T("%c"), i+'A'); strDriveListAfterLogin.Append(str); strDriveListAfterLogin.Append(_T(",")); j++; uCountForLogicalDrivesAfterLogin++; }// checking the number of drives after the target has been added } if (uCountForLogicalDrivesAfterLogin == uCountForLogicalDrives) { if (strDriveListAfterLogin == strDriveListbeforeLogin) { //No Drive has been Added ...Means the //File system remains unknown ::MessageBox(NULL,_T( "The logged on ISCSI target could" + "not be mounted as it has an unknown" + "format"),_T("CIscsi"),MB_OK); return S_OK; } } if (uCountForLogicalDrivesAfterLogin != uCountForLogicalDrives) { if(strDriveListAfterLogin != strDriveListbeforeLogin) { if (strDriveListAfterLogin.GetLength() > strDriveListbeforeLogin.GetLength()) { strDriveListAfterLogin.Replace (strDriveListbeforeLogin.GetString(),_T("")); WCHAR* tempBuff = (WCHAR*) strDriveListAfterLogin.GetString(); strDriveListAfterLogin = tempBuff[0]; CString strMsg ; strMsg.Format(_T( " Logical drive mounted :: %s .\n Do you want to open" + "this drive in Explorer?"), strDriveListAfterLogin.GetString()); tempBuff = NULL; strDriveListAfterLogin.Append(_T(":\\")); if (::MessageBox(NULL,strMsg,_T("Kush_Iscsi"), MB_YESNO) == IDYES) { // This will open the drive on the choice of user ShellExecuteA(NULL, "open", CW2A(strDriveListAfterLogin.GetString()), NULL, NULL, SW_SHOWNORMAL); } } } } return S_OK; } else { CString strTemp = _T("Failed"); *bstStatus = strTemp.AllocSysString(); return S_FALSE; } } else { CString strTemp = _T("Failed"); *bstStatus = strTemp.AllocSysString(); return S_FALSE; } }
- 已登录并执行所有操作后,应用程序现在需要注销此目标。只有当目标已登录时,注销才能成功。因此,首先检查以确保目标已登录,然后执行注销过程。这可以按如下方式进行:
)// will take the target Name on whom the log off function has to be performed STDMETHODIMP CIscsi::LogOffTargetPortal( BSTR bstTargetIdName, BSTR* bstStatus { HRESULT hr = S_OK; GetSessionList fpGetSessionlist = NULL; ISCSI_SESSION_INFO m_sessionInfo[125]; if (!m_hMod) { CString strTemp = _T("Failed"); *bstStatus = strTemp.AllocSysString(); return S_FALSE; } HMODULE hMod = m_hMod; ///Check if the target is already connected or is it not connected // As GetSessionApi is failing and not returning us the required result // we will do a work around and will try to login if the login is not //succeeded it will return -268500929 which will tell us that login has // already been done // and we can remove it but if login succeeds we will immediately log out // using the session id we got and /// will give a msg that the machine is not connected.. CString strTargetSelected (bstTargetIdName); LogonIscsiTarget fpLogonIscsiTarget = NULL; ISCSI_UNIQUE_SESSION_ID pUniqueSessionId; pUniqueSessionId.AdapterSpecific = NULL; pUniqueSessionId.AdapterUnique = NULL; ISCSI_UNIQUE_CONNECTION_ID pUniqueConnectionId; pUniqueConnectionId.AdapterSpecific = NULL; pUniqueConnectionId.AdapterUnique = NULL; fpLogonIscsiTarget = ( LogonIscsiTarget )GetProcAddress(hMod, "LoginIScsiTargetW"); if(fpLogonIscsiTarget != NULL) { hr = fpLogonIscsiTarget((TCHAR*)strTargetSelected.GetString() /*tTargets.GetString()*/, false,NULL ,ISCSI_ANY_INITIATOR_PORT,NULL/*&sTargetPortal*/,NULL, NULL,NULL/*&sLoginOptions*/,NULL, NULL,false,&pUniqueSessionId,&pUniqueConnectionId ); if(hr == S_OK) { LogOutOfIscsiTarget fpLogOut; fpLogOut = ( LogOutOfIscsiTarget )GetProcAddress(hMod, "LogoutIScsiTarget"); if(fpLogOut) { hr = fpLogOut( &pUniqueSessionId); if (hr == S_OK) { CString strTemp = _T("Target Not Connected"); *bstStatus = strTemp.AllocSysString(); return S_FALSE; } else { // do error handling here } } } else if (hr == -268500929 || hr != S_OK ) { fpGetSessionlist = ( GetSessionList )GetProcAddress(hMod, "GetIScsiSessionListW"); // getting ISCSI session list if ( fpGetSessionlist != NULL ) { ULONG uBuffSize = sizeof(m_sessionInfo); ULONG uSessionCount = 0; hr = fpGetSessionlist(&uBuffSize,&uSessionCount, m_sessionInfo); if( hr == S_OK && uSessionCount != 0) { CString strTemp; int iCmp = 1; for ( int i = 0 ; i < uSessionCount ; i ++ ) { strTemp.Empty(); strTemp = m_sessionInfo[i].TargetName; iCmp = wcscmp(m_sessionInfo[i].TargetName, strTargetSelected.GetString()); if (iCmp == 0) { LogOutOfIscsiTarget fpLogOut; fpLogOut = ( LogOutOfIscsiTarget )GetProcAddress(hMod, "LogoutIScsiTarget"); if(fpLogOut) { hr = fpLogOut( &m_sessionInfo[i].SessionId); if (hr == S_OK) { CString strTemp = _T("Success");//returned //only when making a DLL *bstStatus = strTemp.AllocSysString(); return S_OK; } else { CString strTemp = _T("Failed"); *bstStatus = strTemp.AllocSysString(); ::MessageBox(NULL,_T( "The specified Target could not be logged" + "off as the drive associated with this target" + "might be in use.\n Close all the windows" + "opened in the Explorer and try again "), _T("kush_Iscsi"),MB_OK|MB_ICONINFORMATION); // frankly speaking I could not find any other // way to detect if a drive is // opened in explorer //with none of its file in operation return S_FALSE; } } else { // error handling } } else { // error handling } } } else { // error handling CString strTemp = _T("Failed"); *bstStatus = strTemp.AllocSysString(); return S_FALSE; } } } } else { // error handling CString strTemp = _T("Failed"); *bstStatus = strTemp.AllocSysString(); return S_FALSE; } }
- 最后,如果用户想完全删除已添加的目标门户,他可以按照以下方式自由执行。以下是代码说明:
// this takes the IP address of the target portal to be removed from the // added list of target portals STDMETHODIMP CIscsi::RemoveIscsiTargetPortal( /*[in]*/BSTR bstIPAddress,BSTR* bstStatus ) { HRESULT hr = S_OK; if (!m_hMod)// if no handle return from here { // this has to be done only in case when a DLL is made and status // has to be returned in form of string CString strtemp = _T("Failed"); *bstStatus = strtemp.AllocSysString(); return S_FALSE; } HMODULE hMod = m_hMod; CString strListText(bstIPAddress); // Compare the IP selected with the addresses of ISCSI_TARGET_INFO // By using Report Target Portal ReportSendTargetPortals fpSendTargetportals = NULL; // getting the procedure address of function available called // ReportIScsiSendTargetPortals fpSendTargetportals = ( ReportSendTargetPortals )GetProcAddress(hMod, "ReportIScsiSendTargetPortalsW"); if ( fpSendTargetportals ) { ULONG uPortalCount = 0; //CString strTargetAddress = NULL; // getting the count of target portals for correct allocation of memory hr = fpSendTargetportals(&uPortalCount,NULL/*&pTargetPortalInfo*/); if (hr == S_OK || hr == 122) { ISCSI_TARGET_PORTAL_INFO *pTargetPortalInfo = new ISCSI_TARGET_PORTAL_INFO [uPortalCount]; ISCSI_TARGET_PORTAL pFinalTargetPortal; ULONG uPortNumber = 0; int iCount = 0; // now calking to get the list of target portals available hr = fpSendTargetportals(&uPortalCount,pTargetPortalInfo); if (hr == S_OK && uPortalCount > 0) { for ( int i = 0 ; i < uPortalCount ; i++ ) { // comparing as we have to remove the specified target portal int iCmp = wcscmp(pTargetPortalInfo[i].Address, strListText.GetString()); if ( iCmp == 0 )// match found { wcscpy( pFinalTargetPortal.Address , pTargetPortalInfo[i].Address ); pFinalTargetPortal.Socket = pTargetPortalInfo[i].Socket ; wcscpy( pFinalTargetPortal.SymbolicName , pTargetPortalInfo[i].SymbolicName ); // copying all essential information uPortNumber = pTargetPortalInfo[i].InitiatorPortNumber; iCount = i; break; } else { //keep comparing } } RemoveIScsiTargetPortal fpRemoveIscsiTargetPortal = NULL; fpRemoveIscsiTargetPortal = ( RemoveIScsiTargetPortal )GetProcAddress(hMod, "RemoveIScsiSendTargetPortalW"); if ( fpRemoveIscsiTargetPortal ) { // finally calling the function to remove the specified target // portal by supplying the correct details with target portal hr = fpRemoveIscsiTargetPortal( NULL, uPortNumber,&pFinalTargetPortal); if (hr == S_OK ) { CString strtemp = _T("Success"); // returned in case of DLL *bstStatus = strtemp.AllocSysString(); return S_OK; } else { CString strtemp = _T("Failed");// returned in case of DLL *bstStatus = strtemp.AllocSysString(); return S_FALSE; } } else { // logging and error handling } } } } }
结论
因此,这些是可以通过 Microsoft 提供的强大工具实现的基本功能。iSCSI 还可以做很多其他事情,但此处未涵盖,因为这只是一个关于 Windows 上 iSCSI 用法的基本实现的入门教程。我希望我从开发者的角度和开发本身方面都涵盖了基本解释。如有任何疑问,请随时发送电子邮件至 tiwari.kushagra@gmail.com。我会尽快回复您。