随笔 - 355  文章 - 8 评论 - 512 trackbacks - 19

在C语言中,主机信息是存储在一个hostent结构体中,它的结构如下:

struct hostent
{
    char *h_name;
    char **h_aliases;
    int h_addrtype;
    int h_length;
    char **h_addr_list;    
};

解释一下这个结构:

  1. char *h_name; 表示的是主机的规范名。例如 http://www.google.com/ 的规范名其实是 http://www.l.google.com/ 。
  2. char **h_aliases; 表示的是主机的别名。 http://www.google.com/ 就是google他自己的别名。有的时候,有的主机可能有好几个别名,这些,其实都是为了易于用户记忆而为自己的网站多取的名字。
  3. int h_addrtype; 表示的是主机ip地址的类型,到底是ipv4(AF_INET),还是ipv6(AF_INET6)
  4. int h_length; 表示的是主机ip地址的长度
  5. char **h_addr_list; 表示的是主机的ip地址,注意,这个是以网络字节序存储的。不能直接用printf带%s参数来打这个东西,需要调用inet_ntop()函数转换。

下面是一个获取主机信息的例子。

#include <ace/OS.h>

int main(int argc, char *argv[])
{
    //
获取本机主机名
    char hostname[20];
    ACE_OS::hostname(hostname,20);

    struct hostent *hptr;
    //
调用gethostbyname()获取主机地址信息
    if( (hptr = ACE_OS::gethostbyname(hostname) ) == NULL )
    {
        printf("gethostbyname error for host:%s\n", hostname);
        return 0; //
如果调用gethostbyname发生错误,返回
    }

    //
将主机的规范名打出来
    printf("hostname:%s\n",hptr->h_name);
    
    char **pptr;
    //
主机可能有多个别名,将所有别名分别打出来
    for(pptr = hptr->h_aliases; *pptr != NULL; pptr++)
        printf(" alias:%s\n",*pptr);
    
    char ip[32];
    //
根据地址类型,将地址打出来
    switch(hptr->h_addrtype)
    {
    case AF_INET:
    case AF_INET6:
        pptr=hptr->h_addr_list;
        //
将刚才得到的所有地址都打出来。其中调用了inet_ntop()函数
        for(;*pptr!=NULL;pptr++)
            printf("address:%s\n", ACE_OS::inet_ntop(hptr->h_addrtype, *pptr, ip, sizeof(ip)));
        break;
    default:
        printf("unknown address type\n");
        break;
    }
    return 0;
}

C#和C++一样,主机信息存储在IPHostEntry类中,不过它封装得较好,下列C#代码实现了同样的功能,比C++的要精简得多。

static void Main(string[] args)
{
    string hostName = Dns.GetHostName(); //
获取本地主机名
    IPHostEntry hostInfo = Dns.GetHostEntry(hostName); //获取主机消息

    //打印主机名
    Console.WriteLine("host name:\t{0}",hostInfo.HostName);

    //
打印主机关联的别名
    foreach (string name in hostInfo.Aliases)
    {
        Console.WriteLine("aliases:\t{0}",name);
    }

    //
打印ip地址消息
    foreach (IPAddress ip in hostInfo.AddressList)
    {
        Console.WriteLine("ip:\t{0}",ip);
    }
}

 

 

 

posted @ 2007-01-11 16:46 天方 阅读(1366) 评论(0) 编辑

多线程为构建高性能的应用提供了极大的方便,但是也带来了不少的麻烦。线程间同步、数据一致性等烦琐的问题需要细心的考虑,一不小心就会出现一些微妙的,难以调试的错误。另外,应用逻辑和线程逻辑纠缠在一起,会导致程序的逻辑结构混乱,难以复用和维护。本文试图给出一个解决这个问题的方案,通过构建一个并发模型框架(framework),使得开发多线程的应用变得容易。

简单例子

本文将围绕一个简单的例子展开论述,这样可以更容易突出我们解决问题的思路、方法。本文想向读者展现的正是这些思路、方法。这些思路、方法更加适用于解决大规模、复杂应用中的并发问题。

考虑一个简单的例子,我们有一个服务提供者,它通过一个接口对外提供服务,服务内容非常简单,就是在标准输出上打印Hello World。类结构图如下:

 代码如下:

using System;
using System.Collections.Generic;
using System.Text;

using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            IService serv = new ServiceImp();
            Client client = new Client(serv);
            client.requestService();
        }
    }

    interface IService
    {
        void SayHello();
    }

    class ServiceImp : IService
    {
        public void SayHello()
        {
            Console.WriteLine("ServiceImp");
        }
    }

    class Client
    {
        IService serv;
        public Client(IService serv)
        {
            this.serv = serv;
        }

        public void requestService()
        {
            serv.SayHello();
        }
    }
}

如果现在有新的需求,要求该服务必须支持Client的并发访问。一种简单的方法就是在ServicImp类中的每个方法前面加上synchronized声明,来保证自己内部数据的一致性(当然对于本例来说,目前是没有必要的,因为ServiceImp没有需要保护的数据,但是随着需求的变化,以后可能会有的)。但是这样做至少会存在以下几个问题:

  1. 现在要维护ServiceImp的两个版本:多线程版本和单线程版本(有些地方,比如其他项目,可能没有并发的问题),容易带来同步更新和正确选择版本的问题,给维护带来麻烦。 
  2. 如果多个并发的Client频繁调用该服务,由于是直接同步调用,会造成Client阻塞,降低服务质量。 
  3. 很难进行一些灵活的控制,比如:根据Client的优先级进行排队等等。

这些问题对于大型的多线程应用服务器尤为突出,对于一些简单的应用(如本文中的例子)可能根本不用考虑。本文正是要讨论这些问题的解决方案,文中的简单的例子只是提供了一个说明问题,展示思路、方法的平台。

如何才能较好的解决这些问题,有没有一个可以重用的解决方案呢?让我们先把这些问题放一放,先来谈谈和框架有关的一些问题。

框架概述

熟悉 面向对象的读者一定知道面向对象的最大的优势之一就是:软件复用。通过复用,可以减少很多的工作量,提高软件开发生产率。复用本身也是分层次的,代码级的复用和设计架构的复用。

大家可能非常熟悉C语言中的一些标准库,它们提供了一些通用的功能让你的程序使用。但是这些标准库并不能影响你的程序结构和设计思路,仅仅是提供一些机能,帮助你的程序完成工作。它们使你不必重头编写一般性的通用功能(比如printf),它们强调的是程序代码本身的复用性,而不是设计架构的复用性。

那么什么是框架呢?所谓框架,它不同于一般的标准库,是指一组紧密关联的(类)classes,强调彼此的配合以完成某种可以重复运用的设计概念。这些类之间以特定的方式合作,彼此不可或缺。它们相当程度的影响了你的程序的形貌。框架本身规划了应用程序的骨干,让程序遵循一定的流程和动线,展现一定的风貌和功能。这样就使程序员不必费力于通用性的功能的繁文缛节,集中精力于专业领域。

有一点必须要强调,放之四海而皆准的框架是不存在的,也是最没有用处的。框架往往都是针对某个特定应用领域的,是在对这个应用领域进行深刻理解的基础上,抽象出该应用的概念模型,在这些抽象的概念上搭建的一个模型,是一个有形无体的框架。不同的具体应用根据自身的特点对框架中的抽象概念进行实现,从而赋予框架生命,完成应用的功能。

基于框架的应用都有两部分构成:框架部分特定应用部分。要想达到框架复用的目标,必须要做到框架部分和特定应用部分的隔离。使用面向对象的一个强大功能:多态,可以实现这一点。在框架中完成抽象概念之间的交互、关联,把具体的实现交给特定的应用来完成。其中一般都会大量使用了Template Method设计模式。

Java中的Collection Framework以及微软的MFC都是框架方面很好的例子。有兴趣的读者可以自行研究。

构建框架

如何构建一个并发模型框架呢?让我们先回到原来的问题,先来分析一下原因。造成要维护多线程和单线程两个版本的原因是由于把应用逻辑和并发逻辑混在一起,如果能够做到把应用逻辑和并发模型进行很好的隔离,那么应用逻辑本身就可以很好的被复用,而且也很容易把并发逻辑添加进来而不会对应用逻辑造成任何影响。造成Client阻塞,性能降低以及无法进行额外的控制的原因是由于所有的服务调用都是同步的,解决方案很简单,改为异步调用方式,把服务的调用和服务的执行分离。

本框架的核心就是使用主动对象来封装并发逻辑,然后把Client的请求转发给实际的服务提供者(应用逻辑),这样无论是Client还是实际的服务提供者都不用关心并发的存在,不用考虑并发所带来的数据一致性问题。从而实现应用逻辑和并发逻辑的隔离,服务调用和服务执行的隔离。

我们可以通过主动对象实现对并发逻辑的封装。开发者只需要根据需要实现MethodRequest接口,另外再定义一个服务代理类提供给使用者,在服务代理者类中把服务调用者的请求转化为MethodRequest实现,交给活动对象即可。

使用该框架,可以较好的做到应用逻辑和并发模型的分离,从而使开发者集中精力于应用领域,然后平滑的和并发模型结合起来,并且可以针对ActiveQueue定制排队机制,比如基于优先级等。

基于框架的解决方案

本小节将使用上述的框架重新实现前面的例子,提供对于并发的支持。第一步先完成对于IMethodRequest的实现,对于我们的例子来说实现如下:

class SayHelloMethod : IMethodRequest
{
    public SayHelloMethod(IService serv)
    {
        this.serv = serv;
    }

    public void Call()
    {
        serv.SayHello();
    }

    private IService serv;
}

该类完成了对于服务提供接口sayHello方法的封装。接下来定义一个服务代理类,来完成请求的封装、排队功能,当然为了做到对Client透明,该类必须实现Service接口。定义如下:

class ServiceProxy : IService
{
    public ServiceProxy()
    {
        _service = new ServiceImp();
        _active_object = new ActiveObject();
    }

    public void SayHello()
    {
        IMethodRequest mr = new SayHelloMethod(_service);
        _active_object.AddCommand(mr);
    }

    private IService _service;
    private ActiveObject _active_object;
}

其他的类和接口定义不变,下面对比一下并发逻辑增加前后的服务调用的变化,并发逻辑增加前,对于sayHello服务的调用方法:

IService s = new ServiceImp();
Client c = new Client(s);
c.requestService();

并发逻辑增加后,对于sayHello服务的调用方法:

IService s = new ServiceProxy();
Client c = new Client(s);
c.requestService();

可以看出并发逻辑增加前后对于Client的ServiceImp都无需作任何改变,使用方式也非常一致,ServiceImp也能够独立的进行重用。类结构图如下:

读者容易看出,使用框架也增加了一些复杂性,对于一些简单的应用来说可能根本就没有必要使用本框架。希望读者能够根据自己的实际情况进行判断。

结论

本文围绕一个简单的例子论述了如何构架一个Java并发模型框架,其中使用了一些构建框架的常用技术,当然所构建的框架和一些成熟的商用框架相比,显得非常稚嫩,比如没有考虑服务调用有返回值的情况,但是其思想方法是一致的,希望读者能够深加领会,这样无论对于构建自己的框架还是理解一些其他的框架都是很有帮助的。读者可以对本文中的框架进行扩充,直接应用到自己的工作中。下面列出本框架的优缺点:

优点:

  1. 增强了应用的并发性,简化了同步控制的复杂性 
  2. 服务的请求和服务的执行分离,使得可以对服务请求排队,进行灵活的控制 
  3. 应用逻辑和并发模型分离,使得程序结构清晰,易于维护、重用 
  4. 可以使开发者集中精力于应用领域

 缺点:

  1. 由于框架所需类的存在,在一定程度上增加了程序的复杂性 
  2. 如果应用需要过多的活动对象,由于线程切换开销会造成性能下降 
  3. 可能会造成调试困难

PS:原文录自UML软件工程组织,是讲Java的异步框架的,我这里将其简化了一下。

posted @ 2007-01-11 10:27 天方 阅读(669) 评论(1) 编辑

在前面介绍ACE主动对象中我已经简单的介绍了一下主动对象模式,今天这里我就简单的介绍一下主动对象在C#中的实现。

所谓主动对象是相对于被动对象(passive object)而言的,被动对象的方法的调用和执行都是在同一个线程中的,被动对象方法的调用是同步的、阻塞的,一般的对象都属于被动对象;主动对象的方法的调用和执行是分离的,主动对象有自己独立的执行线程,主动对象的方法的调用是由其他线程发起的,但是方法是在自己的线程中执行的,主动对象方法的调用是异步的,非阻塞的。

本框架有如下几部分构成:

  1. 一个ActiveObject类,从Thread继承,封装了并发逻辑的活动对象 
  2. 一个ActiveQueue类,主要用来存放调用者请求 
  3. 一个IMethodRequest接口,主要用来封装调用者的请求,Command设计模式的一种实现方式

它们的一个简单的实现如下:

interface IMethodRequest
{
    void Call();
}

class ActiveQueue<T>
{
    const int maxCount = 10; //
最大个

    Queue<T> queue = new Queue<T>();
    public void Enqueue(T item)
    {
        lock (this)
        {
            if (queue.Count >= maxCount)
            {
                Monitor.Wait(this);
            }
            queue.Enqueue(item);
            Monitor.PulseAll(this);
        }
    }

    public T Dequeue()
    {
        lock (this)
        {
            if (queue.Count == 0)
            {
                Monitor.Wait(this);
            }
            T item = queue.Dequeue();
            Monitor.PulseAll(this);
            return item;
        }
    }
}

class ActiveObject
{
    ActiveQueue<IMethodRequest> cmdQueue = new ActiveQueue<IMethodRequest>();

    public ActiveObject()
    {
        ThreadPool.QueueUserWorkItem(ExcuteCommand);
    }

    public void AddCommand(IMethodRequest cmd)
    {
        cmdQueue.Enqueue(cmd);
    }

    private void ExcuteCommand(object obj)
    {
        while (true)
        {
            IMethodRequest cmd = cmdQueue.Dequeue();
            cmd.Call();
        }
    }
}

在主动对象的三个成员ActiveObject ,IMethodRequest,ActiveQueue中,前两个都比较简单,很容易想到实现的方法。稍微复杂一点的是ActiveQueue对象,它要求提供一个线程安全的操作类,这里我仿照ACE利用Queue简单的封装了一个,为了使用简单,我把它的Enqueue和Dequeue方法简单的修改了一下。

  1. 当使用Enqueue方法时,如果队列中的元素个数已经达到最大个数限制,将会阻塞至队列中元素被Dequeue取走。
  2. 当使用Enqueue方法时,如果队列中无可用元素,将会阻塞至其它线程通过Enqueue将元素插入至队列中,然后返回可用元素。

我采用Monitor实现了这种线程同步,有兴趣的朋友可以参看一下。

主动对象是一种常用的异步通信模式,这里我简单的给出了一种C#的实现。当然,我这里的例子还不是很完善,更多知识可以看一下我以前介绍ACE主动对象的相关章节。

posted @ 2007-01-11 09:56 天方 阅读(616) 评论(1) 编辑