ASIO—下一代C++标准可能接纳的网络库(2)TCP网络应用
write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie
一、 综述
本文仅仅是附着在boost::asio文档的一个简单说明和讲解,没有boost::asio文档可能你甚至都不知道我在讲什么,boost::asio的文档自然是需要从www.boost.org上去下。
基本上,网络编程领域的”Hello World”程序就是类似Echo,daytime等服务器应用了。大牛Stevens经典的《Unix Network Programming》一书更是在这两个服务器上折腾了半本书,Comer的《Internetworking With TCP/IP vol III》也不例外。boost::asio的文档也就更不例外了,全部的网络方面的例子都是以daytime服务为蓝本来讲解的。呵呵,大家这样做是有道理的,毕竟从讲解网络编程的原理来看,echo,daytime等足够的简单:)
二、 Tutorial
首先,因为客户端程序相对服务器程序更为简单,所以一般都从客户端开始,boost::asio也是如此,第一节,给出了一个TCP 的Daytime的实现所谓示例,这里,我不拷贝其源码了,只是列出一个用windows 下用套接字接口实现的同样程序作为对比。
1. A synchronous TCP daytime client(一个同步的TCP daytime客户端程序)
原始的套接字实现:
#include <stdio.h>
#include <string.h>
#include "Winsock2.h"
#include "errno.h"
#include "stdlib.h"
#define MAXLINE 1000
void str_cli(SOCKET sockfd)
{
char recvline[MAXLINE] = {0};
while ( (recv(sockfd, recvline, MAXLINE, 0)) != NULL)
{
printf("%s", recvline);
}
closesocket(sockfd);
}
int main(int argc, char **argv)
{
WORD wVersionRequested = 0;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
// windows下此初始化为必须,实际是初始化WinsockDLL的过程
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return -1;
}
SOCKET sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
{
printf("usage: tcpcli <IPaddress>");
exit(1);
}
sockfd = socket(AF_INET, SOCK_STREAM, 0);
ZeroMemory(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(13);
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
if( SOCKET_ERROR == connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)))
{
printf("connet failed, Error Code: %d", WSAGetLastError());
closesocket(sockfd);
return -1;
}
str_cli(sockfd); /* do it all */
system("pause");
exit(0);
}
共六十一行,并且需要处理socket创建,初始化等繁琐细节,做任何决定时基本上是通过typecode,其实相对来说也不算太难,因为除了socket的API接口属于需要额外学习的东西,没有太多除了C语言以外的东西需要学习,并且因为BSD socket是如此的出名,以至于几乎等同与事实的标准,所以这样的程序能被大部分学习过一定网络编程知识的人了解。
为了方便对比,我还是贴一下boost::asio示例中的代码:
#include <iostream>
#include <boost/array.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: client <host>" << std::endl;
return 1;
}
boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query(argv[1], "daytime");
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
tcp::resolver::iterator end;
tcp::socket socket(io_service);
boost::system::error_code error = boost::asio::error::host_not_found;
while (error && endpoint_iterator != end)
{
socket.close();
socket.connect(*endpoint_iterator++, error);
}
if (error)
throw boost::system::system_error(error);
for (;;)
{
boost::array<char, 128> buf;
boost::system::error_code error;
size_t len = socket.read_some(boost::asio::buffer(buf), error);
if (error == boost::asio::error::eof)
break; // Connection closed cleanly by peer.
else if (error)
throw boost::system::system_error(error); // Some other error.
std::cout.write(buf.data(), len);
}
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
boost::asio的文档中的实现也有47行,用了多个try,catch来处理异常,因为其实现的原因,引入了较多的额外复杂度,除了boost::asio以外,即便你很熟悉C++,你也得进一步的了解诸如boost::array, boost:system等知识,(虽然其实很简单)并且,从使用上来说,感觉并没有比普通的socket API简单,虽然如此,boost::asio此例子还是有其优势的,比如ipv4,ipv6的自适应(原socket API仅仅支持ipv4),出错时更人性化的提示(此点由C++异常特性支持,相对比C语言中常常只能有个error code)。
当然,此例子过于简单,而asio是为了较大规模程序的实现而设计的,假如这么小规模的程序用原始的套接字就足够了。这点是需要说明的。
2. Daytime.2 - A synchronous TCP daytime server(同步的TCP daytime服务器)
有了客户端没有服务器,那客户端有什么用呢?^^所以,接下来boost::asio适时的给出了一个daytime的服务器实现,这里还是先给出使用一个原始套接字的例子:
#include <time.h>
#include "Winsock2.h"
#include "errno.h"
#include "stdlib.h"
#define MAXLINE 1000
int main(int argc, char **argv)
{
WORD wVersionRequested = 0;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
// windows下此初始化为必须,实际是初始化WinsockDLL的过程
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return -1;
}
SOCKET listenfd, connfd;
struct sockaddr_in servaddr;
char buff[MAXLINE];
time_t ticks;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
ZeroMemory(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(13); /* daytime server */
if( bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr))
== SOCKET_ERROR)
{
printf("bind failed: %d/n", WSAGetLastError());
closesocket(listenfd);
WSACleanup();
return 1;
}
if (listen( listenfd, SOMAXCONN ) == SOCKET_ERROR)
{
printf("Error listening on socket./n");
WSACleanup();
return 1;
}