Chosen

常用链接

统计

最新评论

2013年10月7日 #

C++实现简单的线程池

线程池编程简介:

    在我们的服务端的程序中运用了大量关于池的概念,线程池、连接池、内存池、对象池等等。使用池的概念后可以高效利用服务器端的资源,比如没有大量的线程在系统中进行上下文的切换,一个数据库连接池,也只需要维护一定里的连接,而不是占用很多数据库连接资源。同时它们也避免了一些耗时的操作,比如创建一个线程,申请一个数据库连接,而且可能就只使用那么一次,然后就立刻释放刚申请的资源,效率很低。

    在我的上一篇blog中已经实现一个线程基类了,在这里我们只需要实现一个线程池类ThreadPool和该线程池调度的工作线程类WorkThread即可,而且WorkThread是继承自Thread类的。

 

实现思路:

    一个简单的线程池的实现思路一般如下:

  1. ThreadPool中创建多个线程(WorkThreadk对象),每个线程均处于阻塞状态,等待任务的到来
  1. ThreadPool提供一个提交任务的接口,如post_job(ProcCallback func, void* data); post_job后会立即返回,不会阻塞
  2. ThreadPool维护一个空闲线程队列,当客户程序调用post_job()后,如果空闲队列中有空闲线程,则取出一个线程句柄,并设置任务再给出新任务通知事件即可,处理等待的线程捕捉到事件信号后便开始执行任务,执行完后将该线程句柄重新push到空闲线程队列中
  3. 该线程池采用回调函数方式

首先我们实现一个WorkThread类:

 1 typedef void (APR_THREAD_FUNC *ProcCallBack)(void*);        //回调函数指针
 2 //由线程池调度的工作线程
 3 class WorkThread : public Thread      //Thread类的实现可参考我上一篇的blog: 《C++封装一个简单的线程类》
 4 {
 5     friend class ThreadPool;
 6 public:
 7     WorkThread(ThreadPool* pthr_pool)
 8     {
 9         thr_pool_    = pthr_pool;
10         cb_func_    = NULL;
11         param_        = NULL;
12     }
13     virtual ~WorkThread(){}
14     void    set_job(ProcCallBack func, void* param)
15     {
16         cb_func_    = func;
17         param_        = param;
18         notify();            //通知有新的任务
19     }
20     //实现Thread的run方法,并调用用户指定的函数
21     virtual void run()
22     {
23         if (cb_func_)
24             cb_func_(param_);
25     
26         //reset callback function pointer
27         cb_func_    = NULL;
28         param_        = NULL;
29     
30         //执行完任务,将该线程句柄移到线程池空闲队列
31         thr_pool_->move_to_idle_que(this);
32     }
33     
34 private:
35     ThreadPool*    thr_pool_;        //线程池指针
36     ProcCallBack    cb_func_;        //回调函数地址
37     void*             param_;           //回调函数参数
38 };

WorkThread中,有一个回调函数指针和参数,当有新任务时,会在run()中被调用,执行完后会将该线程移动到空闲线程队列,等待下一次任务的提交。

ThreadPool类定义如下:

 1 class ThreadPool
 2 {
 3     friend class WorkThread;
 4 public:
 5     ThreadPool();
 6     virtual ~ThreadPool();
 7     int    start_thread_pool(size_t thread_num = 5);        //启动thread_num个线程
 8     int    stop_thread_pool();                                      //线束线程池
 9     void    destroy();                                                //销毁线程池所申请的资源
10     void    post_job(ProcCallBack func, void* data);        //提交任务接口,传入回调函数地址和参数
11 
12 protected:
13     WorkThread*    get_idle_thread();                              //从获得空闲队列中取得一个线程句柄
14     void        append_idle_thread(WorkThread* pthread);    //加入到thread_vec_和idl_que_中
15     void        move_to_idle_que(WorkThread* idlethread);    //将线程句柄加入到idle_que_中
16 
17 private:
18     size_t                thr_num_;                      //线程数目
19     vector<WorkThread*>        thr_vec_;        //线程句柄集合
20     BlockQueue<WorkThread*>    idle_que_;     //空闲线程队列
21 
22 private:
23     // not implement
24     ThreadPool(const ThreadPool& );
25     ThreadPool&    operator=(const ThreadPool& );
26 };
      线程池实现的关键是如何创建多个线程,并且当任务来临时可以从线程池中取一个线程(也就是去得到其中一个线程的指针),然后提交任务并执行。还有一点就是当任务执行完后,应该将该线程句柄重新加入到空闲线程队列,所以我们将ThreadPool的指针传入给了WorkThread,thr_pool_最后可以调用move_to_idle_que(this)来将该线程句柄移到空闲队列中。
ThreadPool中一些关键代码的实现:
 1 int ThreadPool::start_thread_pool(size_t thread_num)
 2 {
 3     assert(thread_num != 0);
 4     thr_num_    = thread_num;
 5     int    ret        = 0;
 6     for (size_t i = 0; i < thr_num_; ++i)
 7     {
 8         WorkThread*    pthr = new WorkThread(this);
 9         pthr->set_thread_id(i);
10         if ((ret = pthr->start()) != 0)
11         {
12             printf("start_thread_pool: failed when create a work thread: %d\n", i);
13             delete pthr;
14             return i;
15         }
16         append_idle_thread(pthr);        
17     }
18     return thr_num_;
19 }
20 int ThreadPool::stop_thread_pool()
21 {
22     for (size_t i = 0; i < thr_vec_.size(); ++i)
23     {
24         WorkThread* pthr = thr_vec_[i];
25         pthr->join();
26         delete pthr;
27     }
28     thr_vec_.clear();
29     idle_que_.clear();
30     return 0;
31 }
32 void ThreadPool::destroy()
33 {
34     stop_thread_pool();
35 }
36 void ThreadPool::append_idle_thread(WorkThread* pthread)
37 {
38     thr_vec_.push_back(pthread);
39     idle_que_.push(pthread);
40 }
41 void ThreadPool::move_to_idle_que(WorkThread* idlethread)
42 {
43     idle_que_.push(idlethread);
44 }
45 WorkThread* ThreadPool::get_idle_thread()
46 {
47     WorkThread*    pthr = NULL;
48     if (!idle_que_.empty())
49         pthr = idle_que_.take();
50     return pthr;
51 }
52 void ThreadPool::post_job(ProcCallBack func, void* data)
53 {
54     assert(func != NULL);
55     WorkThread* pthr = get_idle_thread();
56     while (pthr == NULL)
57     {
58         apr_sleep(500000);
59         pthr = get_idle_thread();
60     }
61     pthr->set_job(func, data);
62 }
ThreadPool中的BlockQueue<WorkThread*> 也就是一个线程安全的队列,即对std::deque做了一个包装,在插入和取出元素时加了一个读写锁。


使用示例:
//任务执行函数,必须是ProcCallback类型
void count(void* param)
{
// do some your work, like: 
int* pi = static_cast<int*>(param);
int val = *pi + 1;
printf("val=%d\n", val);
pelete pi;
}
//程序中使用如下:
ThreadPool* ptp = new ThreadPool();
ptp->start_thread_pool(3); //启动3 个线程
ptp->post_job(count, new int(1)); //提交任务
ptp->post_job(count, new int(2));
ptp->post_job(count, new int(3));
//程序线束时
ptp->stop_thread_pool();
其实count()函数就是我们的业务实现代码,有任务时,可以提交给线程池去执行。
结尾:
    其实实现一个线程池或其它什么池并不难,当然线程安全和效率还是要从多写代码的经验中获取。像这个线程池也就是基于预创多个建线程,保保存好它们的线程句柄,当有新任务时取一个线程执行即可,执行完后一定要归还到空闲线程队列中,当然我们可以在线程池中增加一个任务队列,因为当post_job()时,若当时没有空闲线程,有两种方案,一是等待有空闲线程,二是加入到任务队列,当WorkThread线程执行完一个任务后,从任务队列中取一个任务继续执行即可,不会阻塞在post_job()中。
  另外,我们可以封装一些线程安全的队列和map什么的,这样在程序中就不用担心创建一个多线程共享的队列时,还必须创建一个锁,挺麻烦的,比如上面的BlockQueue<Type>直接拿来用就行了。

posted @ 2013-10-07 19:22 Choice 阅读(7249) | 评论 (0)编辑 收藏

C++封装一个简单的线程类

多线程编程简介:

    大家在编程时,经常需要在程序中启动一个或多个线程来处理任务,而如果每次都是去调用系统创建线程的API函数来创建,代码量虽不多,但线程的创建和业务逻辑代码就揉在一起了,且创建多个线程的时候,有大量的重复代码,不便于维护。若我们把创建线程和销毁线程的这些共同的代码封装到一个类中,这样我们可以更专注业务逻辑的实现,在其它地方直接拿来用就行,程序也便于维护和扩展。而且这样的话即使程序所使用的线程库更换了,但线程类提供的接口没变,所以我们的业务逻辑代码也不用任何的改动。

    创建一个线程也无非就是调用系统线程API或第三方库的API,然后传入线程函数地址和线程运行所需要的参数即可,所以我们需要将此部分代码抽象出来,并提供使用接口即可。

 

一个线程基类Thread

    这里我们使用的是Apache提供的apr库,该库具有跨平台性,当然不管使用什么库,我们提供的接口都是一样的,且线程创建和销毁的逻辑是一样的。代码:

 1 class Thread
 2 {
 3 public:
 4     Thread(bool bDetach = true);
 5     virtual ~Thread();
 6 
 7     virtual void run() = 0;      //业务接口
 8 
 9     int       start();              //启动线程
10     int      join();                //等待线程线束
11     void    destroy();           //销毁线程所申请的资源
12 
13     // attribute functions
14     int      get_thread_id()        { return thr_id_; }
15     void    set_thread_id(unsigned long thrId) {    thr_id_ = thrId; }
16 
17 protected:
18     static    void* __stdcall thread_proc(apr_thread_t* th, void* data);
19     void    notify() { cond_.signal(); }
20     bool    check_interrupt() { return bExit_; }
21 
22 private:
23     size_t            thr_id_;        //线程ID
24     bool            bExit_;            //线程是否要退出标志
25 
26     apr_thread_t*    thr_;            //线程句柄
27     Condition        cond_;            //线程函数中等待任务的条件变量
28     
29 private:
30     //not implement
31     Thread(const Thread& );
32     Thread& operator=(const Thread& );
33 };

一些说明:

  1. 我们在start()方法中调用apr库提供的线程API创建一个线程: apr_thread_create(),并将线程函数thread_proc()Thread*为线程函数参数传入apr_thread_create()即可,具体代码在后面贴出。
  2. Join()函数用于等待线束线程,而destroy() 则是用于显示销毁该线程所占用的资源。
  3. 线程基类有一个纯虚函数run(),即应用线程继承Thread类后必须实现run()函数,即实现程序的业务逻辑
  4. start()创建完线程后系统便在某一时刻开始执行thread_proc()方法,我们在该方法中会调用run()函数,由于多态性,也就会调用应用程序多实现的run()函数了

具体实现(Thread.cpp):

 1 int    Thread::start()
 2 {
 3     apr_status_t        rv;
 4     apr_threadattr_t*    thrattr = NULL;    
 5     apr_threadattr_create(&thrattr, g_mpool);
 6 
 7     //创建一个线程
 8     if ((rv = apr_thread_create(&thr_, thrattr, Thread::thread_proc, this, g_mpool)) != APR_SUCCESS)
 9     {
10         set_error_code(rv);
11         char errbuf[512];
12         apr_strerror(rv, errbuf, sizeof(errbuf));
13         log2DebugView("Thread: failed create a thread: [%d][%s]\n", rv, errbuf);
14         return rv;
15     }
16     apr_sleep(100000);        //ensure the thead_proc is running
17 
18     return rv;
19 }
20 //等待线束线程
21 int    Thread::join()
22 {
23     bExit_    = true;
24     notify();
25     apr_sleep(100000);
26 
27     apr_status_t rv = 0;
28     return apr_thread_join(&rv, thr_);
29 }
30 //销毁线程
31 void Thread::destroy()
32 {
33     if (!bExit_)
34         join();
35     cond_.destroy();
36 }
37 //线程函数,将会调用子类实现的run()方法
38 void* Thread::thread_proc(apr_thread_t* th, void* data)
39 {
40     Thread* pthis = static_cast<Thread*>(data);
41     while (!pthis->bExit_)
42     {
43         //调用子类实现的run()方法
44         pthis->run();
45 
46         //wait for signal
47         pthis->cond_.wait();
48     }
49 
50     printf("thread exit, id: %d\n", pthis->get_thread_id());
51     apr_thread_exit(th, APR_SUCCESS);
52     return NULL;
53 }

    这里我们不要太过意研究线程在具体代码是如何创建的,比如在start()函数中,在windows下线程函数可以是 UINT thread_proc(LPVOID param); 而创建线程则是调用__beginthreadex()的windows API即可,具体可参照windows的线程创建和销毁逻辑。线程使用如下:

应用示例

 1 //继承Thread类并实现run()接口,有点类似Java或C#的用法
 2 class MyThread : public Thread
 3 {
 4 public:
 5     MyThread(){ loop_ = true; }
 6     virtual MyThread(){}
 7 
 8     //只关心如何实现业务逻辑,而看不到线程是如何创建的
 9     virtual void run()
10     {
11         while (loop)
12         {
13             //do some work
14         }
15         printf("MyThread exit.\n");
16     }
17 
18 private:
19     bool loop_;
20 };
21 
22 // 在程序中使用如下
23 MyThread* pmt = new MyThread();
24 pmt->start();        //调用start()方法后,即启动了一个线程了


    这样,我们就完成了一个线程类的封装和使用了,代码不多,但很常用哈。最后说明一下线程类中使用一个Condition的类,其实也就是一个对事件的封装使用,完全可以用windows下的 SetEvent()/WaitForSingleObject()替代或Linux下的pthread_condition_t的pthrad_condition_signal()/pthread_condition_wait()替代,即等待事件和通知事件的处理。
    下一节我将会利用这个线程类实现一个简单的线程池,便于我们在程序中使用。

posted @ 2013-10-07 18:12 Choice 阅读(11968) | 评论 (0)编辑 收藏

2013年7月21日 #

利用C/C++库函数读写文件的基本用法

      本文分别简单讲述了如何利用C和C++库函数对文件的读写,涵盖了常用的文件操作函数。网上关于C/C++文件操作的介绍的博客很多,所以本文也大同小异,也主要是做一个备忘,不有每次都百度了。但本文重点在于几个库函数的运用,而不是从文件的属性或分类开始。
说明:由于本人水平有限或写博客时,打字疏忽再所难免。所以对于函数的使用有异议的请以msdn或相关标准文档为准,而本文主要是告诉你各个文件操作函数的功能和用法。
一、C文件操作:
      C语言中,文件的操作都是通过一个FILE类型的文件指针进行,也就是说只有通过文件指针,才能调用相应的文件。FILE是一个由系统定义的结构体(定义在stdio.h中),可以存放文件的相关信息。
文件的打开(fopen函数)
      文件的操作过程一般为:打开 => 读/写 => 关闭。
fopen函数的原型如下:
   FILE* fopen(const char* filename, const char* mode);
函数调用就比较简单了:
   FILE* fp;
   fp = fopen("file1.txt", "r");
如果打开成功,返回file1.txt文件的指针,如果打开失败,返回一个NULL指针,所以调用fopen()后我们需要检查fp的值才进行下一步操作。fopen()的第一个参数为文件名,第二个参数为文件打开方式,含义如下表:

使用文件方式

含义

"r"(只读) 为输入打开一个文本文件
"w"(只写) 为输出打开一个文本文件
"a"(追加) 为追加打开一个文本文件
"rb"(只读) 为输入打开一个二进制文件
"wb"(只写) 为输出打开一个二进制文件
"ab"(追加) 为追加打开一个二进制文件
"r+"(读写) 为读/写打开一个文本文件
"w+"(读写) 为读/写创建一个文本文件
"a+"(读写) 为读/写打开一个文本文件
"rb+"(读写) 为读/写打开一个二进制文件
"wb+"(读写) 为读/写创建一个二进制文件
"ab+"(读写) 为读/写打开一个二进制文件

说明: 1. 使用"r"时,如果文件不存在,则出错。
         2. 使用"w"时,如果没有文件,则创建一个新文件。
         3. 使用"a"时,如果希望向文件尾添加数据,则该文件必须存在,否则出错。
         4. "r+","w+","a+"都是可以输入和输出数据,但必须遵守上述3点
         5. 操作二进制文件时,加上"b"字符,且二进制文件对换行符不会进行转换,而文本文件会将换行符转换为回车和换行两个字符。
文件的关闭(fclose函数)
      在使用完一个文件后,若不关闭则会造成系统资源泄漏。使用fclose()关闭文件即可,原型为 int fclose(FILE* fp)。使用:fclose(fp); flose()返回0时为顺利关闭文件,否则返回EOF(-1)。
文件的读写
   
1) fputc(), fgetc()分别为从文件流中写和读一个字符,原型分别如下:
   写:int fputc(int c, FILE* fp);      读:int fgetc(FILE* fp); 失败时均返回EOF
   2) fputs(), fgets()分别为从文件流中写和读一个字符串,原型分别如下:
   写:int fputs(const char* str, FILE* fp);  例如: fputs("I love this game!", fp);
   读:char* fgets(char* str, int n, FILE* fp); 从流中读取n-1个字符或读完一行,参数str用于接收读取的字符串。注意当读取一行时,不包括行尾的'\n'字符。
   3) fseek() 一般用于二进制模式打开的文件中,功能是定位到流中指定的位置。原型如下:
   int fseek(FILE* fp, lont offset, int whence); 参数offset是移动的字符数,whence是移动的基准,取值是:
      SEEK_SET 0 //文件开头 
      SEEK_CUR 1 //当前读写的位置 
      SEEK_END 2 //文件尾部 
   4) fprintf(),fscanf()是将数据按格式输出输入到文件流中,用法类似printf()和scanf()。原型分别如下:
   int fprintf(FILE* fp, const char* format, ...); 它与printf()不同的就是将数据写到了文件流中,而不是控制台罢了。
   int fscanf(FILE* fp, cosnt char* format, ...); 从文件流中按格式读取,与scanf()不同的就是数据是从文件流中读取而已。
   例如: fprintf(fp, "count=%d", 5);         fscanf(fp, "%d", &x);
   5) feof()是检测是否已到文件尾,是返回真,否则返回0,原型是 int feof(FILE* fp);
   6) rewind() 则是把当前的读写位置回到文件开始,相当于 fseek(fp, 0L, SEEK_SET); 原型: void rewind(FILE* fp);
   7) remove() 删除文件,原型: int remove(const char* filename);  参数为要删除的文件名,成功则返回0;
   8) fread(), fwrite() 它们相当于可将一块的数据读出或写入,相当的方便。原型如下:
   size_t fread(void* ptr, size_t size, size_t n, FILE* fp); 从流中读指定个数的字符,size是每块的字节娄,n则是读取的块数。
   size_t fwrite(const void* ptr, size_t size, size_t n, FILE* fp); 类似的是向文件流中写入n块size字节数的数据。可以看到数据指针为void*型,即可以使用任何类型的指针来替换。例如:
现在一个结构体: struct student_t{char name[16]; int id; int age;}; 创建三个学生的数据并赋值:struct student_t stu[3];
这时,当我们找开文件后(一般是进制模式),可以调用fwrite()将三个学生的数据都写入到文件中,两种方式:
      for(int i = 0; i < 3; ++i)
         fwrite(&stu[i], sizeof(struct student_t), 1, fp); 
      或者:fwrite(stu, sizeof(struct student_t), 3, fp);
些时,我们调用fread()函数便可很轻松的将刚才写入的3个学生的数据读取出来:
      struct student_t stus[3];
      for(int i = 0; i < 3; ++i)
         fread(&stus[i], sizeof(struct student_t), 1, fp);
      或者:fread(stus, sizeof(struct student_t), 3, fp); 这样便可将三个学生的数据读入到stus变量中了(有木有很方便呐)
注意:如果你发现使用fread()读取之后,最后一个学生读取的数据不完全,可能是由于你没有使用二进制模式打开的原因。
      9) 最后是tmpfile()和tmpnam(),前者为生成一个临时文件,后者为生成一个唯一的文件名,具体使用在此不介绍了。
二、使用C++中的fstream文件流操作类进行文件的读写
      使用fstream操作文件与使用C库函数类似,只不过fstream为面向对象方式,或多了上些C++的特性。首先,这里大概有三个流:
fstream为文件输入输出流,ifstream为输入文件流,ofstream为输出文件流,它们与ostream不同的就流的目的地为文件,而不是控制台。这里只介绍与上述的一些不同点:
      1. 打开文件,如可以是 ifstream input_file("file2.txt"); 这样将会以默认方式打开file2.txt文件并进行读取。也可使用open()方式打开一个文件,并指定打开方式,例如:
      ifstream input_file;
      input_file.open("file2.txt", ios::binary);
打开后,可以使用is_open()检测是否打开成功:
      if(input_file.is_open()){},然后可以使用流操作符向文件写数据了,例如:
      input_file << "this is a test line";
      input_file << "another info";
另外,这里的文件打开方式在ios空间下:
   ios::app 添加到文件尾
   ios::ate 把文件标志放在末尾而非起始。
   ios::trunc 默认. 截断并覆写文件。
   ios::nocreate 文件不存在也不创建。
   ios::noreplace 文件存在则失败。
ofstream使用方式类似,读取一行数据可以使用getline(buf, count), 类型于fgets()。fstream类还提供一个很多其它方法,如fclose()为关闭文件,eof()用于检测状态是否已经到了文件末尾。
这里还有两个类似于上述的fread()和fwrite()函数,是read(), write(),功能和用法类似类似,例如:
      output_file.write((const char*)stu, 3 * sizeof(struct student_t));
      input_file.read((char*)stus, 3 * sizeof(struct student_t));
注意,这里也需要使用二进制模式打开,否则read的时候最后的上些数据读不完全。调用上面两个函数后可以使用bad()来检测文件流对象是否错误,例如,if(input_file.bad()){printf("error when read file\n"); return;},最后input_file.close()即可。
      最后,C/C++文件的操作并不复杂,多使用几次便可熟练掌握,需要注意的就是文件的打开方式,和当用同一个文件指针进行又读又写时,注意文件指针位置的移动。


posted @ 2013-07-21 19:50 Choice 阅读(5119) | 评论 (0)编辑 收藏

2013年5月1日 #

C++实现类似printf的不定参数函数

      其实,C++实现类似printf()函数的不定参数很简单,代码一写就明白了:
 1 #define BUFFSIZE 4096
 2 
 3 void Log2DebugView(const char* format,  ...)
 4 {
 5     char buf[BUFFSIZE];
 6     char* p = buf;
 7     va_list args;
 8     va_start(args, format);
 9     vsprintf(p, format, args);
10     va_end(args);
11     //OutputDebugStringA(buf);
12 }
      其中最核心的就是第7到第10行,解析format字符串中的格式化参数,如 %c, %d 等。最后得到的'buf',即是格式化后的字符串。比如调用了: Log2DebugView("Test dbgview for id=%d, name=%s.", 5, "chosen"); 然后buf的内容便是: "Test dbgview for id=5, name=chosen."。
      至于最后的那句"OutptDebugString(buf);",是一个系统调用函数,主要用于将格式化后信息输出到Visual Studio的'output'窗口中(Debug模式下),或者输出到一个叫"dbgView.exe"的程序窗口中(运行模式下)。这对于我们调试来说是一个非常有用的工具,而且程序运行完后,这些格式化后的信息,还会保留在output或debView的窗口中,供我们分析。注:dbgView.exe可以在网上搜索并免费下载使用。

posted @ 2013-05-01 17:23 Choice 阅读(2654) | 评论 (2)编辑 收藏

2013年4月29日 #

开源跨平台C++日志组件Log4cxx的入门级使用

      由于上一文章已经讲述了如何在VS2010下编译log4cxx源码,将会生成两个文件:log4cxx.lib 和 log4cxx.dll。编译时导入该lib文件,运行时,需要确保该dll文件在程序运行目录或系统目录下即可。 
      下面来介绍一下该日志组件的使用,如果你是初次接触log4cxx,恭喜你,可以继续往下走,并可能得到一些帮助哦,那高手呢,就路过吧。(注:本人水平不限,疏忽之处再所难免,欢迎拍砖。)
   1.   概况: 要想使用log4cxx日志组件,除了需要上述两个文件外,还需要一个文本配置文件,一般命名为 logging.properties,loggging.config 等相关便于识别的字样。还就是在程序中导入相应的头文件,使用cofigure加载配置文件,然后得到一个LoggerPtr的指针,再进行日志的输入即可。日志的输入格式以及输入到哪儿,均是在配置文件中指定的。
   2.   配置文件简介:首先,配置文件格式大概如下所示,你可以按照要求格式自己写,也可以从其它地方copy一个过来。这里也做一些简单的介绍。一个配置文件中可能包含多个以下文本段,一般以后这么些行文本表示一个logger,即在你的项目中,可能会把不同的项目功能分支,输出到不同的日志里,在些指定多个logger及输入文件即可。下面来简单介绍一下其中一些行的含意:
1log4j.logger.TradeServer=DEBUG, B
2#log4j.appender.B=org.apache.log4j.RollingFileAppender
3log4j.appender.B=org.apache.log4j.DailyRollingFileAppender
4log4j.appender.B.File=log/myclient.log
5#log4j.appender.B.MaxFileSize=5000KB
6#log4j.appender.B.MaxBackupIndex=10
7log4j.appender.B.DatePattern='.'yyyy-MM-dd
8log4j.appender.B.layout=org.apache.log4j.PatternLayout
log4j.appender.B.layout.ConversionPattern=%d{ISO8601} %5p %-11c{1} - %m%n
      行1:指定了logger的名字为"TradeServer", 即在程序代码中可以这样得到此logger:
              log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("TradeServer");   然后还指定了该logger为DEBUG级别,
              以及一个appender:"B", 可以观察到接下来2-9行,均有".appender.B"字样。至于关于更详细配置信息的讲解,百度一下。
      行4:日志将会输出到程序要目录下的 log 目录下的 myclient.log 文件中,当然可以改为你喜欢的日志文件名。
      行7:为日志输出的时间格式
      行8:使用何种日志方式,log4cxx默认有几种layout,这里就不介绍了
      行9:为日志信息输出的格式
   2. 配置文件写好后,假如文件名为:logging.config ,文件名将在程序代码中加载log4cxx的配置文件时使用,比如:
      log4cxx::PropertyConfigurator::configure("conf/logging.config");
   3. 示例代码:
1// load the configure file
2log4cxx::PropertyConfigurator::configure(_T("conf/logging.properties"));
3// get the "TradeServer" logger
4log4cxx::LoggerPtr    logger    = log4cxx::Logger::getLogger("TradeServer");
5// log info
6//LOG4CXX_INFO(log4cxx::Logger::getLogger("TradeServer"), "Trade Server running...");
7LOG4CXX_INFO(logger, "Trade Server running...");
8// log error
9LOG4CXX_ERROR(logger, "run: error=" << rv << ", errbuf=" << errbuf);
      上述第2行在程序运行时加载配置即可,第4行可以作为全局logger,即可以其它地方使用了。在宏的logger后面,可以使用流操作符写信息,很方便的哦(其实个人感觉有时候不太方便!),另外还有一些其它级别的logger,如LOG4CXX_AAA等,具体可以百度的。
   4. Log4cxx的简单使用就介绍到这儿啦。log4cxx功能很强大,也还有很多东西我在这儿也没介绍到,还需要多研究学习。但简单的使用是不是并没有想象中的那么难啊~

posted @ 2013-04-29 18:39 Choice 阅读(3036) | 评论 (0)编辑 收藏

VS2010编译log4cxx-0.10.0

 

初次在cppblog写博客,希望未来越写越好,与大家分享探讨技术人生(说起来都没底气=_=)。
Log4cxx是业界跨平台日志组件中用得比较广泛的,具体的介绍说明这里就不多了,百度一下,你就知道。本文章主要讲述了源码如何在VS2010中编译成功,至于简单的使用将在下一篇博文是讲述。

最近,项目leader说要用到apache-log4cxx- 0.10.0,让我先熟悉一下。便从 www.apache.org 分别下载了 apache-log4cxx-0.10.0.zip , apr-1.4.6-win32-src.zip , apr-util-1.5.1-win32-src.zip 三个开源源码包。看来要使用的话还必须先编译成库文件才是。apr apr-util  编译倒是没什么问题,但 log4cxx 就遇到一些问题了。从网上找了些答案,已记不清原网络链接了,所以也在这里写一篇文章以备日后查看。

切入正题,步骤大概如下:

1. 到Apache站点上下载上述三个源码包

2. 将源码包解压至同一目录下,并更改目录名: apr-1.4.6 改为 apr,将 apr-util-1.5.1 改为apr-util

3. 在开始菜单的VS2010目录下,启动“Visual Studio 命令提示(2010),并切换到 apache-log4cxx-0.10.0 目录下,运行 configure.bat

4. 执行完上述命令后,再运行 configure-aprutil.bat,这时可能会提示 ‘sed’ 不是内部或外部命令xxx,这时我是装了一个 Cygwin,关于Cygwin的安装可能参考如下 http://www.cygwin.cn/site/install/ 然后在我的电脑属性里设置环境变量,在path变量中加入如: G:\cygwin\bin 至于Cygwin在安装过程中会有一个步骤让你选择安装哪些包,这时可以输入 sed 关键字过滤选项即可,然后沟选相应的包安装即可。

5. 再运行 configure-aprutil.bat,现在应该会出现 cygwin warning等信息

6. 启动VS2010并打开 apache-log4cxx-0.10.0\projects\ 下的 log4cxx.dsw 工程,然后选择 “是”,转换并打开此项目

7. 将 log4cxx设置为启动项,然后开始编译

8. 此时可能会出现以下一些错误,一一处理即可:

    1xxx\apache-log4cxx-0.10.0\src\main\include\log4cxx/spi/loggingevent.h(155):error C2252: 只能在命名空间范围内显式实例化模板' 错误.

a)双击 "输出" 窗口中的错误行, 此时会在 "代码窗口" 中出现错误的位置.

b)选择 LOG4CXX_LIST_DEF, 按键盘 F12, 此时会跳转到该宏的定义

c)将

#define LOG4CXX_LIST_DEF(N, T) \

template class LOG4CXX_EXPORT std::allocator<T>; \

template class LOG4CXX_EXPORT std::vector<T>; \

typedef std::vector<T> N

注释掉,替换为:

#define LOG4CXX_LIST_DEF(N, T) \

typedef std::vector<T> N

    2network_io\unix\multicast.c(137): error C2079:“mip”使用未定义的 struct“group_source_req"' 等错误.

         双击第一行出错输出, 136 148 行的

         #if MCAST_JOIN_SOURCE_GROUP  注释, 替换为

         #if defined (group_source_req)

    3xxx \src\main\cpp\stringhelper.cpp(64): error C2039: “insert_iterator”: 不是“std”的成员' 等错误.

           在该.cpp (stringhelper.cpp) 加入头文件 #include <iterator>

    4无法解析的外部符号 xxx' 等错误

          则右键log4cxx项目属性,通用属性 | 框架和引用,将apr, aprutil, xml 添加至 log4cxx 的引用中

9. 选择重新生成解决方案即可,编译将得到 log4cxx.lib 和 log4cxx.dll 两个文件

编译成功后,就可以将该lib和dll文件在你的项目中应用了哦~
简单使用介绍将在下一节介绍。

posted @ 2013-04-29 17:42 Choice 阅读(1140) | 评论 (0)编辑 收藏

仅列出标题