性能測試要檢查SMTP服務器向外域發信速度的問題,於是動手做了個mock的smtp,就叫做smtpd_mock.
之前一篇文章寫過Epoll+消息隊列的一些爲代碼,而且那次的消息隊列還是用數組實現的,每次都要遍歷,比較慢.
這次的代碼是可運行的,頭文件就不放上來了,大家看代碼及其中的註釋就能理解整個實現過程.
這次的消息隊列用的是linux自帶的鏈表,list.h.
語言不是很陽春白雪,我也本不是高雅的人啊^-^
看代碼的順序和逛青樓一樣,都要走正門的,除非你藝高人膽大,非要走後門也不是不可以的,只是要看人家姑娘是否有意見...
請從main()開始閱讀.
#include "smtpd_mock.h" char* strsub (char *instr, unsigned start, unsigned end) { unsigned n = end - start; char * outstr = (char *)malloc(n+1); //bzero(outstr,n+1); strncpy (outstr, instr + start, n); outstr[n] = 0; return outstr; } int setnonblocking(int sockfd) { if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1) { return -1; } return 0; } void smtp_echo(void* data) { int socket = *(int*)data; char ebuf[128],buffer[BUFFER_SIZE]; int length = 0, z; regex_t reg; regmatch_t pm[10]; const size_t nmatch = 10; const char * split = "\r\n"; char * pline, * cmd; z = regcomp (®, smtp_cmd_format, REG_EXTENDED); if (z != 0){ regerror (z,®, ebuf, sizeof (ebuf)); fprintf (stderr, "%s: regcomp()\n", ebuf); return; } { while (1) { bzero(buffer,BUFFER_SIZE); length = recv(socket,buffer,BUFFER_SIZE,0); if (length == -1) { if(errno == EAGAIN){ break; } syslog(LOG_ERR,"recv - %m"); break; } syslog(LOG_DEBUG,"%s",buffer); pline = strtok (buffer,split); while(pline!=NULL) { syslog(LOG_DEBUG,"%s\n",pline); if (0==(strcasecmp(pline, "."))){ smtp_cmd("HELO"); continue; } z = regexec (®, pline, nmatch, pm, 0); if (z == REG_NOMATCH) { // do nothing; } else if (z != 0) { regerror (z,®, ebuf, sizeof (ebuf)); fprintf (stderr, "%s: regexec('%s')\n", ebuf, pline); return ; } if(pm[1].rm_so != -1) { cmd = strsub (pline, pm[1].rm_so, pm[1].rm_eo); syslog(LOG_NOTICE,"cmd => %s\n", cmd); if(pm[2].rm_so != -1) { syslog(LOG_NOTICE,"other content => %s\n", strsub (pline, pm[2].rm_so, pm[2].rm_eo)); } smtp_cmd(cmd,socket); } pline = strtok(NULL,split); } if(length < BUFFER_SIZE) break; } } regfree (®); return; } void smtp_cmd(char * cmd,int socket) { char buffer[BUFFER_SIZE]; bzero(buffer, BUFFER_SIZE); if(0 == (strcasecmp(cmd,"HELO"))) { strcpy(buffer,"250 Regards from CharlesCui\r\n"); send(socket,buffer,strlen(buffer),0); } else if(0==(strcasecmp(cmd,"QUIT"))) { strcpy(buffer,"221 QUIT OK\r\n"); send(socket,buffer,strlen(buffer),0); close(socket); epoll_ctl(kdpfd, EPOLL_CTL_DEL, socket, &ev); } else if(0==(strcasecmp(cmd,"NOOP"))) { strcpy(buffer,"250 NOOP\r\n"); send(socket,buffer,strlen(buffer),0); } else if(0==(strcasecmp(cmd,"DATA"))) { strcpy(buffer,"354 End data with <CR><LF>.<CR><LF>\r\n"); send(socket,buffer,strlen(buffer),0); } else if(0==(strcasecmp(cmd,"EHLO"))) { strcpy(buffer,"334 250-mail\r\n250-PIPELINING\r\n250-AUTH LOGIN PLAIN\r\n250-AUTH=LOGIN PLAIN\r\n250 8BITMI\r\n"); send(socket,buffer,strlen(buffer),0); } else if(0==(strcasecmp(cmd,"AUTH"))) { strcpy(buffer,"334 dXNlcm5hbWU6\r\n"); send(socket,buffer,strlen(buffer),0); } else if(0==(strcasecmp(cmd, "MAIL"))) { strcpy(buffer,"250 Mail Ok\r\n"); send(socket,buffer,strlen(buffer),0); } else if(0==(strcasecmp(cmd, "RCPT"))) { strcpy(buffer,"250 Rcpt Ok\r\n"); send(socket,buffer,strlen(buffer),0); } else if(0==(strcasecmp(cmd,"220"))) { strcpy(buffer,"220 Welcome to CharlesCui's smtpd mock server.\r\n"); send(socket,buffer,strlen(buffer),0); } else { strcpy(buffer,""); send(socket,buffer,strlen(buffer),0); syslog(LOG_NOTICE,"Error smtp command."); } } int init_smtp(int port) { struct sockaddr_in *server_addr; server_addr = malloc(sizeof(struct sockaddr_in)); server_addr->sin_family = AF_INET; server_addr->sin_addr.s_addr = htons(INADDR_ANY); server_addr->sin_port = htons(port); int server_socket = socket(AF_INET,SOCK_STREAM,0); syslog(LOG_NOTICE,"init_smtp:server_socket => %d\n",server_socket); setnonblocking(server_socket); if( server_socket < 0) { syslog(LOG_ERR,"Create Socket Failed! - %m\n"); exit(1); } if( bind(server_socket,(struct sockaddr*)server_addr,sizeof(struct sockaddr_in))) { syslog(LOG_ERR,"Server Bind Port : %d Failed! - %m\n", port); exit(1); } if ( listen(server_socket, g_listen_size) ) { syslog(LOG_ERR,"Server Listen Failed! - %m\n"); exit(1); } struct rlimit rt; rt.rlim_max = rt.rlim_cur = g_epoll_size; if (setrlimit(RLIMIT_NOFILE, &rt) == -1) { syslog(LOG_ERR,"setrlimit - %m"); exit(1); } else { syslog(LOG_NOTICE,"設置系統資源參數成功!\n"); } return server_socket; } void block_queue(void * param) { /* 姑娘們,排好對,等客了! 老鴇吩咐要做什麼都知道了嗎?(func爲回調函數) */ void(* func)(void* ); int fd; block_queue_node_t *head_node; //param是全局變量bqp block_queue_param_t* bque = (block_queue_param_t*)param; func = bque->func; for(;;) { pthread_mutex_lock(&bque->mutex); pthread_cond_wait(&bque->cond,&bque->mutex); /* 來客啦! */ if(list_empty(&head)) { //哪個小二瞎喊,命名一個客人都沒來! pthread_mutex_unlock(&bque->mutex); continue; }else { /* 大爺,跟我走吧,我那兒寬敞 從鏈表頭部取出一個節點 */ head_node = list_entry(head.next,block_queue_node_t,list); fd = head_node->fd; //大爺,你是我的了! //同時刪除該節點 list_del(&head_node->list); /**/ free(head_node); counter--; } pthread_mutex_unlock(&bque->mutex); /*幹*/ func(&fd); } } int insert_queue(block_queue_param_t *bque,int fd) { //生成臨時節點,用來保存fd block_queue_node_t *b = (block_queue_node_t *)malloc(sizeof(block_queue_node_t)); b->fd = fd; pthread_mutex_lock(&bque->mutex); if(counter > g_listen_size){ //當客人數量超過小姐接待能力的時候 //就放棄接待該客人,並且返回1. //青樓是殘酷滴,一個蘿蔔一個坑 return 1; }else{ counter++; } /* 將新增的節點插入到尾部, 相對應的,block_queue循環體中取節點時, 是從鏈表頭取到的. */ list_add_tail(&b->list,&head); /* 客人到! 姐妹們快搶客啊!(內核用broadcast通知各阻塞的線程) */ pthread_cond_broadcast(&bque->cond); pthread_mutex_unlock(&bque->mutex); return 0; } int init_threads() { size_t i=0; //這是今天的流水賬, //客人們來了都會在這裏(head鏈表)登記的. //都知道今天各位姑娘要做什麼吧(smtp_echo). //爲全局變量bqp設置屬性 bqp.func = (void*)smtp_echo; /* 不許搶客人!(互斥mutex) 說了多少次了,不管男女老幼長短粗細, 只有客人想不到,沒有我們做不到! 別隻盯着帥哥. */ pthread_cond_init(&bqp.cond,NULL); pthread_mutex_init(&bqp.mutex,NULL); /* 姑娘們起牀了! 初始化各個線程 */ for( i = 0; i < g_th_count; ++i) { pthread_t child_thread; pthread_attr_t child_thread_attr; pthread_attr_init(&child_thread_attr); pthread_attr_setdetachstate(&child_thread_attr,PTHREAD_CREATE_DETACHED); /* 養你們是要幹活(block_queue)的, 沒活的時候可以休息着(pthread_cond_wait) 活來了(pthread_cond_signal)就麻利點去接客(head鏈表非空) */ if( pthread_create(&child_thread,&child_thread_attr,(void *)block_queue, (void *)&bqp) < 0 ) { syslog(LOG_ERR,"pthread_create Failed : %s - %m\n",strerror(errno)); return 1; } else { syslog(LOG_NOTICE,"pthread_create Success : %d\n",(int)child_thread); } } } int handler(void* fd) { syslog(LOG_NOTICE,"handler:fd => %d\n",*(int *)(fd)); //向全局變量bqp中插入一個節點 //姑娘們聽好了, //大爺都在排隊呢, //一個個麻利點,伺候起來了! return insert_queue(&bqp,*(int *)fd); } void init_daemon(void) { int pid; int i; if(pid=fork()) exit(0);//是父進程,結束父進程 else if(pid< 0) exit(1);//fork失敗,退出 //是第一子進程,後臺繼續執行 setsid();//第一子進程成爲新的會話組長和進程組長 //並與控制終端分離 if(pid=fork()) exit(0);//是第一子進程,結束第一子進程 else if(pid< 0) exit(1);//fork失敗,退出 //是第二子進程,繼續 //第二子進程不再是會話組長 for(i=0;i< NOFILE;++i)//關閉打開的文件描述符 close(i); chdir("/tmp");//改變工作目錄到/tmp umask(0);//重設文件創建掩模 return; } int main(int argc, char **argv) { char ch; int d = 0; //處理argv while( ( ch = getopt( argc, argv, "p:t:l:e:d?" ) ) != EOF ) { switch(ch) { case 'p': printf("SMTPD_PORT =>%s ", optarg); g_port = atoi(optarg); break; case 't': printf("THREADS_COUNT => %s ", optarg); g_th_count = atoi(optarg); break; case 'l': printf("LENGTH_OF_LISTEN_QUEUE => %s. ",optarg); g_listen_size = atol(optarg); break; case 'e': printf("MAX_EPOLL_SIZE => %s. ",optarg); g_epoll_size = atol(optarg); break; case 'd': printf("RUN AS DAEMON. "); d = 1; case '?': printf("Useage: -p [SMTPD_PORT|8025] -t [THREADS_COUNT|100] -l [LENGTH_OF_LISTEN_QUEUE|1024] -e [MAX_EPOLL_SIZE|1000] -d (RUN AS DAEMON.)\n"); exit(1); default: printf("Not support option :%c\n",ch); exit(2); } } if(d == 1) init_daemon(); //一天的流水賬要記錄下來啊 //初始化 syslog char *ident = "Smtp Mock"; int logopt = LOG_PID | LOG_CONS; int facility = LOG_USER; openlog(ident, logopt, facility); setlogmask(LOG_UPTO(LOG_ERR)); syslog(LOG_INFO,"syslog inited."); //初始化鏈表 INIT_LIST_HEAD(&head); //生成smtp套接字 //本店開張了,歡迎訪問 int server_socket = init_smtp(g_port); int n; if(init_threads() == 0) syslog(LOG_NOTICE,"Success full init_threads."); /* 下面要把本店加入全球領先的企業管理系統中, 該系統節省人力資源, 不需要服務員傻等在門口, 而是客人到了就會通知服務員出來迎賓. */ kdpfd = epoll_create(g_epoll_size); ev.events = EPOLLIN | EPOLLET; ev.data.fd = server_socket; if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, server_socket, &ev) < 0) { fprintf(stderr, "epoll set insertion error: fd=%d < 0", server_socket); return -1; } //老鴇(主線程)負責拉客,姑娘(子線程)負責接客 for(;;) { struct sockaddr_in local; socklen_t length = sizeof(local); int client; //epoll_wait實現了阻塞,而不是busy loop nfds = epoll_wait(kdpfd, events, g_epoll_size, -1); for(n = 0; n < nfds; ++n) { //判斷套接字 //看是熟客還是生客 if(events[n].data.fd == server_socket) { //新新新新,新來的吧 //你是新新新新新來的吧 client = accept(server_socket, (struct sockaddr *) &local,&length); //是生客就發一個新的id卡(client) if(client < 0){ syslog(LOG_ERR,"accept - %m"); continue; } setnonblocking(client); //先跟大爺打聲招呼,顯得我們姑娘主動些 smtp_cmd("220",client); ev.events = EPOLLIN | EPOLLET; ev.data.fd = client; /* 再發張VIP卡, 把大爺加入VIP客戶列表, 享受天上人間的服務 */ if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) { fprintf(stderr, "epoll set insertion error: fd=%d < 0", client); return -1; } } else /* 這位大爺肯定來過好幾次了, 否則怎麼連後門都知道. */ /* 後屋一排姑娘,大爺您慢慢挑 老鴇就不奉陪了,姑娘們伺候着! */ if(handler((void *)&events[n].data.fd) != 0) syslog(LOG_ERR,"handler ret != 0 - %m"); } } //打擊色..情產業, //被迫歇業了 close(server_socket); return 0; }