Series60套接字使用指南
一、关键组件:
1、 套接字服务器:提供对套接字协议的共享且通用性的访问。
2、 客户端API:提供对套接字服务器的受控访问。
3、 服务器插件系统:这里称为协议模块或PRT。
二、客户端和服务器:
套接字表示两个参与者之间连接的端点,两个参与者分别被称为服务器和客户端。服务器被动等待到来的连接,而客户端则主动请求与服务器建立连接。
三、断开式和连接式套接字:
1、 断开式:
断开式套接字把数据包(数据包)简单地从一个参与者传递到另一个参与者,而没有在两个参与者之间进行会话的概念。这导致:
a、 每次向断开式套接字写入数据时,都必须指定目的参与者的地址。
b、 类似地,从断开式套接字读取数据时,每次读取都必须指定发送者的地址。
c、 断开式套接字不保证数据包将按照发送时相同的次序到达目的地。
类似于向一个朋友发送一系列信件。
2、 连接式(面向连接):
连接式套接字保证了在两个参与者之间建立会话的概念,因此只需在首次连接时一次性指定地址,并且数据报会按次序到达。类似于给一个朋友打电话。这种套接字的特点是:建立和使用效率较低。
注意:Series60同时支持连接式和断开式套接字,而且两者使用的类完全相同,例如Rsocket和RsocketServ。区别仅在于建立和配置套接字的方式,以及之后用于发送和接收数据的Rsocket成员函数。
四、连接式套接字:
这种套接字要比断开式常用,但是以效率的少量降低为代价,提供了两个端点间的可靠数据流传输,使开发人员将精力集中于数据本身,而不是传输数据的机制。
1、 连接symbian OS的Socket服务器:
无论是客户端还是服务器,所有Series60套接字编程的第一步都是连接到symbian OS套接字服务器。这比串行通信所需的初始化简单的多,不需要预先装载驱动器,也不需要显式启动服务器。
Tint err=iSocketServer.Connect();
If(err!=KerrNone && err!=KErrAlreadyExists)
{
User::Leave(err)
}
其中:上面的Connect()函数的定义是:
TInt Connect(TUint aMessageSlots=KESockDefaultMessageSlots);
在这里提供了一个默认的参数值,即如果使用此函数时不指定参数值的话,就会使用这个缺省的参数。
在这里,参数aMessageSlots代表所要连接的服务器,可以是系统定义的其他服务器,也可以是程序员自己编写的服务器。
包含客户端套接字的应用程序必须连接到Symbian OS套接字服务器以创建套接字。接下来需要再执行两个步骤,首先,作为健全性检查,应该确保套接字服务器至少支持一种协议(即提供一个或多个PRT插件模块)
Tuint numProtocols;
err=iSocketServer.NumProtocols(numProtocols);
User::LeaveIfError(err);
最后需要获取与希望使用的协议相关的信息。根据名字查找协议并获取描述:
_LIT(KPotocol,”RFCOMM”);
TprotocolName protocolName(KProtocol);
err=iSocketServer.FindProtocol(protocolName,iProtocolInfo);
User::LeaveIfError(err);
2、 打开套接字
连接到Symbian OS套接字服务器后,即可打开一个套接字。这通过调用Rsocket::Open()执行,该方法带有以下参数:
l RsocketServ& aServer:对一个打开的套接字服务器的会话的引用。
l Tuint addrFamily:表示地址族的常数值。
l Tuint sockType:表示套接字类型的常数值。
l Tuint protocol:表示协议的常数值。
举例:
Tint err;
//创建一个服务器端套接字对象iSocket
Err=iSocket.Open(iSocketServer,iProtocolInfo.iAddrFamily,iProtocolInfo.iSockType,iProtocolInfo.iProtocol);
If(err!=KerrNone&&err!=KErrAlreadyExists)
{
User::Leave(err);
}
注意:打开套接字实际上并未连接它,只是初始化一个套接字对象。
3、 连接服务器套接字:
为把套接字初始化为服务器,需要3个步骤。
a、首先,把该套接字绑定到一个地址和一个端口号,客户端套接字连接到此服务器时需要指定它们。
Const Tuint KportNumber=0x02;
iIrdaSockAddr.setPort(KPortNumber);
iSocket.Bind(iIrdaSocketAddr);
b、把套接字绑定到地址和端口号后,使用Listen()方法令它监听从别处到达的连接。为Listen()方法传递的KsizeOfListenQueue参数指定了监听队列的长度,即拒绝更多请求之前,套接字可以放入队列的未处理连接请求的个数。
此外,需要使用第二个套接字为所有到达的请求服务,因为监听的套接字必须保持空闲,以监视其他到达的连接请求。
Const Tuint KsizeOfListenQueue=1;
iSocket.Listen(KSizeOfListenQueue);
//创建空的套接字iServiceSocket
iServiceSocket.Open(iSocketServer);
c、最后需要调用Accept()接受第一个到达的请求。注意,有可能尚未接收到请求。但Accept() 是一个异步请求,因此它将在接收到请求时最终完成。另一方面,如果其他设备已经发出一个请求,该请求将被挂起至Listen()的监听队列中,导致Accept()快速返回。
任意情况下,调用Accept()的形式都是相同的:
iSocket.Accept(iServicSocket,iStatus);
iState=EAccepting;
SetActive();
在上面的一系列代码片断中,注意:
l 把第二个套接字(iServiceSocket)作为参数传递到Accept(),因为它才是连接建立之后实际用于传输数据的套接字。
l CirSocketEngine类是一个活动对象,为此,把自己的iStatus成员传递到Accept(),随后调用自己的SetActive()方法。连接请求发生时,调用该对象的RunL()方法。
l 使用iState成员在CIrSocketEngine类内实现了一个基本的状态机。这是必不可少的,因为套接字API本质上是异步的――调用RunL()时,必须能判断当前处于哪种状态(例如接受中、读取中、写入中等)。iState是CirSocketsEngine类中这样的状态机中实现的一部分,而不是套接字API的一部分。注意,其他Series60套接字代码可能使用不同的变量名(或类型),不过,一般来说总会在这些行中实现某中状态机。
4、 连接客户端套接字
在最简单的层次中,连接客户端套接字只涉及调用Connect()并指定需要的地址和端口信息。比如把一个TCP/IP套接字连接到已知地址和端口的因特网服务器就属于这种情况。对于红外线、蓝牙,预先不知道目标机器地址,连接前需要有一个搜索阶段,设备会在此时查找范围内为给定协议提供服务(在某个套接字监听)的其他机器。
(1)、搜寻
用于搜寻其他设备的类是RhostResolver。同时,该类还被TCP/IP套接字用于把主机名解析为数字ip地址。这和搜寻红外和蓝牙是完全不同的过程,但它们扮演的角色相似,同时RhostResolver类确保所有套接字类型有一致的API集。打开RhostResolver对象的方法与打开Rsocket的方法相同:调用Open()方法,并传递一个打开的套接字服务器会话、一个地址族和一个协议。
搜寻有可能需要较长的时间,因此,对应的API(RhostResolver::GetByName())是异步的。CirSocketsEngine类本身是一个活动对象,将会发出这些异步请求并处理它们的完成。启动搜寻阶段所需的代码如下:
const Tuint KnumOfDevicesToDiscover=1;
Tint err;
THostName hostname;
TPckgBuf<TUint> buf (KNumOfDevicesToDiscover);
iSocket.SetOpt(KdiscoverySlotsOpt,KleverIrlap,buf);
err=iHostResolver.Open(iSocketServer,iProtocolInfo.iAddrFamily,iProtocolInfo.iProtocol);
User::LeaveIfError(err);
iState=EDiscovering;
iHostResolver.GetByName(hostname,iNameEntry,iStatus);
SetActive();
这段代码最终调用异步函数iHostResolver::GetByName(),并传入自己的iStatus成员,启动搜索过程,同时把自己设置为活动。这使得RunL()方法将在搜索结束时被调用,注意同时相应的设置了iState变量,因此RunL()将能正确地判断正在处理的是哪种请求。
注意:可以通过调用RhostResolver::Cancel()取消GetByName()以及RhostResolver上的其他异步调用。
(2)连接
活动对象处理GetByName()的调用成功结束时,iNameEntry成员将包含搜寻到的某个有效服务器机器的地址。之后,使用该地址进行连接,如下:
iHostResolver.Close();
iIrdaSockAddr=TirdaSockAddr(iNameEntry().iAddr);
iIrdaSockAddr.SetPort(KPortNumber);
iState=Econnecting;
iSocket.Connect(iIrdaSockAddr,iStatus);
SetActive();
Connect()也是一个异步函数。注意,即使搜索阶段成功,此函数仍有可能失败,例如服务器在被搜寻到之后移动到了范围之外。