论软件接口中几种底层通讯的实现
李伟华 msn:liweihua200204@hotmail.com
2005-3-28
一、概述
软件接口是实现一个系统跟另外系统进行信息交互的桥梁,在不同的系统之间,根据系统的关联程度的不同存在紧耦合和松耦合两种:紧耦合要求接口响应反应快,消息不能阻塞;松耦合对响应反应要求比较低。本人主要讨论紧耦合接口通讯实现,在目前应用中,Socket、中间件、SOAP等都用相应的应用,但是应用中发现各通讯方式有自己固有的特征,“适合的才是最好的”,这是真理。
在接口和系统信息交互的过程中,两种模式使用得很普遍:同步调用和异步调用,同步调用要求接口发出请求消息后必须等待服务端系统的应答消息,接口阻塞直至超时;异步调用则发出请求消息后,接口可以从事其它处理,定时轮询服务端应答消息和消息或事件通知。同步方式简单,但是很容易造成接口阻塞,造成消息积压超时。
二、技术实现
1、 Socket通讯
Socket通讯相对来说是很古老的通讯方式,也是最常用的通讯方式。Socket通讯有阻塞和非阻塞两种方式。在同步方式,采用阻塞编程比较简单,但是为了防止接口阻塞,我们需要设置Socket超时,因此可以使用Socket的SELECT模型(参考如下示例代码):
ReceLen=0;
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进行读写和关闭操作的消息:
status = WSAAsyncSelect(TempSocket, hWnd, WSA_READ, FD_READ | FD_CLOSE | FD_WRITE);
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;
}
三、总结
在三种通讯方式中,各有优缺点,但是主要还在于服务端采用什么技术方案来实现,接口必须对应采用相应的通讯模式。
除了上面的通讯模式,当然还有很多其它的方式,如管道、消息队列等,目前我在紧耦合的接口中使用得不多。