一、概述
軟件接口是實現一個系統跟另外系統進行信息交互的橋梁,在不同的系統之間,根據系統的關聯程度的不同存在緊耦合和松耦合兩種:緊耦合要求接口響應反應快,消息不能阻塞;松耦合對響應反應要求比較低。本人主要討論緊耦合接口通訊實現,在目前應用中,Socket、中間件、SOAP等都用相應的應用,但是應用中發現各通訊方式有自己固有的特征,"適合的才是最好的",這是真理。
在接口和系統信息交互的過程中,兩種模式使用得很普遍:同步調用和異步調用,同步調用要求接口發出請求消息後必須等待服務端系統的應答消息,接口阻塞直至超時;異步調用則發出請求消息後,接口可以從事其它處理,定時輪詢服務端應答消息和消息或事件通知。同步方式簡單,但是很容易造成接口阻塞,造成消息積壓超時。
二、技術實現
1、Socket通訊
Socket通訊相對來說是很古老的通訊方式,也是最常用的通訊方式。Socket通訊有阻塞和非阻塞兩種方式。在同步方式,采用阻塞編程比較簡單,但是為了防止接口阻塞,我們需要設置Socket超時,因此可以使用Socket的SELECT模型(參考如下示例代碼):
CurReceLen=0;
for(;;)
{
iResult=select(0,&fdread,NULL,NULL,&timeout);
if(iResult==0)
{
AfxMessageBox("接收應答消息超時!!!",MB_OK|MB_ICONERROR);
closesocket(Socket);
return FALSE;
}
CurReceLen = recv(Socket, oBuf+ReceLen, len, NO_FLAG_SET);
if((CurReceLen>0) && (CurReceLen != SOCKET_ERROR))
{
oBuf[ReceLen+CurReceLen]=''\0'';
memcpy((char *)&MsgLen,oBuf,sizeof(WORD32));
MsgLen=ntohl(MsgLen);
if(ReceLen+CurReceLen==MsgLen)
{
ReceLen+=CurReceLen;
break;
}
ReceLen+=CurReceLen;
}
}
在異步方式下,采用非阻塞方式實現比較方便,在非阻塞方式下可使用WSAAsyncSelect模型和WSAEventSelect模型:WSAAsyncSelect模型基於消息,WSAEventSelect模型基於事件,下面的示例代碼設置了Socket進行讀寫和關閉操作的消息:
if (status == SOCKET_ERROR)
{
WriteLogFile("Set stream socket module fail!!!IP(%s),
Port(%d) and error(%d)",
GetIPAddr((PeerMap+node)->IPAddr),
(PeerMap+node)->PeerPortNo,
WSAGetLastError());
CloseSocket(TempSocket,__LINE__,__FILE__);
return FALSE;
}
無論使用阻塞方式或非阻塞方式編程,需要重點考慮的一個問題:粘包現象,即應用發送兩個或以上的數據包,在Socket通訊層將數據包合並成一個發送出去,因此接收端收到數據包以後需要對數據包根據應用定義的長度進行拆分,否則導致應用層丟包。
2、中間件通訊
目前中間平台也比較多,我所使用的中間件有BEA的Tuxedo和東方通的TongEasy,TongEasy我就不說了,主要討論一下Tuxedo。BEA Tuxedo系統提供九種通信模式:
⑴.同步Request/Response模式;
⑵.異步Request/Response模式;
⑶.嵌套調用;
⑷.調用轉發;
⑸.會話通信;
⑹.主動消息通告;
⑺.基於事件的通信;
⑻.基於隊列的通信;
⑼.使用事務。
同步請求/應答方式比較簡單,因此在應用中也使用得相當多。但是同步請求/應答方式在服務端服務端反應比較慢時造成阻塞,如果使用了多線程方式,不管Tuxedo采用長連接還是短連接均容易造成線程掛起,不能再進行後續處理,很多情況需要程序重啟。采用異步方式不會造成阻塞,但需要去查詢是否有應答消息,下面的代碼實現了使用異步函數實現同步調用的功能,增加了超時處理,這樣不會導致程序阻塞:
int tpcallex(char *svc, char *idata, long ilen, char **odata, long *olen, long flags, long timeout)
{
const int err_invoke_result = -1;
int iHandler=0;
int iResult=0;
int iTimeOut=timeout;
iHandler = tpacall((char *)svc, (char *)idata, ilen, (long)TPNOBLOCK);
if(iHandler == err_invoke_result)
{
return iHandler;
}
while(iTimeOut>0)
{
iResult = tpgetrply(&iHandler, (char **)odata, olen, (long)TPNOBLOCK);
if(iResult == err_invoke_result)
{
Sleep(10);
iTimeOut -= 10;
continue;
}
break;
}
if(iTimeOut<=0)
{
tpcancel(iHandler);
return err_invoke_result;
}
return iHandler;
}
如果要增加接口的處理能力,使用多線程方式會存在隱患,最好的方式是采用多進程,不過存在如何消息均衡的問題。
3、SOAP通訊
SOAP作為一種協議,同服務端Web Service進行通訊。IBM和微軟提供了SOAP協議的SDK,我使用的是微軟的SOAP Toolkit3.0,這是基於COM的一套組件,因此具有COM的特征,在調用參數的處理,windows和unix順序恰好相反,下面的代碼實現了調用一個Web Service:
if(!m_bFlatType)
{
for(i=paramNum,j=0;i>j;i--,j++)
{
VARIANTARG argTemp;
VariantInit(&argTemp);
argTemp=va[i-1];
va[i-1]=va[j];
va[j]=argTemp;
}
}
params.cArgs = paramNum;
params.rgvarg = va;
params.cNamedArgs = 0;
params.rgdispidNamedArgs = NULL;
hr = SoapConnect.pSoapClient[index]->Invoke(dispidFn,IID_NULL,LOCALE_SYSTEM_DEFAULT,DISPATCH_METHOD,¶ms,&result, 0, 0);
if(FAILED(hr))
{
HandleHResult(_T("Invoke of "+strService+" method failed."), hr);
VariantClear(&result);
for(i=0;i<MAX_PARAM_NUM;i++) VariantClear(&va[i]);
SysFreeString(bstrServiceName);
CoUninitialize();
return FALSE;
}
三、總結
在三種通訊方式中,各有優缺點,但是主要還在於服務端采用什麼技術方案來實現,接口必須對應采用相應的通訊模式。
除了上面的通訊模式,當然還有很多其它的方式,如管道、消息隊列等,目前我在緊耦合的接口中使用得不多。