Chinaunix首页 | 论坛 | 博客
  • 博客访问: 233601
  • 博文数量: 46
  • 博客积分: 1400
  • 博客等级: 上尉
  • 技术积分: 620
  • 用 户 组: 普通用户
  • 注册时间: 2009-01-12 18:04
文章分类

全部博文(46)

文章存档

2010年(7)

2009年(39)

我的朋友

分类:

2009-03-18 14:04:29

关键功能模块分析

分析

3.1.1 消息队列

Mail queue 邮件队列默认路径在/var/qmail/queue目录下。

在这个目录下,创建以下子目录分别存放邮件的不同部分。每个邮件消息做为一个单独的文件存储,用它的inode号做为文件名。Inode是在pid目录根据 [pid+createtime+seq]创建的消息文件,成功情况下获取的inode号。除pid目录,其他子目录都是根据inode号进行命名。

目录

描述

操作模块

Pid

存放新接收的邮件消息

Qmail-queue

Mess

包含要投递的邮件消息

Qmail-queue

Intd

包含由qmail-queue重新组织的构造的消息头

Qmail-queue

Todo

包含完整的消息头,包括发送方和接收方

Qmail-queue

Qmail-send

info

包含邮件接收方地址

Qmail-send

Local

包含本地接收方地址

Qmail-send

Remote

包含远程接收方地址

Qmail-send

lock

包含用来指示何时消息能被qmail-send读取,针对todo目录

Qmail-queue

Qmail-send

Bounce

包含已经永久投递失败的信息

Qmail-send

 

3.1.2 邮件消息的生与死

每个message,不论是qmail-inject创建还是qmail-smtpd创建,均是将消息体传递给qmail-queue进程处理。每开始处理一个message,都会创建一个新的qmail-queue进程进行处理。

 

qmail-queue从标准输入0读取发送过来的消息体数据,将消息写入消息队列,并进行相关预处理。

Qmail-send从消息队列中获取待发送的消息,进行消息分拣处理后,交给对应的发送程序进行发送。

 

消息体在整个消息队列中的状态如下图:

 

3.1.2.1消息插入

Qmail-queue将启动后就会在queue/pid子目录下创建一个唯一的文件,根据该文件的inode号保证两个消息没有重名。至于pid下的文件是否保存消息体内容,代码上实现上看是没有的。

此时该状态尚未结束,qmail-queue根据inode号在mess子目下创建消息文件后,状态结束。

3.1.2.2消息存储

Qmail-queue根据inode号在queue/mess子目下创建消息存储文件fd将消息体存储在该文件下。并同时在intd子目下创建inode号命名的文件。

此时该状态结束。

3.1.2.3消息头提取

Qmail-queue扫描mess下消息文件,将消息头信息复制到intd下消息文件中存储。

并将intd下消息文件linktodo子目录下。通知qmail-send进程。

此时该状态结束。

3.1.2.4消息排队与处理

Qmail-send一直在后台运行,不停的扫描todo目录,等待待处理的邮件消息。一旦有新的邮件,qmail-send检查infolocal remote子目录是否存在同名文件,如果有删除掉。

接下来就将读取todo下的邮件文件,将接收地址信息存入info目录下的同名文件中。

Qmail-send在根据info目录下的邮件文件中的接收地址信息,在local或者remote子目录中创建同名文件,将接收地址写入。

此时该状态结束。

3.1.2.5消息投递

Qmail-send处理localremote子目录下的文件。每个文件都包含接收者的地址信息。Qmail-send读取接收者地址信息,并试图将mess子目录下的邮件正文投递给指定的接收方。Qmail-send将每个接收方根据投递的状态标以DONENOT DONE

一旦所有接收方都标以DONE,该文件被删除,邮件发送成功。

如果投送收到一个临时性错误,接收方将标以NOT DONE,等待再次处理。

如果投送收到永久性失败,接收方标以DONE并在bounce子目录下的文件中添加一行记录。

 

Bounce目录下的文件将和mess目录的文件一起做为新的消息被重新交给qmail-queue处理,接收者为原邮件的发送者。一旦消息创建了,bounce目录下的文件就被删除。

 

3.1.3 消息队列瞬时情况

队列里的每条消息都由唯一的号码标识,假定某条消息是的标识码是457。队列被组织成几个目录,每个目录都可能包含和这条消息相关的文件:

mess/457: 消息正文

todo/457: 信封: 消息的来源地址和目的地址。实际是指向intd/457的链接。

intd/457: 信封,由qmailqueue生成。

info/457: 信封上的发送者地址,预处理后生成。

local/457: 本地接受地址,预处理后生成。

remote/457: 远程接受地址,预处理后生成。

bounce/457: 传输错误信息。

以下是一条消息所有可能的状态。“+”号表示该目录下文件存在,“-”表示该目录下文件不存在,“?”表示未知。

 

S1. -mess -intd -todo -info -local -remote -bounce

S2. +mess -intd -todo -info -local -remote -bounce

S3. +mess +intd -todo -info -local -remote -bounce

S4. +mess ?intd +todo ?info ?local ?remote -bounce (queued,表示已经进入队列)

S5. +mess -intd -todo +info ?local ?remote ?bounce (preprocessed,表示已通过预处理)

分析

3.2.1进程环境

代码参考:

struct qmail {

  int flagerr;

  unsigned long pid;/* qmail-queue pid*/

  int fdm; /*mail message fd*/

  int fde; /* envelope information fd*/

  substdio ss;

  char buf[1024];

} ;

 

extern int qmail_open();

extern void qmail_put();

extern void qmail_puts();

extern void qmail_from();

extern void qmail_to();

extern void qmail_fail();

extern char *qmail_close();

extern unsigned long qmail_qp();

 

标准输入0 上数据格式:

Mime 邮件体信息。 Data命令中获取。

 

标准输出1 上数据格式:

信封信息。从Mail From Rcpt to 命令获取。格式为:

Fxxxxxxxxxxxxx[mail from输入]

Txxxxxxxxxxxxx[rcpt to 输入]

整个qmail-queue都是通过该结构进行操作。理清该结构的接口,也就对整个qmail-queue进程环境搞清了。

3.2.2操作流程

代码参考: qmail-queue.c

按照邮件状态分析

l  消息插入
参考代码

/*创建唯一文件 pidfn = qmail_dir/queue/pid/xxx[mypid].xxxxx[datetime].x[seq]*/

 pidopen();

 if (fstat(messfd,&pidst) == -1) die(63);

 

/*生成将要建立的文件的文件名

几个文件都是根据刚才建立的pid文件的inode节点号命名的.inode不可能被两个文件同时占用,这保证了邮件唯一性。

其中mess目录下的文件放置有一个%23的问题,

tips: 因为是%23所以该目录名最大的可能只有22,明白queue/mess目录下目录为什么最大只22了吧

比如说inode节点号为3455,那么3455%23=5,那么将生成/var/qmail/queue/mess/5/3455 这样一个文件来存放邮件。

/var/qmail/queue/todo/3455/var/qmail/queue/intd/3455是相同的,都是保存用户id,进程id,mailfrom,rcptto的。

*/

 messnum = pidst.st_ino;

 messfn = fnnum("mess/",1);

 todofn = fnnum("todo/",0);

 intdfn = fnnum("intd/",0);

 

/*创建mess子目录下邮件消息文件*/

 if (link(pidfn,messfn) == -1) die(64);

/*删除pid下临时文件*/

 if (unlink(pidfn) == -1) die(63);

 flagmademess = 1;

此时邮件队列状态如下:

S1. +mess -intd -todo -info -local -remote -bounce

 

 

l  消息存储

参考代码

/*设置messfd为标准输出fd[ssout]*/

 substdio_fdbuf(&ssout,write,messfd,outbuf,sizeof(outbuf));

 

 /*设置0为标准输入fd[ssin], 0 读入的是 message stream*/

 substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf));

 

 /*qmail-queue对邮件消息体头上添加的自己的信息

  信息格式:

.生成自已的邮件首部,也就是你在邮件头中见到的类似下面的东西

       Recevied (qmail 855 invoked by uid 0); 2 May 2003 12:18:09 -0000

*/

 if (substdio_bput(&ssout,received,receivedlen) == -1) die_write();

 

 /*将输入的数据拷贝到输出缓冲中*/       

 switch(substdio_copy(&ssout,&ssin))

  {

   case -2: die_read();

   case -3: die_write();

  }

 

 /*输出flush messfd*/

 if (substdio_flush(&ssout) == -1) die_write();

 if (open_sync && fsync(messfd) == -1) die_write();

 

 /*创建intdfn*/     

 intdfd = open_excl(intdfn);

 if (intdfd == -1) die(65);

 flagmadeintd = 1;

此时邮件队列状态如下:

S2. +mess +intd -todo -info -local -remote -bounce

 

 

l  消息头提取

参考代码

/*initfd写入:

        uxxx[uid] pxxx[pid]

 */

 if (substdio_bput(&ssout,"u",1) == -1) die_write();

 if (substdio_bput(&ssout,tmp,fmt_ulong(tmp,uid)) == -1) die_write();

 if (substdio_bput(&ssout,"",1) == -1) die_write();

 

 if (substdio_bput(&ssout,"p",1) == -1) die_write();

 if (substdio_bput(&ssout,tmp,fmt_ulong(tmp,mypid)) == -1) die_write();

 if (substdio_bput(&ssout,"",1) == -1) die_write();

 

 

 if (substdio_get(&ssin,&ch,1) < 1) die_read();

 if (ch != 'F') die(91);

 if (substdio_bput(&ssout,&ch,1) == -1) die_write();

 for (len = 0;len < ADDR;++len)

  {

   if (substdio_get(&ssin,&ch,1) < 1) die_read();

   if (substdio_put(&ssout,&ch,1) == -1) die_write();

   if (!ch) break;

  }

 if (len >= ADDR) die(11);

 

 if (substdio_bput(&ssout,QUEUE_EXTRA,QUEUE_EXTRALEN) == -1) die_write();

 

 for (;;)

  {

   if (substdio_get(&ssin,&ch,1) < 1) die_read();

   if (!ch) break;

   if (ch != 'T') die(91);

   if (substdio_bput(&ssout,&ch,1) == -1) die_write();

   for (len = 0;len < ADDR;++len)

    {

     if (substdio_get(&ssin,&ch,1) < 1) die_read();

     if (substdio_bput(&ssout,&ch,1) == -1) die_write();

     if (!ch) break;

    }

   if (len >= ADDR) die(11);

  }

 

 if (substdio_flush(&ssout) == -1) die_write();

 if (open_sync && fsync(intdfd) == -1) die_write();

 

 /*todofd link to intfd*/

 if (link(intdfn,todofn) == -1) die(66);

 

 triggerpull();

 

顺序执行以上三个状态后,qmail-queue成功退出。

此时邮件队列状态如下:

S3. +mess +intd +todo -info -local -remote -bounce

阅读(1145) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~