上一次介绍了在Symbian平台下如何使用Socket连接指定的网络服务器,这一次,我们继续讲解如何使用Socket进行读写操作。
无论是读操作还是写操作,我们都必须使用之前的SocketConnection类,因为我们要使用该类中的RSocket来进行读写操作,并且,无论是读操作还是写操作,我们都不应该阻碍用户的操作,因此,我们在这里仍旧要使用活动对象来完成功能。
(1)写操作
#ifndef SOCKETWRITER_H
#define SOCKETWRITER_H
// INCLUDES
#include
#include
#include "SocketConnection.h"
// CLASS DECLARATION
/**
* CSocketWriter
* 该类为CSocketConnection的友元对象,即可以直接方位CSocketConnection的私有属性
*/
class CSocketWriter : public CActive
{
public:
~CSocketWriter( );
static CSocketWriter* NewL( CSocketConnection* aSocketConn );
static CSocketWriter* NewLC( CSocketConnection* aSocketConn );
void Write( const TDesC8& aData );
protected:
// from CActive
virtual void RunL( );
virtual void DoCancel( );
virtual TInt RunError( TInt aError );
private:
CSocketWriter( );
void ConstructL( CSocketConnection* aSocketConn );
private:
CSocketConnection* m_SocketConn;//Socket连接对象
TSockXfrLength m_len;//Socket连接中特有的长度对象
};
#endif // SOCKETWRITER_H
#include "SocketWriter.h"
#include "log.h"
#include
CSocketWriter::CSocketWriter( ) :
CActive( EPriorityStandard )
{
// No implementation required
}
CSocketWriter::~CSocketWriter( )
{
Cancel( );
}
CSocketWriter* CSocketWriter::NewLC( CSocketConnection* aSocketConn )
{
CSocketWriter* self = new ( ELeave ) CSocketWriter( );
CleanupStack::PushL( self );
self->ConstructL( aSocketConn );
return self;
}
CSocketWriter* CSocketWriter::NewL( CSocketConnection* aSocketConn )
{
CSocketWriter* self = CSocketWriter::NewLC( aSocketConn );
CleanupStack::Pop( ); // self;
return self;
}
void CSocketWriter::ConstructL( CSocketConnection* aSocketConn )
{
m_SocketConn = aSocketConn;
CActiveScheduler::Add( this );
}
void CSocketWriter::Write( const TDesC8& aData )
{
if ( !IsActive( ) )
{
if ( m_SocketConn )
{
#if DEBUG
LOG( _L("be ready to write,context is \n"));
HBufC* pBuf = HBufC::New( aData.Length( ) );
TPtr buf = pBuf->Des( );
CnvUtfConverter::ConvertToUnicodeFromUtf8( buf , aData );
LOG( _L("%S"),pBuf );
delete pBuf;
#endif
m_SocketConn->m_Socket.Send( aData , 0 , iStatus , m_len );
SetActive( );
}
else
{
LOG( _L("SocketConn is NULL ,so can not write !"));
}
}
else
{
#if DEBUG
LOG( _L("CSocketWriter is busy...") );
#endif
if ( m_SocketConn && m_SocketConn->m_pSocketObserver )
{
m_SocketConn->m_pSocketObserver->OnSocketSent( 0 );
}
}
}
void CSocketWriter::RunL( )
{
if ( iStatus == KErrNone )
{
int len = m_len( );
if ( m_SocketConn && m_SocketConn->m_pSocketObserver )
{
m_SocketConn->m_pSocketObserver->OnSocketSent( len );
}
}
else
{
if ( m_SocketConn )
{
m_SocketConn->DoSocketError( EWrite , iStatus.Int( ) );
}
}
}
void CSocketWriter::DoCancel( )
{
if ( m_SocketConn )
{
m_SocketConn->m_Socket.CancelSend( );
}
}
TInt CSocketWriter::RunError( TInt aError )
{
#if DEBUG
LOG( _L("SocketWriter error happend ! For reason : %d") , aError );
#endif
return 0;
}
从代码就可以看出,写操作是相对容易的,只要通过CSocketConnection中的RSocket对象进行发送处理即可,然后到RunL中取得结果,并通过观察者模式返回给观察者结果即可。(如果对观察者模式不太清楚的朋友,而已在网上找些资料,其实挺容易的)
#ifndef SOCKETREADER_H
#define SOCKETREADER_H
// INCLUDES
#include
#include
#include "SocketConnection.h"
// CLASS DECLARATION
/**
* CSocketReader
* 该类为CSocketConnection的友元,即可以直接访问CSocketConnection的私有属性
*/
class CSocketReader : public CActive
{
public:
~CSocketReader( );
static CSocketReader* NewL( CSocketConnection* aSocketConn );
static CSocketReader* NewLC( CSocketConnection* aSocketConn );
void Read();
protected:
// from CActive
virtual void RunL( );
virtual void DoCancel( );
virtual TInt RunError( TInt aError );
private:
CSocketReader( );
void ConstructL( CSocketConnection* aSocketConn );
private:
CSocketConnection* m_SocketConn;//Socket连接对象
TBuf8 < 512 > m_readBuffer;//数据缓冲块
TSockXfrLength m_len;//Socket连接中特有的长度对象
};
#endif // SOCKETREADER_H
#include "SocketReader.h"
#include "log.h"
CSocketReader::CSocketReader( ) :
CActive( EPriorityStandard )
{
}
CSocketReader::~CSocketReader( )
{
Cancel();
}
CSocketReader* CSocketReader::NewLC( CSocketConnection* aSocketConn )
{
CSocketReader* self = new ( ELeave ) CSocketReader( );
CleanupStack::PushL( self );
self->ConstructL( aSocketConn );
return self;
}
CSocketReader* CSocketReader::NewL( CSocketConnection* aSocketConn )
{
CSocketReader* self = CSocketReader::NewLC( aSocketConn );
CleanupStack::Pop( ); // self;
return self;
}
void CSocketReader::ConstructL( CSocketConnection* aSocketConn )
{
m_SocketConn = aSocketConn;
CActiveScheduler::Add( this );
}
void CSocketReader::Read( )
{
if ( m_SocketConn && m_SocketConn->m_ConnectStatus == EConnected )
{
if ( !IsActive( ) )
{
#if DEBUG
LOG(_L("start to read data from Socket by reader !"));
#endif
m_SocketConn->m_Socket.RecvOneOrMore(
m_readBuffer ,
0 ,
iStatus ,
m_len );
SetActive( );
}
#if DEBUG
else
{
LOG(_L("Reader has been Started !"));
}
#endif
}
#if DEBUG
else
{
LOG(_L("m_SockdetConn pointer or m_SocketConn->m_ConnectStatus != EConnected"));
}
#endif
}
void CSocketReader::RunL( )
{
if ( iStatus == KErrNone && m_SocketConn)
{
if(m_SocketConn->m_pSocketObserver)
{
m_SocketConn->m_pSocketObserver->OnSocketReceived(m_readBuffer);
Read( );
}
#if DEBUG
else
{
LOG(_L("SocketObserver is NULL !"));
}
#endif
}
else
{
m_SocketConn->DoSocketError(ERead,iStatus.Int());
}
}
void CSocketReader::DoCancel( )
{
if(m_SocketConn )
{
m_SocketConn->m_Socket.CancelRead();
}
}
TInt CSocketReader::RunError( TInt aError )
{
#if DEBUG
LOG(_L("SocketReader error happend ! Reason : %d"),aError);
#endif
return 0;
}
读操作中说白了就是在活动对象中不断的调用RSocket对象的RecvOneOrMore()方法,该方法会在有数据时读取数据,没有数据时会等待数据,因此该类也必须是活动对象的,否则,在等待数据的时候就会Block主用户的操作。
由于加入了读写类,因此原先的CSocketConnection也必须做出相应的调整以响应读写操作,这里就把源代码贴给大家
#ifndef SOCKETCONNECTION_H
#define SOCKETCONNECTION_H
// INCLUDES
#include
#include
#include
#include
//#include "SocketReader.h"
// CLASS DECLARATION
#define DEBUG 1
class MSocketObserver //Socket观察者对象
{
public:
virtual void OnSocketConnected( ) = 0; //连接后会被调用,即通知给观察者
virtual void OnSocketReceived( const TDesC8& data ) = 0; //接收数据时被调用,即通知观察者
// bytes: count of bytes sent
virtual void OnSocketSent( TInt bytes ) = 0; //发送数据后会调用,会通知给观察者发送了多少字节
virtual void OnSocketError( ) = 0; //出错时会调用
};
enum
{
ENotConnected, ELookingUp, EConnecting, EConnected
};
enum
{
ERead,EWrite
};
class CSocketConnection : public CActive
{
friend class CSocketReader;
friend class CSocketWriter;
//friend class CSocketWriter;
public:
~CSocketConnection( );
static CSocketConnection* NewL( MSocketObserver* pSocketObserver );
static CSocketConnection* NewLC( MSocketObserver* pSocketObserver );
void ConnectServer( const TDesC& aServerName , TInt aServerPort );
void DisconnectServer( );
void DoSocketError(TInt aType,TInt aErr);
void WriteBySocket(const TDesC8 aData);
protected:
// from CActive
virtual void RunL( );
virtual void DoCancel( );
virtual TInt RunError( TInt aError );
private:
CSocketConnection( );
void SocketConnect( TUint32 aAddr );
void ConstructL( MSocketObserver* pSocketObserver );
private:
RSocketServ m_SocketServer;//用于连接Symbian系统服务中的Soket服务
RSocket m_Socket;//Socket通道
RHostResolver m_Resolver;//寻找其他设备类,用来通过DNS将主机名解析为ip地址
HBufC* m_pServerName;//主机名称
TInt m_ServerPort;
TNameEntry m_NameEntry;//用来从RHostResolver中返回结果到该对象中
TInt m_ConnectStatus;
MSocketObserver* m_pSocketObserver;
CSocketReader* m_pSocketReader;//Socket读取对象
CSocketWriter* m_pSocketWriter;//Socket写入对象
};
#include "SocketConnection.h"
#include "SocketReader.h"
#include "SocketWriter.h"
#include
#if DEBUG
#include "log.h"
#endif
CSocketConnection::CSocketConnection( ) :
CActive( EPriorityStandard )
{
m_ConnectStatus = 0;
}
CSocketConnection::~CSocketConnection( )
{
//m_pSocketReader和m_pSocketWriter必须在m_Socket之前释放掉,
//这是因为这两个对象在释放的时候会调用m_Socket对象
if ( m_pSocketReader && m_pSocketReader->IsActive( ) )
{
m_pSocketReader->Cancel( );
}
if ( m_pSocketWriter && m_pSocketWriter->IsActive( ) )
{
m_pSocketWriter->Cancel( );
}
delete m_pSocketReader , m_pSocketReader = NULL;
delete m_pSocketWriter , m_pSocketWriter = NULL;
m_Socket.Close( );
m_SocketServer.Close( );
m_Resolver.Close( );
delete m_pServerName , m_pServerName = NULL;
}
CSocketConnection* CSocketConnection::NewLC( MSocketObserver* pSocketObserver )
{
CSocketConnection* self = new ( ELeave ) CSocketConnection( );
CleanupStack::PushL( self );
self->ConstructL( pSocketObserver );
return self;
}
CSocketConnection* CSocketConnection::NewL( MSocketObserver* pSocketObserver )
{
CSocketConnection* self = CSocketConnection::NewLC( pSocketObserver );
CleanupStack::Pop( ); // self;
return self;
}
void CSocketConnection::ConstructL( MSocketObserver* pSocketObserver )
{
m_pSocketReader = CSocketReader::NewL( this );
m_pSocketWriter = CSocketWriter::NewL( this );
m_pSocketObserver = pSocketObserver;
CActiveScheduler::Add( this );
//连接服务器
m_SocketServer.Close( );
m_Socket.Close( );
#if DEBUG
LOG( _L("be ready to connect RServer(Socket)...") );
#endif
User::LeaveIfError( m_SocketServer.Connect( ) );
#if DEBUG
LOG( _L("have connected the RServer(Socket) !") );
LOG( _L("be ready to open(init) a pointer of RSocket...") );
#endif
User::LeaveIfError( m_Socket.Open(
m_SocketServer ,
KAfInet ,
KSockStream ,
KProtocolInetTcp ) );
#if DEBUG
LOG( _L("have open(init) the pointer of RSocket !") );
#endif
}
/**
* Socket连接函数
* @TUint32 aAddr: Ip地址
*/
void CSocketConnection::SocketConnect( TUint32 aAddr )
{
m_Socket.Close( );
User::LeaveIfError( m_Socket.Open(
m_SocketServer ,
KAfInet ,
KSockStream ,
KProtocolInetTcp ) );
TInetAddr addr;
addr.SetPort( m_ServerPort );
addr.SetAddress( aAddr );
m_Socket.Connect( addr , iStatus );
m_ConnectStatus = EConnecting;
#if DEBUG
LOG( _L("be ready to connecting Socket !") );
#endif
SetActive( );
}
void CSocketConnection::DisconnectServer( )
{
if ( m_ConnectStatus == EConnected )
{
m_Socket.Close( );
if ( m_pSocketReader && m_pSocketReader->IsActive( ) )
{
m_pSocketReader->Cancel( );
}
m_ConnectStatus = ENotConnected;
}
}
void CSocketConnection::DoSocketError( TInt aType , TInt aErr )
{
DisconnectServer( );
m_SocketServer.Close( );
m_pSocketObserver->OnSocketError( );
if ( aType == ERead && aErr == -25 )
{
return;
}
#if DEBUG
LOG( _L("Socket happend error when runtime [reader or writer ] ! %d,%d") ,
aType , aErr );
#endif
}
void CSocketConnection::WriteBySocket( const TDesC8 aData )
{
if ( aData.Length( ) > 0 && m_pSocketWriter )
{
m_pSocketWriter->Write( aData );
}
}
/**
* 连接函数
* @TDesC& aServerName: 主机名称
* @TInt aServerName: 主机端口号
*/
void CSocketConnection::ConnectServer(
const TDesC& aServerName ,
TInt aServerPort )
{
if ( m_ConnectStatus != ENotConnected ) return;
delete m_pServerName , m_pServerName = NULL;
if ( aServerName.Length( ) > 0 )
{
m_pServerName = aServerName.Alloc( );
}
else
{
m_pServerName = _L("").Alloc( );
}
m_ServerPort = aServerPort;
if ( m_pServerName->Length( ) <= 0 )
{
#if DEBUG
LOG( _L("the ServerName is empty !") );
#endif
return;
}
#if DEBUG
LOG( _L("ServerName is [%S]") , m_pServerName );
LOG( _L("ServerPort is [%d]") , m_ServerPort );
#endif
//通过TInetAddr来判断aServerName是否为IP地址
//如果是ip地址,则直接进入连接过程
//如果不是ip地址,则进入解析过程,通过RHostResolver类将主机名称解析为ip地址
TInetAddr addr;
if ( addr.Input( *m_pServerName ) == KErrNone )
{
//进入连接过程
SocketConnect( addr.Address( ) );
}
else
{
#if DEBUG
LOG( _L("the ServerName is not a IP address !") );
LOG(
_L("be ready to parse ServerName [%S] to IP adress !") ,
m_pServerName );
#endif
m_Resolver.Close( );
m_Resolver.Open( m_SocketServer , KAfInet , KProtocolInetUdp );
m_Resolver.GetByName( *m_pServerName , m_NameEntry , iStatus );
m_ConnectStatus = ELookingUp;
SetActive( );
}
}
void CSocketConnection::DoCancel( )
{
m_Socket.Close( );
m_Resolver.Close( );
}
TInt CSocketConnection::RunError( TInt aError )
{
return 0;
}
void CSocketConnection::RunL( )
{
switch ( m_ConnectStatus )
{
case EConnecting :
{
if ( iStatus == KErrNone )//连接成功
{
#if DEBUG
LOG( _L("connecting is sucessful !") );
#endif
m_ConnectStatus = EConnected;//状态置为已连接状态
if ( m_pSocketObserver )//如果观察者对象存在
{
#if DEBUG
LOG( _L("call Observer OnSocketConnected() !") );
#endif
m_pSocketObserver->OnSocketConnected( );//通知观察者,连接操作完成
}
if ( m_pSocketReader )//如果Reader对象不为空(一般都不为空,这里只是进行安全检查)
{
#if DEBUG
LOG( _L("call reader Read() !") );
#endif
m_pSocketReader->Read( );//读取数据
}
}
else//连接不成功,状态置为未连接状态
{
m_ConnectStatus = ENotConnected;
}
break;
}
case ELookingUp :
{
//搜寻其他设备或连接地址,在Soket应用中,时常需要判断给出的主机地址名是否为一个IP地址,如果不是则需要进行对主机名称解析為IP地址
m_Resolver.Close( );
if ( iStatus == KErrNone )//状态无错误,则证明解析地址成功
{
TNameRecord nameRecord = m_NameEntry( );
m_ConnectStatus = ENotConnected;
//进入连接过程
SocketConnect( TInetAddr::Cast( nameRecord.iAddr ).Address( ) );
#if DEBUG
TBuf < 15 > address;
TInetAddr::Cast( nameRecord.iAddr ).Output( address );
LOG(
_L("Get IP Address [%S]->[%S] sucessfuly !") ,
m_pServerName ,
&address );
#endif
}
else
{
m_ConnectStatus = ENotConnected;
#if DEBUG
LOG(
_L("Can not get IP address from ServerName [%S]") ,
m_pServerName );
#endif
}
break;
}
}
}
自此,整个Socket层的连接与读写就已经完成了,我们实际上现在就可以列用它来发送Http请求了,用例如下:
在通知函数中,利用观察者的通知来调用发送函数,即在连接成功后,发送请求,
void CLearningSocketAppUi::OnSocketConnected( )
{
if ( m_pSocketConn )
{
TFileName tfn;
tfn.Append(
_L("Get http://mail.163.com/ HTTP/1.1\r\nHost: mail.163.com\r\n\r\n") );
HBufC8* pBuf = HBufC8::NewL( tfn.Length( ) * 3 );
TPtr8 buf = pBuf->Des( );
CnvUtfConverter::ConvertFromUnicodeToUtf8( buf , tfn );
m_pSocketConn->WriteBySocket( buf );
delete pBuf;
}
}
CLearningSocketAppUi本身继承了MSocketObserver类,即CLearningSocketAppUi就是一个观察者对象,这样在OnSocketConnected()方法中就可以发送请求了。
当然,做到现在我们还远远不够,后面我们将封装比较简单的HttpConnection类来帮助我们能够更直接的完成Http请求
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/9059159/viewspace-588945/ ,如需转载,请注明出处,否则将追究法律责任。