Clamav杀毒软件源码分析笔记[八]

Clamav杀毒软件源码分析笔记[八]


刺猬@http://blog.csdn.net/littlehedgehog





[command]


上回说到主循环(accept_th),这是一个死循环,因为我们Clamd在没有什么特殊的情况下是一直阻塞地苦苦等待在等待有客户端发出请求,然后安排好线程派发(dispatch)工作,接着我们的注意力便专注于线程的运作,这里的command是我们的重点研究对象.

说来command这个函数集众功能于一身,貌似强大无比,但自然也是个程序逻辑中转站,下面来看看里面的部分代码:

  1.   retval = poll_fd(desc, timeout);  

这里采用了多路复用的代码, 说来多路复用本身还有两套实现方案,这是unix阵营分裂的产物吧. 我这里只看看select,有如下资料:



  select系统调用是用来让我们的程序监视多个文件句柄(file descriptor)的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有某一个或多个发生了状态改变。

文件在句柄在Linux里很多,如果你man某个函数,在函数返回值部分说到成功后有一个文件句柄被创建的都是的,如man socket可以看到“On success, a file descriptor for the new socket is returned.”而man 2 open可以看到“open() and creat() return the new file descriptor”,其实文件句柄就是一个整数,看socket函数的声明就明白了:
int socket(int domain, int type, int protocol);


select函数原型如下:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
函数的最后一个参数timeout显然是一个超时时间值,其类型是struct timeval *,即一个struct timeval结构的变量的指针,所以我们在程序里要申明一个struct timeval tv;然后把变量tv的地址&tv传递给select函数。struct timeval结构如下:

  1. struct timeval {
  2.              long    tv_sec;         /* seconds */
  3.              long    tv_usec;        /* microseconds */
  4.          };
   第2、3、4三个参数是一样的类型: fd_set *,即我们在程序里要申明几个fd_set类型的变量,比如rdfds, wtfds, exfds,然后把这个变量的地址&rdfds, &wtfds, &exfds 传递给select函数。这三个参数都是一个句柄的集合,第一个rdfds是用来保存这样的句柄的:当句柄的状态变成可读的时系统就会告诉select函数返回,同理第二个wtfds是指有句柄状态变成可写的时系统就会告诉select函数返回,同理第三个参数exfds是特殊情况,即句柄上有特殊情况发生时系统会告诉select函数返回。特殊情况比如对方通过一个socket句柄发来了紧急数据。如果我们程序里只想检测某个socket是否有数据可读,我们可以这样:


  1. fd_set rdfds; /* 先申明一个 fd_set 集合来保存我们要检测的 socket句柄 */
  2. struct timeval tv; /* 申明一个时间变量来保存时间 */
  3. int ret; /* 保存返回值 */
  4. FD_ZERO(&rdfds); /* 用select函数之前先把集合清零 */
  5. FD_SET(socket, &rdfds); /* 把要检测的句柄socket加入到集合里 */
  6. tv.tv_sec = 1;
  7. tv.tv_usec = 500; /* 设置select等待的最大时间为1秒加500毫秒 */
  8. ret = select(socket + 1, &rdfds, NULL, NULL, &tv); /* 检测我们上面设置到集合rdfds里的句柄是否有可读信息 */
  9. if(ret < 0) perror("select");/* 这说明select函数出错 */
  10. else if(ret == 0) printf("超时/n"); /* 说明在我们设定的时间值1秒加500毫秒的时间内,socket的状态没有发生变化 */
  11. else { /* 说明等待时间还未到1秒加500毫秒,socket的状态发生了变化 */
  12.     printf("ret=%d/n", ret); /* ret这个返回值记录了发生状态变化的句柄的数目,由于我们只监视了socket这一个句柄,所以这里一定ret=1,如果同时有多个句柄发生变化返回的就是句柄的总和了 */
  13.     /* 这里我们就应该从socket这个句柄里读取数据了,因为select函数已经告诉我们这个句柄里有数据可读 */
  14.     if(FD_ISSET(socket, &rdfds)) { /* 先判断一下socket这外被监视的句柄是否真的变成可读的了 */
  15.         /* 读取socket句柄里的数据 */
  16.         recv(...);
  17.     }
  18. }
  1. /************关于本文档********************************************
  2. *filename: Linux网络编程一步一步学-select详解
  3. *purpose: 详细说明select的用法
  4. *wrote by: zhoulifa([email protected]) 周立发(http://zhoulifa.bokee.com)
  5. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  6. *date time:2007-02-03 19:40
  7. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  8. * 但请遵循GPL
  9. *Thanks to:Google
  10. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  11. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  12. *********************************************************************/

  13. int sa, sb, sc;
  14. sa = socket(...); /* 分别创建3个句柄并连接到服务器上 */
  15. connect(sa,...);
  16. sb = socket(...);
  17. connect(sb,...);
  18. sc = socket(...);
  19. connect(sc,...);

  20. FD_SET(sa, &rdfds);/* 分别把3个句柄加入读监视集合里去 */
  21. FD_SET(sb, &rdfds);
  22. FD_SET(sc, &rdfds);
  23.    在使用select函数之前,一定要找到3个句柄中的最大值是哪个,我们一般定义一个变量来保存最大值,取得最大socket值如下:

  24. int maxfd = 0;
  25. if(sa > maxfd) maxfd = sa;
  26. if(sb > maxfd) maxfd = sb;
  27. if(sc > maxfd) maxfd = sc;
  28.    然后调用select函数:
  29. ret = select(maxfd + 1, &rdfds, NULL, NULL, &tv); /* 注意是最大值还要加1 */
  30.    同样的道理,如果我们要检测用户是否按了键盘进行输入,我们就应该把标准输入0这个句柄放到select里来检测,如下:

  31. FD_ZERO(&rdfds);
  32. FD_SET(0, &rdfds);
  33. tv.tv_sec = 1;
  34. tv.tv_usec = 0;
  35. ret = select(1, &rdfds, NULL, NULL, &tv); /* 注意是最大值还要加1 */
  36. if(ret < 0) perror("select");/* 出错 */
  37. else if(ret == 0) printf("超时/n"); /* 在我们设定的时间tv内,用户没有按键盘 */
  38. else { /* 用户有按键盘,要读取用户的输入 */
  39.     scanf("%s", buf);
  40. }
貌似把多路复用说的过多,但这文章主要是为我读书笔记所用,贴上代码,方便我后来温故时习也.


多路复用只是一个开端,打劫之前,踩点所用,下面我们才来看看用户会告知什么消息. 这就比如说,我们从主进程中截获用户一小道消息,只是可能客户端有任务而已,遂分配线程确认工作.


确认工作:

  1. while ((bread = readsock(desc, buff, 1024)) == -1 && errno == EINTR);

只此一句,绝无多言,这里通过read已连接的套接口描述符,我们将客户请求装入buffer缓存区中. 这里的readsock貌似库函数,其实不然,里面封装着recvmsg,在这里就毋庸多言了. 这里用户请求已截获至buffer缓存,接下来就是分析buffer中所含命令了.好在客户/服务通信暗号也只是我们一家所定,还好不复杂,只需字符串一一匹配即可. 如下代码:



  1. if (!strncmp(buff, CMD1, strlen(CMD1)))  /* SCAN */
  2.     {
  3.         if (scan(buff + strlen(CMD1) + 1, NULL, root, limits, options, copt, desc, 0) == -2)
  4.             if (cfgopt(copt, "ExitOnOOM"))
  5.                 return COMMAND_SHUTDOWN;

  6.     }

搜寻buffer中是否含CMD1字符串,作者也很好心的加了注释CMD1表示命令"扫描". 里面绝大部分都是客户端的扫描要求, 但也有其它的一些命令字符串,比如:

  1.     else if (!strncmp(buff, CMD5, strlen(CMD5)))  /* PING */
  2.     {
  3.         mdprintf(desc, "PONG/n");

  4.     }
客户端发送"ping",服务端要应答"pong",这样表明连接畅通. 像这样简单的处理恐怕也只有这一项功能了.回头我们还是来看看病毒扫描吧.



你可能感兴趣的:(socket,struct,cmd,null,Descriptor,杀毒软件)