九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

ASIO—下一代C++标准可能接纳的网络库(3)UDP网络应用

ASIO—下一代C++标准可能接纳的网络库(3)UDP网络应用

write by九天雁翎(JTianLing) – www.jtianling.com**

讨论新闻组及文件

一、   综述

接着前面

ASIO—下一代C++标准可能接纳的网络库(1)简单的应用

ASIO—下一代C++标准可能接纳的网络库(2)TCP网络应用

继续,讲了简单应用,讲了TCP,自然而然是UDP了。其实个人感觉UDP与TCP的接口假如经过封装是可以做到接口比较一致的,但是遗憾的是asio没有遵循这样的接口设计方案。

      

二、   Tutorial

1.      Daytime.4 - A synchronous UDP daytime client(同步UDP daytime客户端)

还是先看看普通的socket API的情况:

#include <stdio.h>
#include <string.h>
#include "Winsock2.h"
#include "errno.h"
#include "stdlib.h"

#define MAXLINE 1000

void str_cli(SOCKET sockfd, const struct sockaddr* pservaddr, int servlen)
{
    int n;
    char recvline[MAXLINE] = {0};
    char sendline[2] = {0};
    sendto(sockfd, sendline, 2, 0, pservaddr, servlen);

    n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
    recvline[n] = 0;
    printf("%s", recvline);
}


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_DGRAM, 0);

    ZeroMemory(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(13);
    servaddr.sin_addr.s_addr = inet_addr(argv[1]);

    str_cli(sockfd, (const struct sockaddr*)&servaddr, sizeof(servaddr));

    system("pause");
    WSACleanup();
    exit(0);
}

相对来说,假如用了socket API,会发现UDP的程序编写逻辑是比TCP要简单的,起码省略了connect的过程,但是难就难在当网络情况不好时UDP程序的处理。这么简单的程序不再加更多说明了。

看看asio的例子:

#include <iostream>
#include <boost/array.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::udp;

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;
       udp::resolver resolver(io_service);
       udp::resolver::query query(udp::v4(), argv[1], "daytime");
       udp::endpoint receiver_endpoint = *resolver.resolve(query);
       udp::socket socket(io_service);
       socket.open(udp::v4());
       boost::array<char, 1> send_buf = { 0 };
       socket.send_to(boost::asio::buffer(send_buf), receiver_endpoint);
       boost::array<char, 128> recv_buf;
       udp::endpoint sender_endpoint;
       size_t len = socket.receive_from(
           boost::asio::buffer(recv_buf), sender_endpoint);
       std::cout.write(recv_buf.data(), len);
    }
    catch (std::exception& e)
    {
       std::cerr << e.what() << std::endl;
    }
    return 0;
}

甚至没有感觉到有任何简化。一大堆的resolver(为了适应ipv6),一大堆的array,其实并不优雅,在很简单的程序中,会发现,似乎asio就是简单的为socket进行了非常浅的封装一样,你还得了解一大堆本来可以不了解的东西,asio内在的高效率,异步啊,用的那些模式啊,都看不到。。。。。。。。。。呵呵, 也许socket API本来就是Make the simple things simple吧,而asio就是为了应付绝对复杂的情况而做出相对复杂设计的吧。这样的例子没有任何说服力能让人放弃socket API而使用asio。。。。。。。不知道asio的文档中加入这些干啥。。。仅仅为了说明?-_-!

 

2.      A synchronous UDP daytime server(同步的UDP daytime服务器)

还是先来个原始的socket API写的版本:

#include <time.h>
#include "Winsock2.h"
#include "errno.h"
#include "stdlib.h"

#define MAXLINE 1000

void str_svr(SOCKET sockfd, struct sockaddr* pcliaddr, int clilen)
{
    int n = 0;
    time_t ticks = 0;
    int len;
    char recvline[2] = {0};
    char sendline[MAXLINE] = {0};
    for(;;)
    {
       len = clilen;
       if( (n = recvfrom(sockfd, recvline, 2, 0, pcliaddr, &len)) == INVALID_SOCKET)
       {
            printf("recvfrom failed: %d/n", WSAGetLastError());
            return;
       }

       ticks = time(NULL);
       _snprintf(sendline, sizeof(sendline), "%.24s/r/n", ctime(&ticks));

       sendto(sockfd, sendline, strlen(sendline), 0, pcliaddr, len);
    }
}


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,cliaddr;
    sockfd = socket(AF_INET, SOCK_DGRAM, 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(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr))
       == SOCKET_ERROR) 
    {
       printf("bind failed: %d/n", WSAGetLastError());
       closesocket(sockfd);
       WSACleanup();
       return 1;
    }

    str_svr(sockfd, (struct sockaddr*)&cliaddr, sizeof(cliaddr));

    closesocket(sockfd);
    WSACleanup();
    return 1;
}

与上篇文章中tcp服务器的例子很像,基本上来说,用socket写客户端还是相对简单一些,但是写个服务器就相对要复杂很多,这个例子还没有精细的判断每个返回值(比如send函数),但是已经比较复杂了。

接着是asio版本:

#include <ctime>
#include <iostream>
#include <string>
#include <boost/array.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::udp;

std::string make_daytime_string()
{
    using namespace std; // For time_t, time and ctime;
    time_t now = time(0);
    return ctime(&now);
}

int main()
{
    try
    {
       boost::asio::io_service io_service;
       udp::socket socket(io_service, udp::endpoint(udp::v4(), 13));
       for (;;)
       {
           boost::array<char, 1> recv_buf;
           udp::endpoint remote_endpoint;
           boost::system::error_code error;
           socket.receive_from(boost::asio::buffer(recv_buf),
              remote_endpoint, 0, error);
           if (error && error != boost::asio::error::message_size)
              throw boost::system::system_error(error);
           std::string message = make_daytime_string();
           boost::system::error_code ignored_error;
           socket.send_to(boost::asio::buffer(message),
              remote_endpoint, 0, ignored_error);
       }
    }
    catch (std::exception& e)
    {
       std::cerr << e.what() << std::endl;
    }
    return 0;
}

我甚至觉得在asio中写服务器比写客户端更加简单-_-!也许一开始asio就是为了写高性能服务器而设计的,所以导致写客户端相对那么麻烦,但是写服务器却又简单很多吧。不过,熟悉socket API对于使用asio也是有意义的,比如这里的receive_from,send_to不过是对应的socket API函数换汤不换药的版本而已,使用起来除了参数传递方式上的变化,最后效果一致。

 

3.      An asynchronous UDP daytime server(异步 UDP daytime 服务器)

又是有点意思了的程序了,asio的命名就是表示异步的io,所以展现异步的程序才能体现asio的实力及其简化了底层操作的本事。TCP的应用是这样,这里也不例外。

不过出于UDP应用相对于TCP应用本身的简单性,所以这个示例程序比对应的TCP版本就要简化很多,只是,不知道asio的UDP实现了更丰富的UDP特性没有,比如超时重发等机制。。。。

 

write by九天雁翎(JTianLing) – www.jtianling.com

分类:  网络技术 
标签:  ASIO  Boost  C++ 

Posted By 九天雁翎 at 九天雁翎的博客 on 2009年06月09日

前一篇: ASIO—下一代C++标准可能接纳的网络库(2)TCP网络应用 后一篇: 《数据结构与算法分析C++描述》 分离链接(separate chaining)哈希表的C++实现