测试Lighttpd accept的惊群现象

本文探讨了lighttpd服务器中的惊群现象,并通过实验验证了其在非阻塞IO复用模型下如何处理连接请求。实验证明,虽然多个进程会被唤醒,但仅有一个能成功接受连接。
lighttpd里面采用的是prefork的模型,在fork进程之前就已经创建好了listen socket
那么fork了进程池之后,所有进程都有一份自己独立的listen socket fd,

但实际上这个独立的fd 对应的确是一个文件表项,即实际上任然是一个共享的文件描述符

在阻塞模型中,各进程分别通过accept阻塞,等待连接到达,当一个连接到达时,所有的进程都会被唤醒,但只有其中一个进程可以成功accept该连接,其余的则继续投入睡眠,这就是所谓的惊群现象

lighttpd使用的是非阻塞IO复用模型,测试一下是否会有惊群现象呢?

先把结论给出:

1.比如有20个进程注册了listen socket的请求连接事件,当一个连接到达确实会有多个进程 被通知有事件要处理(但不是全部,大约只有5,6个进程)
2.被唤醒的这几个进程会调用accept函数,其中只有一个成功返回连接fd,其余进程均返回EAGAIN或者 EWOULDBLOCK错误(因为是非阻塞的)

测试方法,自己写了一个prefork进程 + epoll的非阻塞server,启动20个进程,client telnet,打印服务器日志

try to accept new connection,pid=29879
try to accept new connection,pid=29876
try to accept new connection,pid=29880

[color=red][b]process 29879 accept connection[/b][/color]

accept EAGAIN error pid=29876
try to accept new connection,pid=29875
accept EAGAIN error pid=29880
accept EAGAIN error pid=29875

四个进程被通知有事件处理,1个成功accept,3个返回EAGAIN

[b]、
在lighttpd中,server当被通知有连接要处理时,server会通过循环执行
accept,直到返回错误,或者超过一个上限值

这样,当海量请求连接到达时,似乎惊群不会带来太多的性能损耗。
[/b]

部分测试代码
base.h


enum conn_states {
conn_listening, /** the socket which listens for connections */
conn_read, /** reading in a command line */
conn_write, /** writing out a simple response */
conn_nread, /** reading in a fixed number of bytes */
conn_swallow, /** swallowing unnecessary bytes w/o storing */
conn_closing, /** closing this connection */
conn_mwrite, /** writing out many items sequentially */
};
typedef struct{
int fd;
int state;
}conn;


server.c


#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<errno.h>
#include<fcntl.h>
#include<netinet/in.h>
#include<sys/resource.h>
#include "event.h"
#include "base.h"

//forward declaration

static fdevents *ev;
static conn **conns;
static int freetotal;
static int freecurr;

static int create_listen_fd(char *addr,int port);
static int conn_init();
static conn *get_conn_from_freelist();
static int add_conn_to_freelist(conn *c);
conn *conn_new(int fd,int state);

static int conn_init(){
freetotal=200;
freecurr=0;
conns=(conn **)malloc(freetotal * sizeof(*conns));
if(!conns){
return -1;
}
return 0;
}
static conn *get_conn_from_freelist(){
conn *con;
if(freecurr > 0){
con=conns[--freecurr];
conns[freecurr]=NULL;
return con;
}
return NULL;
}
static int add_conn_to_freelist(conn *c){
if(freecurr<freetotal){
conns[freecurr++]=c;
return 0;
}else{
conn **new_conns=(conn **)realloc(conns,sizeof(*new_conns)*2*freetotal);
if(new_conns){
freetotal*=2;
conns=new_conns;
conns[freecurr++]=c;
return 0;
}
}
return -1;
}
conn *conn_new(int fd,int state){
conn *c;
c=get_conn_from_freelist();
if(!c){
c=(conn *)malloc(sizeof(*c));
}

c->fd=fd;
c->state=state;

return c;

}
static int create_listen_fd(char *addr,int port){
int fd,val,flags;
struct sockaddr_in sockaddr;
fd=socket(AF_INET,SOCK_STREAM,0);
if(fd==-1){
fprintf(stderr,"socket()\n");
return -1;
}
val=1;
if(setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val))<0){
fprintf(stderr,"reuseaddr\n");
return -1;
}
if((flags=fcntl(fd,F_GETFL,0)<0) || fcntl(fd,F_SETFL,flags | O_NONBLOCK) < 0){
fprintf(stderr,"nonblocking\n");
return -1;
}

bzero(&sockaddr,sizeof(sockaddr));
sockaddr.sin_family=AF_INET;
sockaddr.sin_port=htons(port);
inet_pton(AF_INET,addr,&sockaddr.sin_addr);

if(bind(fd,(struct sockaddr *)&sockaddr,sizeof(sockaddr))<0){
fprintf(stderr,"bind error %s",strerror(errno));
return -1;
}
if(listen(fd,2048)<0){
fprintf(stderr,"listen %s",strerror(errno));
return -1;
}
return fd;
}
void event_handler(int fd,void *ctx, int revents){
struct sockaddr_in addr;
socklen_t sock_len;
int done=0,connfd;
conn *c;
c=(conn *)ctx;
while(!done){
switch(c->state){
case conn_listening:
printf("try to accept new connection,pid=%d\n",getpid());
sock_len=sizeof(addr);
connfd=accept(fd,(struct sockaddr *)&addr,&sock_len);
if(connfd>0){
printf("process %d accept connection\n",getpid());
c = conn_new(connfd,conn_read);
fdevent_register(ev,connfd,event_handler,c);
fdevent_event_add(ev,connfd,FDEVENT_IN);
}else{
if(errno== EAGAIN || errno == EWOULDBLOCK){
printf("accept EAGAIN error pid=%d\n",getpid());
}
if(errno==EINTR){
printf("accept EINTR error pid=%d\n",getpid());
}
if(errno==ECONNABORTED){ /* this is a FreeBSD thingy */
printf("accept EABORTED error pid=%d\n",getpid());
}
if(errno==EMFILE){
printf("accept EMFILE error pid=%d\n",getpid());
}
}
done=1;
break;
case conn_read:
printf("on read");
break;
}
}
}
int main(int argc,char **argv){
int fd,o;
char *listen_addr;
int port,num_childs,max_fds;
struct rlimit rlim;
conn *c;
port=0;
num_childs=5;
while(-1!=(o=getopt(argc,argv,"l:p:f:h"))){
switch(o){
case 'l':
listen_addr=strdup(optarg);
break;
case 'p':
port=atoi(optarg);
break;
case 'f':
num_childs=atoi(optarg);
break;
case 'h':
printf("Usage -l listen addr\n");
printf("Usage -p listen port \n");
printf("Usage -f fork num\n");
exit(1);
}
}
if(!listen_addr){
listen_addr=strdup("127.0.0.1");
}
if(!port){
printf("port is unknown\n");
exit(1);
}
if(0 != getrlimit(RLIMIT_NOFILE,&rlim)){
fprintf(stderr,"getrlimit failed.reason %s\n",strerror(errno));
exit(1);
}

max_fds=rlim.rlim_cur;

//create listen socket
if(-1==(fd=create_listen_fd(listen_addr,port))){
fprintf(stderr,"create listen fd failed\n");
exit(1);
}
//prefork child
if(num_childs > 0){
int child=0;
while(!child){
if(num_childs >0){
switch(fork()){
case -1:
return -1;
case 0:
child=1;
break;
default:
num_childs--;
break;
}
}else{
int status;
if(-1 !=wait(&status)){
num_childs++;
}else{
//ignore
}
}
}
}
//child process event
conn_init();
c=conn_new(fd,conn_listening);
ev=fdevent_init(max_fds);
if(!ev){
fprintf(stderr,"fdevent_init()\n");
exit(1);
}
fdevent_register(ev,fd,event_handler,c);
fdevent_event_add(ev,fd,FDEVENT_IN);
fdevent_poll(ev,1000);
}
我现在有一个问题就是 我使用curl -X PUT http://localhost:8085/test.txt -d "Hello, WebDAV!" 这个命令的时候 他不会进入mod_smbdav_physical_handler 但是我要是curl -v http://localhost:8085/smb/test.txt 这个的话他就会进入这个函数 我把我写的给你看#include "first.h" /* first */ #include <sys/types.h> #include "sys-dirent.h" #include "sys-mmap.h" #include <sys/types.h> #include "sys-stat.h" #include "sys-time.h" #include "sys-unistd.h" /* <unistd.h> getpid() linkat() rmdir() unlinkat() */ #include <errno.h> #include <fcntl.h> #include <stdio.h> /* rename() */ #include <stdlib.h> /* strtol() */ #include <string.h> #include <sqlite3.h> #include "base.h" #include "buffer.h" #include "chunk.h" #include "fdevent.h" #include "http_chunk.h" #include "http_date.h" #include "http_etag.h" #include "http_header.h" #include "log.h" #include "request.h" #include "response.h" /* http_response_redirect_to_directory() */ #include "stat_cache.h" /* stat_cache_mimetype_by_ext() */ #include "plugin.h" #include <samba-4.0/libsmbclient.h> #define DBE 1 #ifdef DEBUG #define Cdbg(level, format, ...) \ do { \ if (level) { \ log_error_write(srv, __FILE__, __LINE__, "[smbdav] " format, ##__VA_ARGS__); \ } \ } while (0) #else #define Cdbg(level, format, ...) ((void)0) #endif #define http_status_get(r) ((r)->http_status) #define http_status_set_fin(r, code) ((r)->resp_body_finished = 1,\ (r)->handler_module = NULL, \ (r)->http_status = (code)) #define http_status_set(r, code) ((r)->http_status = (code)) #define http_status_unset(r) ((r)->http_status = 0) #define http_status_is_set(r) (0 != (r)->http_status) #define HTTP_STATUS_OK 200 #define HTTP_STATUS_CREATED 201 #define HTTP_STATUS_NO_CONTENT 204 #define HTTP_STATUS_NOT_MODIFIED 304 #define HTTP_STATUS_BAD_REQUEST 400 #define HTTP_STATUS_FORBIDDEN 403 #define HTTP_STATUS_NOT_FOUND 404 #define HTTP_STATUS_CONFLICT 409 #define HTTP_STATUS_INTERNAL_SERVER_ERROR 500 #define HTTP_STATUS_NOT_IMPLEMENTED 501 #define HTTP_STATUS_SERVICE_UNAVAILABLE 503 #define HTTP_SERVER_ERROR 500 #define HTTP_SERVER_NOT_IMPLEMENTED 501 #define HTTP_SERVER_BAD_GATEWAY 502 #define HTTP_SERVER_SERVICE_UNAVAILABLE 503 #define HTTP_SERVER_GATEWAY_TIMEOUT 504 #define HTTP_SERVER_VERSION_NOT_SUPPORTED 505 #define HTTP_SERVER_INSUFFICIENT_STORAGE 507 #define HTTP_STATUS_CONTINUE 100 #define HTTP_STATUS_SWITCHING_PROTOCOLS 101 #define HTTP_STATUS_PROCESSING 102 #define HTTP_STATUS_MULTI_STATUS 207 #define HTTP_STATUS_ALREADY_REPORTED 208 #define HTTP_STATUS_IM_USED 226 #define HTTP_METHOD_NOT_ALLOWED 405 #define HTTP_SERVER_INTERNAL_SERVER_ERROR 500 typedef struct { unsigned short enabled; unsigned short is_readonly; unsigned short log_xml; buffer *smb_share_root; buffer *sqlite_db_name; } plugin_config; typedef struct { PLUGIN_DATA; buffer *tmp_buf; request_uri uri; physical physical; plugin_config **config_storage; plugin_config conf; } plugin_data; static int handle_propfind(request_st *r, plugin_data *p, buffer *smb_path) { /* 1. 解析 PROPFIND 请求的 XML 内容 */ /* 2. 查询 SMB 资源的属性 */ /* 3. 生成 WebDAV 标准的 XML 响应 */ fprintf(stderr, "[TEST] handle_propfind called with path: %s\n", smb_path->ptr); return HANDLER_GO_ON; } static int handle_proppatch(request_st *r, plugin_data *p, buffer *smb_path) { /* 1. 解析 PROPPATCH 请求的 XML 内容 */ /* 2. 更新 SMB 资源的属性 */ /* 3. 返回操作结果 */ fprintf(stderr, "[TEST] handle_proppatch called with path: %s\n", smb_path->ptr); return HANDLER_GO_ON; } static int handle_lock(request_st *r, plugin_data *p, buffer *smb_path) { /* 1. 解析 LOCK 请求的 XML 内容 */ /* 2. 创建或更新资源的锁定状态 */ /* 3. 返回锁定令牌 */ fprintf(stderr, "[TEST] handle_lock called with path: %s\n", smb_path->ptr); return HANDLER_GO_ON; } static buffer *build_smb_path(plugin_data *p, const char *path) { buffer *smb_path = buffer_init(); if (!smb_path) return NULL; fprintf(stderr, "[TEST] smb_path2222222222: %s\n", p->conf.smb_share_root->ptr); /* 拼接 SMB 共享根目录和请求路径 */ /*自己写这个源码*/ buffer_copy_buffer(smb_path, p->conf.smb_share_root); fprintf(stderr, "[TEST] smb_path: %s\n", smb_path->ptr); buffer_append_string(smb_path, path); fprintf(stderr, "[TEST] smb_path1111111111: %s\n", smb_path->ptr); return smb_path; } INIT_FUNC(mod_smbdav_init) { plugin_data *p; p = calloc(1, sizeof(*p)); p->tmp_buf = buffer_init(); Cdbg(DBE,"complete smbc_init..."); return p; } int mod_smbdav_subrequest_handler(request_st *r, void *p_d) { fprintf(stderr, "[TEST] mod_smbdav_subrequest_handler called with path:"); plugin_data *p = p_d; connection *con = r->con; int ret = HANDLER_GO_ON; fprintf(stderr, "[TEST] mod_smbdav_physical_handler called with path: %s\n", r->physical.path.ptr); /* 1. 检查插件是否启用 */ if (!p->conf.enabled) { r->http_status = HTTP_SERVER_SERVICE_UNAVAILABLE; fprintf(stderr, "[TEST] mod_smbdav_conf.enabled is false"); return HANDLER_FINISHED; } else { fprintf(stderr, "[TEST] mod_smbdav_conf.enabled is true"); } /* 2. 解析 SMB 路径 */ buffer *smb_path = build_smb_path(p, r->physical.path.ptr); fprintf(stderr, "[TEST] smb_path: %s\n", smb_path->ptr); if (!smb_path) { r->http_status = HTTP_SERVER_INTERNAL_SERVER_ERROR; return HANDLER_FINISHED; } /* 3. 根据 HTTP 方法分发处理 */ fprintf(stderr, "[TEST] con->request.http_method: %d\n", con->request.http_method); switch (con->request.http_method) { case HTTP_METHOD_PROPFIND: ret = handle_propfind(r, p, smb_path); break; case HTTP_METHOD_PROPPATCH: ret = handle_proppatch(r, p, smb_path); break; case HTTP_METHOD_LOCK: ret = handle_lock(r, p, smb_path); break; default: r->http_status = HTTP_METHOD_NOT_ALLOWED; ret = HANDLER_FINISHED; } buffer_free(smb_path); return ret; } int mod_smbdav_physical_handler(request_st *r, void *p_d) { fprintf(stderr, "[TEST] mod_smbdav_physical_handler called with path:"); plugin_data *p = p_d; connection *con = r->con; int ret = HANDLER_GO_ON; fprintf(stderr, "[TEST] mod_smbdav_physical_handler called with path: %s\n", r->physical.path.ptr); /* 1. 检查插件是否启用 */ if (!p->conf.enabled) { r->http_status = HTTP_SERVER_SERVICE_UNAVAILABLE; fprintf(stderr, "[TEST] mod_smbdav_conf.enabled is false"); return HANDLER_FINISHED; } else { fprintf(stderr, "[TEST] mod_smbdav_conf.enabled is true"); } /* 2. 解析 SMB 路径 */ buffer *smb_path = build_smb_path(p, r->physical.path.ptr); fprintf(stderr, "[TEST] smb_path: %s\n", smb_path->ptr); if (!smb_path) { r->http_status = HTTP_SERVER_INTERNAL_SERVER_ERROR; return HANDLER_FINISHED; } /* 3. 根据 HTTP 方法分发处理 */ fprintf(stderr, "[TEST] con->request.http_method: %d\n", con->request.http_method); switch (con->request.http_method) { case HTTP_METHOD_PROPFIND: ret = handle_propfind(r, p, smb_path); break; case HTTP_METHOD_PROPPATCH: ret = handle_proppatch(r, p, smb_path); break; case HTTP_METHOD_LOCK: ret = handle_lock(r, p, smb_path); break; default: r->http_status = HTTP_METHOD_NOT_ALLOWED; ret = HANDLER_FINISHED; } buffer_free(smb_path); return ret; } int smbdav_set_defaults(server *srv, plugin_data *p) { static const config_plugin_keys_t config_keys[] = { /* SMB 共享根目录 */ { CONST_STR_LEN("smbdav.smb_share_root"), T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 是否启用模块 */ { CONST_STR_LEN("smbdav.enabled"), T_CONFIG_BOOL, T_CONFIG_SCOPE_CONNECTION }, /* 是否只读模式 */ { CONST_STR_LEN("smbdav.is_readonly"), T_CONFIG_BOOL, T_CONFIG_SCOPE_CONNECTION }, /* 是否记录 XML 请求 */ { CONST_STR_LEN("smbdav.log_xml"), T_CONFIG_BOOL, T_CONFIG_SCOPE_CONNECTION }, /* 终止标记 */ { NULL, 0, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } }; /* 初始化默认配置 */ p->conf.smb_share_root = buffer_init(); p->conf.smb_share_root->ptr = "/home/zhihonghe/smb_share"; p->conf.smb_share_root->used = 1000; p->conf.smb_share_root->size = 1000; p->conf.enabled = 1; p->conf.is_readonly = 0; p->conf.log_xml = 0; /* 解析配置文件 */ if (!config_plugin_values_init(srv, p, config_keys, "mod_smbdav")) { fprintf(stderr, "[TEST] config_plugin_values_init failed"); return HANDLER_ERROR; } /*打印配置信息*/ fprintf(stderr, "[TEST] smb_share_root: %s\n", p->conf.smb_share_root->ptr); fprintf(stderr, "[TEST] enabled: %d\n", p->conf.enabled); fprintf(stderr, "[TEST] is_readonly: %d\n", p->conf.is_readonly); fprintf(stderr, "[TEST] log_xml: %d\n", p->conf.log_xml); /* 检查 SMB 共享路径是否配置 */ if (buffer_string_is_empty(p->conf.smb_share_root)) { fprintf(stderr, "[TEST] smb_share_root is not configured"); return HANDLER_ERROR; } /* 绑定临时缓冲区 */ p->tmp_buf = srv->tmp_buf; fprintf(stderr, "[TEST] finished smbdav_set_defaults"); return HANDLER_GO_ON; } int mod_smbdav_plugin_init(plugin *p) { p->version = LIGHTTPD_VERSION_ID; p->name = "smbdav"; /* Core callbacks */ p->init = mod_smbdav_init; p->cleanup = NULL; p->set_defaults = smbdav_set_defaults; p->priv_defaults = NULL; /* Optional */ p->worker_init = NULL; /* Optional */ /* Request handlers */ p->handle_uri_raw = NULL; /* Not used */ p->handle_uri_clean = NULL; p->handle_docroot = NULL; /* Not used */ p->handle_physical = mod_smbdav_physical_handler; p->handle_request_env = NULL; /* Not used */ p->handle_request_done = NULL; /* Not used */ p->handle_subrequest = mod_smbdav_subrequest_handler; /* Not used */ p->handle_response_start = NULL; /* Not used */ p->handle_request_reset = NULL; /* Not used */ /* Connection handlers */ p->handle_connection_accept = NULL; /* Not used */ p->handle_connection_shut_wr = NULL; /* Not used */ p->handle_connection_close = NULL; /* Server handlers */ p->handle_trigger = NULL; /* Not used */ p->handle_sighup = NULL; /* Not used */ p->handle_waitpid = NULL; /* Not used */ p->data = NULL; p->lib = NULL; return 0; }
09-19
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值