十五ACE编写简单的通信程序

               

                该程序将演示如何将一个简单结构序列化后发送到网络上,如何从网络上接收到数据后反序列化回结构。

    ACE的C++ WRAPPER FACADE层将网络通信分成三种角色:连接者(ACE_SOCK_Connector)、等待者(ACE_SOCK_Acceptor)和传输者(ACE_SOCK_Stream)。


建立连接

    首先使用ACE_SOCK_Connector::connect连接某个服务器(使用ip地址和端口号),该服务器上使用ACE_SOCK_Acceptor::accept等待外部的连接请求。ACE_INET_Addr类进行管理SOCKET通信使用的IP地址和端口号。当连接建立的时候,连接者和等待者都初始化一个传输者用于通信。

    下面就是连接者如何连接本机的7777端口的服务程序代码:

#include <iostream>

using namespace std;

#include "ace/INET_Addr.h"

#include "ace/SOCK_Stream.h"

#include "ace/SOCK_Connector.h"

int main(void)

{

    ACE_INET_Addr address("127.0.0.1:7777");

    ACE_SOCK_Connector connector;

    ACE_SOCK_Stream stream;

    if(connector.connect(stream,address)==-1)

    {

    cout<<strerror(errno)<<endl;

    }

}

    如果连接成功,connect方法返回0,如果连接失败,返回-1,线程专有的errno变量将被设置对应的错误码,你可以通过strerror函数获取错误信息描述字符串,也可以使用线程安全的版本strerror_r。ACE不使用异常报错,原因之一是早些时候异常并不被所有的C++编译器支持,原因之二是异常对性能仍然有影响,作为高性能底层库ACE仍然采用了C风格进行错误处理。但是你仍然可以在自己的应用逻辑中使用异常,并不会和ACE发生冲突。

    下面是等待者的示例:

#include <iostream>

using namespace std;

#include "ace/INET_Addr.h"

#include "ace/SOCK_Stream.h"

#include "ace/SOCK_Acceptor.h"

int main(void)

{

    ACE_SOCK_Acceptor acceptor;

    //本地端口7777的ACE_INET_Addr对象

    ACE_INET_Addr address;

    address.set(7777);

    //绑定本地端口,并且设置为监听状态

    if(acceptor.open(address)==-1)

    {

        cout<<strerror(errno)<<endl;

    }

    ACE_SOCK_Stream stream;

    if(acceptor.accept(stream)==-1)

    {

        cout<<strerror(errno)<<endl;

    }

}


    注意,ACE_SOCK_Acceptor::accept和ACE_SOCK_Connector::connect方法都可以接收一个ACE_TIME_Value*参数。该参数缺省直为NULL,就像上面的两个示例,表示除非建立连接,否则不会返回;如果我们创建ACE_TIME_Value time(0,0)对象作为参数,则表示方法不会阻塞,如果不能立刻建立连接,就返回-1,并且errno为EWOULDBLOCK;如果我们创建ACE_TIME_Value time(5,0)对象作为参数,就表示方法会最多等待5秒钟,如果5秒钟内还没有建立连接,就返回-1,并且errno为ETIME.

    ACE_SOCK_Acceptor对象没有状态,因此多线程可以在不锁定的情况下共享该对象。

数据传输

    通常数据传输的过程是将对象中的数据按照某种格式序列化成连续的字节流,然后发送到网络上,当另一端接收到字节流后,按照此格式反序列化成对象。当连接建立好后,通信双方都有两个可以发送和接收数据的ACE_SOCK_Stream对象。该对象提供了发送和接收的方法。send_n/recv_n用于发送和接收确定数量的字节流,如果没有发送或者接收完,该方法将阻塞。而send/recv就不保证这一点,可能实际发送或者接收的数据比参数指定的少,该方法不会阻塞,而是返回实际发送或者接收的数据大小。send/recv方法实际是从父类ACE_SOCK_IO继承而来的。

    网络传输的一种高效的方法是集中写和分散读。不同缓冲区的数据没有必要拷贝到一起,就可以直接按照次序一次型的发送出去。从网络另一端收到后,有可以分散的写到不同的缓冲区中。这就避免了数据复制的开销。ACE_SOCK_Stream的方法recvv_n/sendv_n方法就提供了这个机制。我们后面的示例将演示这个方法的使用。

    如果我们使用TCP/IP协议发送数据,TCP/IP协议有一个Nagle算法。该算法将缓存小数据,减少网络发送的次数,从而避免过多通信的开销。在某些情况下,我们需要关闭该算法,让我们的数据能够立刻发送出去。ACE_SOCK_Stream的set_option方法使用参数TCP_NODELAY可以关闭这个算法。另一个方法是当我们使用sendv_n方法时,也会强制数据立刻发送。

    下面的示例将一个结构SHMRecord初始化,并序列化到ACE_OutputCDR对象中。然后使用sendv_n方法将数据发出。


#include <iostream>

using namespace std;

#include "ace/INET_Addr.h"

#include "ace/SOCK_Stream.h"

#include "ace/SOCK_Connector.h"

#include "ace/CDR_Stream.h"


class SHMRecord

{

public:

    SHMRecord():pData_(NULL){}

 

    ACE_UINT16 type_;

    ACE_UINT32 offset_;

    void* pData_;

    ACE_UINT32 dataLength_;


    size_t size() const

    {

        return 2+4+4+dataLength_;

    }

 

    ~SHMRecord()

    {

    if(pData_!=NULL)

        delete[] static_cast<char*>(pData_);

    }

};


int operator<<(ACE_OutputCDR & cdr,SHMRecord const& record)

{

    cdr<<record.type_;

    cdr<<record.offset_;

    cdr<<record.dataLength_;

    cdr.write_char_array(static_cast<char*>(record.pData_),record.dataLength_);

    return cdr.good_bit();

}


int operator>>(ACE_InputCDR & cdr,SHMRecord & record)

{

    cdr>>record.type_;

    cdr>>record.offset_;

    cdr>>record.dataLength_;

    record.pData_=new char[record.dataLength_]();

    cdr.read_char_array(static_cast<char*>(record.pData_),record.dataLength_);

    return cdr.good_bit();

}




int main(void)

{

    ACE_INET_Addr address("127.0.0.1:7777");

    ACE_SOCK_Connector connector;

    ACE_SOCK_Stream stream;

    if(connector.connect(stream,address)==-1)

    {

    cout<<strerror(errno)<<endl;

    }


    SHMRecord record;

    record.type_=1;

    record.offset_=2;

    record.pData_=new char[4]();

    record.dataLength_=4;

    strcpy(static_cast<char*>(record.pData_),"hih");


    const size_t size=record.size()+ACE_CDR::MAX_ALIGNMENT;

    ACE_OutputCDR payload(size);

    payload<<record;


    //create cdr header for this data

    ACE_OutputCDR header(ACE_CDR::MAX_ALIGNMENT+8);

    header<<ACE_OutputCDR::from_boolean(ACE_CDR_BYTE_ORDER);

    header<<ACE_CDR::ULong(size);

 

    iovec iov[2];

    iov[0].iov_base=header.begin()->rd_ptr();

    iov[0].iov_len=8;//如果使用ACE_LACKS_CDR_ALIGNMENT宏,8要被替换成实际的大小

    iov[1].iov_base=payload.begin()->rd_ptr();

    iov[1].iov_len=size;


    stream.sendv_n(iov,2);

    cout<<record.type_<<endl;

    cout<<record.offset_<<endl;

    cout<<static_cast<char*>(record.pData_)<<endl;

    cout<<record.dataLength_<<endl;

}

    ACE 提供了ACE_OutputCDR 和ACE_InputCDR 类,是针对网络程序经常遇到的将对象数据序列化到字节流和从字节流中反序列化到对象的情况。你可以提供自己的operator<< 和operator>> 操作,就像上面的例子一样。
    为什么我在构造ACE_OutputCDR对象的时候总是在实际缓冲区大小上加上ACE_CDR::MAX_ALIGNMENT呢?因为如果我在编译时没有在config.h中用宏 #defineACE_CDR_IGNORE_ALIGNMENT,那么ACE会自动进行边界调整,使得变量按照四字节对齐(32位处理器)。如果缓冲区中的起始位置没有对齐,第一个变量值通常都会往缓冲区头部的后面若干字节开始写入。这时候,缓冲区如果没有足够的空间,写数据就会越界。因此ACE_CDR::MAX_ALIGNMENT代表的8字节,能够保证我们创建足够大小的缓冲区应付这种情况。

    这种方式和支持标准C++流的方式是一样的。那么,为什么不直接使用标准C++流呢?因为ACE所支持的平台很多,有些编译器不支持标准C++流。并且据我个人的体验,标准C++流在内存管理上是封装的,你不可能通过公有方法获得内部关里的缓冲区的指针,除非自己定义自己的派生类,这并不容易。还有一个原因是不同编译器和不同的硬件使用了不同的字节顺序(大尾数法和小尾数法)。正确的使用ACE的cdr类就可以保证各种环境下都能使用,因为它在内部使用了CORBA公共数据表示的格式。在这个示例程序里,我们实际上创建了两个ACE_OutputCDR对象,一个用来表示数据头,一个存放实际结构中的数据。数据头中前4个字节存放了一个布尔值,表示本机的字节顺序,后面四个字节表示第二个对象的实际长度。这里我没有使用宏#define ACE_LACKS_CDR_ALIGNMENT,否则数据会被紧缩。布尔值将只有一个字节大小。

    还有一种方法,本例没有提供,这里说明一下。首先在config.h文件中加上宏#define ACE_ENABLE_SWAP_ON_WRITE,然后重新编译ACE。然后创建CDR对象时明确就是通过网络字节顺序(大尾数法):

ACE_OutputCDR ocdr(&mb, ACE_CDR::BYTE_ORDER_BIG_ENDIAN);  

ACE_InputCDR icdr(&mb, ACE_CDR::BYTE_ORDER_BIG_ENDIAN); 

 

    对于基本的数值类型,各个平台也有可能有长度的差异,比如int究竟是16,32还是64。所以这里使用了ACE提供的基本数值类型,比如ACE_UINT32。

 

    本例中,接收数据时首先接收固定长度的头对象,取得字节顺序标志后,调整字节顺序,然后获取实际长度,根据该长度接收第二个ACE_OutputCDR对象存放的实际数据。

下面的例子演示了如何接收发送来的数据。



int main(void)

{

    ACE_SOCK_Acceptor acceptor;

    ACE_INET_Addr address;

    address.set(7777);

    if(acceptor.open(address)==-1)

    {

        cout<<strerror(errno)<<endl;

    }

    ACE_SOCK_Stream stream;

    if(acceptor.accept(stream)==-1)

    {

        cout<<strerror(errno)<<endl;

    }

 


    auto_ptr<ACE_Message_Block> spBlock(new ACE_Message_Block(ACE_DEFAULT_CDR_BUFSIZE));

    ACE_CDR::mb_align(spBlock.get());

    if(stream.recv_n(spBlock->wr_ptr(),8)==8)//receive the header of CDR

    {

    //parse the cdr header

    spBlock->wr_ptr(8);

    ACE_InputCDR cdr(spBlock.get());

    ACE_CDR::Boolean byte_order;

    cdr>>ACE_InputCDR::to_boolean(byte_order);

    cdr.reset_byte_order(byte_order);

    ACE_CDR::ULong length;

    cdr>>length;

 

    //receive the data from master

    spBlock->size(length+8+ACE_CDR::MAX_ALIGNMENT);

    if(stream.recv_n(spBlock->wr_ptr(),length)==length)

    {

    spBlock->wr_ptr(length);

    //必须重新创建一个CDR对象,否则解析不正确

    ACE_InputCDR cdr2(spBlock.get());

    ACE_CDR::Boolean byte_order;

    cdr2>>ACE_InputCDR::to_boolean(byte_order);

    cdr2.reset_byte_order(byte_order);

    ACE_CDR::ULong length;

    cdr2>>length;


    auto_ptr<SHMRecord> spRecord(new SHMRecord);

    cdr2>>*spRecord;

    cout<<spRecord->type_<<endl;

    cout<<spRecord->offset_<<endl;

    cout<<static_cast<char*>(spRecord->pData_)<<endl;

    cout<<spRecord->dataLength_<<endl;

}

}

}


    ACE_Message_Block类用来管理数据,内部有一个指向ACE_Data_Block对象的指针,ACE_Data_Block类管理实际的缓冲区数据。这种设计允许多个ACE_Message_Block对象共享同一个ACE_Data_Block对象,对于效率的提高很有帮助。多个ACE_Message_Block对象可以组成一个链表(双向或者单向)。

    在上面的例子中,我们 创建了一个默认大小的ACE_Message_Block对象,然后将接收的数据写入ACE_Data_Block的缓冲区中,并且移动写指针的位置。ACE_InputCDR通过和ACE_Message_Block对象关联来读取缓冲区的数据。

 

           

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

已标记关键词 清除标记
相关推荐
【为什么还需要学习C++?】 你是否接触很多语言,但从来没有了解过编程语言的本质? 你是否想成为一名资深开发人员,想开发别人做不了的高性能程序? 你是否经常想要窥探大型企业级开发工程的思路,但苦于没有基础只能望洋兴叹?   那么C++就是你个人能力提升,职业之路进阶的不二之选。 【课程特色】 1.课程共19大章节,239课时内容,涵盖数据结构、函数、类、指针、标准库全部知识体系。 2.带你从知识与思想的层面从0构建C++知识框架,分析大型项目实践思路,为你打下坚实的基础。 3.李宁老师结合4大国外顶级C++著作的精华为大家推出的《征服C++11》课程。 【学完后我将达到什么水平?】 1.对C++的各个知识能够熟练配置、开发、部署; 2.吊打一切关于C++的笔试面试题; 3.面向物联网的“嵌入式”和面向大型化的“分布式”开发,掌握职业钥匙,把握行业先机。 【面向人群】 1.希望一站式快速入门的C++初学者; 2.希望快速学习 C++、掌握编程要义、修炼内功的开发者; 3.有志于挑战更高级的开发项目,成为资深开发的工程师。 【课程设计】 本课程包含3大模块 基础篇 本篇主要讲解c++的基础概念,包含数据类型、运算符等基本语法,数组、指针、字符串等基本词法,循环、函数、类等基本句法等。 进阶篇 本篇主要讲解编程中常用的一些技能,包含类的高级技术、类的继承、编译链接和命名空间等。 提升篇: 本篇可以帮助学员更加高效的进行c++开发,其中包含类型转换、文件操作、异常处理、代码重用等内容。
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页