此文转自:http://blog.csdn.net/misskissC/article/details/9985167
1 C++ Boost库asio网络通信类核心结构
在C++ Boost库中用于通信的类的层次为boost::asio::ip,所有有关通信的类别都在这个层次之下。
asio封装了berkeley socket APIS,使其支持TCP,UDP,ICMP通信协议,提供了一个健壮且易用的网络通信库:boost::asio::ip。
(1)ip:tcp
网络通信TCP部分位于的层次为ip:tcp。类是asio网络通信(TCP)部分主要的类,主要形式是定义了多个应用于TCP通信的typedef的类,用来协作完成网路通信。包括端点类endpoint,套接字类socket,流类iostream、acceptor及resolver等等。
Socket类是TCP下的类。
ip::tcp的内部类型socket,acceptor及resolver是ip::tcp的核心类,它们封装了socket的连接、断开和数据收发功能,使用它们很容易编写出socket程序。
<1>socket class
属TCP通信的基本类,调用成员函数connect()可以连接到一个指定的通信端点,成功连接后用local_endpoint()和remote_endpoin()获得连接两端的端点信息,用read_some()和write_some()阻塞读写数据,当操作完成后使用close()函数关闭socket[ 如果不关闭其析构函数也自动调用close关闭 socket]
<2>acceptor class
acceptor类对应socket API的accept()函数功能,它用于服务器端,在指定的端口号接受连接,必须配合socket类让其对象作为参数才能完成通信。
<3>resolver class
resolver类对应socket API的getaddrinfo()系列函数,用于客户端解析网址获得可用的IP地址,解析得到的IP地址可以使用socket对象连接。在实际生活中大多数我们不可能知道socket连接另一端的地址,而只有域名,这个时候就应该使用resolver类来通过域名获得可用的IP。它可以实现与IP地址无关的网址解析。
(2)ip:address
ip地址独立于TCP等通信协议,asio库使用ip::address来表示ip地址支持ipv4及ipv6。
ip地址再加上一个端口号就构成了一个端点(ip::tcp::endpoint),它的主要用法是通过构造函数创建一个可用于socket通信的的端点对象,端点的地址和端口号可以由address和port获取。
可简要查阅其各IP下类的类摘要。
2 编写socket程序
首先明确使用socket类在C++ boost库内的asio库下的ip::tcp下,所以,它得遵循asio基本程序的结构和流程[vs2010 boost库配置 asio程序结构流程]。
步骤
(1)建立控制台程序
用VS2010新建两个控制台程序,一个用于socket程序的服务器端,一个用于socket程序的客户端。配置好boost库环境。明确boost库下各类需要包含的头文件、命名空间及宏。
(2)用socket类编写服务端程序
[1]遵循asio程序的结构和流程,首先定义io_service对象
io_service io_s; |
[2]用io_service对象,TCP协议及端口号初始化acceptor
ip::tcp::acceptor accpt( io_s, ip::tcp::endpoint( ip::tcp::v4(), 55 ) ); |
[3]创建服务器端的socket,将io_service对象传入
ip::tcp::socket ser_socket( io_s ); |
[4]用acceptor对象调用accept函数同步等待客户端的连接
accpt.accept( ser_socket ); |
在客户端与此服务器端连接上之前,此程序会在此语句处停留直到与客户端相连。
与客户端连上之后,可以用本地socket对象调用remote_endpoint().address()查看客户端的ip地址信息。
[5]向客户端传送数据[如果要聊天,则可以使用循环来实现,不过对于连接的服务器端与客户端可能会断开,所以呢,要做一个完善的网络编程还得随时检测两者的连接是否断开 ]
此数据可以是用户程序内定也可以由交互式的用户输入。
string input_str; cout << "Input your words what you want to say to client:"; cin >> input_str; //Send message to client ser_socket.write_some( buffer( input_str ) ); |
write_some函数就是向客户端发送数据函数。buffer*函数可以包装很多种类的容器称为asio组件可用的缓冲区类型。
[6]接受客户端发送的数据并显示[ ^-^,高级的以后再说 ]
deadline_timer d_t( io_s, posix_time::seconds(2) ); vector<char> str( 100, 0 ); ser_socket.read_some( buffer( str ) ); cout << "client's answer:" << &str[0] << "\n\n"; |
第一行是为了接受客户端发送的数据而故意延迟的。从这里可以看出来,这个入门的服务器端和客户端肯定不能进行流畅的聊天。嘎嘎^-^
read_some就是读取客户端发送的数据。
(3)用socket类编写客户端
用socket类编写客户端的步骤和代码跟编写服务器端差不多。
[1]创建io_service io_s对象
[2]在客户端之上创建socket对象,创建连接的端口,端口值跟服务器端口保持一致,并指明具体的ipv4 ip地址值
ip::tcp::socket client_socket( io_s ); ip::tcp::endpoint client_ep( ip::address::from_string( "127.0.0.1"), 55 ); |
[3]连接服务器端
client_socket.connect( client_ep ); |
[4]接收服务器端数据,向服务器端发送数据
client_socket.read_some( buffer( str ), e_c ); cout << "\nreceive form" << client_socket.remote_endpoint().address() << ":"; cout << &str[0]<< "\n"; memset( &str[0], 0, str.size() ); cout << "my reply is:>> Slient!\n"; //Send client_socket.write_some( buffer( "您好我现在有事不在,一会儿跟您联系!") ); |
采取先读服务器端数据并显示再发送固定数据的方式来完成这个操作。客户端向服务器端发送数据的方式之所以采用固定字符串是因为服务器端和客户端两边的程序都是单独运行的,如果在服务器端执行到read_some之前客户端还没有发送数据,就会出错。这里采用固定字符串的方式来对应服务器端延时两秒。在时间的逻辑上是走的通的。
memset的作用是清楚str的内容而不受上次所存内容的影响。
3程序分析和运行结果
(1)实现似聊天的功能
先启动服务器端,服务器端的socket对象被创建,由acccptor对象调用accept函数来等待客户端的连接。稍后启动客户端,客户端的socket对象和端点对象被创建,客户端socket兑现调用connect函数去连接服务器端,如果一切正常就会建立服务器端和客户端的一次连接。在不考虑服务器端客户端之间连接上会意外断开的可能,那么在二者连接后就可以在它们之间实现很多操作(如数据传输)。对于带周期规律性的操作如聊天,就可以采取循环操作来完成。如果只实现程序中描述的聊天模式,那么只需要在服务器端数据传输和数据接收部分加一个条件循环[如while(true) ],只需要在客户端数据接收和数据发送部分加一个条件循环[如while( tue ) ]。
(2)程序中的隐患
对于两个程序之间的通信,很可能出现许多的异常,此时为了程序的可调性和对用户的良好性,就应该对各种异常做出回应。最好是使用C++的try-throw-catch机制来解决这个问题。socket类中的函数几乎每个函数都接受一个检测异常的boost::system::error_code e_c;
参数,并会将错误返回给参数e_c。
程序中有以下几个地方存在这样的隐患:
[1]客户端在连接服务器端时,需要判断两者是否真的连接成功。如果不判断程序会继续进行,后面的数据处理就没有意义而且会引发错误。
client_socket.connect( client_ep, e_c ); if( e_c ) { throw boost::system::system_error( e_c ); } |
[2]在接收数据时应该判断服务器端和客户端的连接是否已经断开
if ( e_c == boost::asio::error::eof ) { break; } else if ( e_c ) { throw boost::system::system_error( e_c ); } |
呵呵,当然了在有throw的情况下,try-throw-catch使用的结构就要有了。
(3)程序运行结果
先运行服务器端程序,后运行客户端程序,这样子才能保证客户端能连接上服务器端。
[1]运行服务器端,确定已经在等待客户端的连接
图1 运行服务器端
[2]运行客户端,简单的看一下服务器端和客户端的通信
图2 服务器端与客户端的数据通信
被封装的东东就是强大呀,依旧是字符界面的编程,比最开始多需要的东西是C++面向对象的思想和开发环境中环境库的配置。
4.异步方式的网络通信总结
[1]就网络通信方面的步骤
跟同步网络通信的步骤一个样一个样的,只是在每要进行异步操作时所调用的函数不同( 多一个async的前缀 ),然后多一个异步的机制。
[2]就同步/异步程序的步骤[ 同步/异步小机制 ]
异步方式就比同步方式多一个回调函数和多调用is_service::run()函数两个步骤。对于回调函数的参数问题可以用bind来解决,对于异步调用函数的生存空间书中所述说可以用智能指针来解决( 回调函数经异步调用后还能够被主程序继续使用 )。
[3]关于如何利用同步/异步方式
这还得归结于对同步/异步运行机制的把握和设计功底了。再加上如果用上多线程,嘿嘿,功能应该就会变得很强大,哪怕是同步机制。只是这种组合会稍微难点,但每个模块( 多线程,同步/异步,网络通信)在程序设计时一般都不会被孤立。
此次笔记记录完毕。