博客概述
先前关于linux文件系统驱动的博客已经删除,文件系统可能是一个比较复杂的话题,涉及的东西也比较多。我虽然想写,不过发现可能要叙述的东西太多,我没办法掌握重点,所以暂时不写,等以后有好的思路在写。根据朋友的建议,先写服务器开发的博客。首先从简单的回文服务器开始到http服务器。
博客面向的读者是已经熟悉c,unix编程和unix网络编程,然后想编写服务器程序的人。
服务器概述
大部分的服务器都是接受客户端的请求之后做一些处理,然后发回一些内容给客户端。比如回文服务器是接受一段文本,然后回射给客户端。如下给出服务器的一个图例:
一个服务器可能会和很多客户端同时交互,所以保证每个客户端公平的快速响应是很重要的。
mc_echo概述
mc_echo是一个回文服务器,也是本博客讲解的服务器开发的第一个服务器,项目地址为https://github.com/mc-robin/mc_server/tree/master/mc_echo。以下给出mc_echo的大致图例:
主进程用于接受新的客户端连接,然后分发连接请求。处理进程用于处理客户端的请求,然后回应。调度进程用于帮助主进程更加合理的分发服务。mc_echo并不涉及模块机制,所以是比较简单的服务器示例。
mc_echo配置文件解析
mc_echo的配置文件大致如下:
port { "8888" }
user { "nobody" }
group { "nogroup" }
IPversion { "ipv4" }
LOGFile { "/var/log/echo.log" }
maxrequests { "1024" }
maxprocesses { "4096" }
upperthreshold { "512" }
lowerthreshold { "64" }
processcreat { "4" }
cycletime { "60" }
backlog { "1024" }
#keyfile { "/home/mc_server/mc_echo/key.pem" }
#certfile { "/home/mc_server/mc_echo/cert.pem" }
port是服务器的监听端口,user和group是服务器运行后的uid和gid(同时接受id和名字格式),ipversion是ip版本,logfile是日志文件路径,maxrequests是处理进程最多能处理的客户端数目,maxprocesses是处理进程的最大数目,upperthreshold和lowerthreshold是调度参数(细节在讲诉调度进程时解析),processcreat是预创建的处理进程数目,cycletime是调度进程的周期,backlog是listen的参数。keyfile和certfile是ssl的加密文件路径。
注: mc_echo只能监听一个端口,mc_httpd能同时监听多个端口(要实现监听多个端口本质不难,细节在讲解mc_httpd时讨论)
mc_echo采用lex和yacc读取配置文件,lex的代码如下:
%{
#include "configure.h"
#include "conf_yacc.h"
%}
or [\|,]
ws [ \t\n]+
comment #.*
string \"[^\"\n]*[\"\n]
%%
{ws} ;
{comment} ;
{or} { return OR; }
\{ { return OBRACE; }
\} { return EBRACE; }
{string} { yylval.dstr = strdup(yytext + 1);
if(yylval.dstr[yyleng - 2] != '"'){
mc_err("Error: '%s' is Illegal string\n", yytext);
}else
yylval.dstr[yyleng - 2] = '\0';
return STRING;
}
[Uu][Ss][Ee][Rr] { return USER; }
[Pp][Oo][Rr][Tt] { return PORT; }
[Gg][Rr][Oo][Uu][Pp] { return GROUP; }
[Cc][Ii][Pp][Hh][Ee][Rr] { return CIPHER; }
[Bb][Aa][Cc][Kk][Ll][Oo][Gg] { return BACKLOG; }
[Ll][Oo][Gg][Ff][Ii][Ll][Ee] { return LOGFILE; }
[Pp][Ii][Dd][Ff][Ii][Ll][Ee] { return PIDFILE; }
[Kk][Ee][Yy][Ff][Ii][Ll][Ee] { return KEYFILE; }
[Cc][Ee][Rr][Tt][Ff][Ii][Ll][Ee] { return CERTFILE; }
[Ii][Pp][Vv][Ee][Rr][Ss][Ii][Oo][Nn] { return IPVERSION; }
[Cc][Yy][Cc][Ll][Ee][Tt][Ii][Mm][Ee] { return CYCLETIME; }
[Mm][Aa][Xx][Rr][Ee][Qq][Uu][Ee][Ss][Tt][Ss] { return MAXREQUESTS; }
[Mm][Aa][Xx][Pp][Rr][Oo][Cc][Ee][Ss][Ss][Ee][Ss] { return MAXPROCESSES; }
[Pp][Rr][Oo][Cc][Ee][Ss][Ss][Cc][Rr][Ee][Aa][Tt] { return PROCESSCREAT; }
[Uu][Pp][Pp][Ee][Rr][Tt][Hh][Rr][Ee][Ss][Hh][Oo][Ll][Dd] { return UPPERTHRESHOLD; }
[Ll][Oo][Ww][Ee][Rr][Tt][Hh][Rr][Ee][Ss][Hh][Oo][Ll][Dd] { return LOWERTHRESHOLD; }
. ;
%%
从代码可以看出,mc_echo的配置文件是不区分大小写的。另外,此处的代码与git上的略有不同,此处的代码更加好。
yacc的代码如下:
%{
#undef YYSTACKSIZE
#define YYSTACKSIZE 5000
#include "configure.h"
%}
%union{
char *dstr;
}
%token PORT
%token USER
%token GROUP
%token CIPHER
%token BACKLOG
%token LOGFILE
%token PIDFILE
%token KEYFILE
%token CERTFILE
%token IPVERSION
%token CYCLETIME
%token MAXREQUESTS
%token MAXPROCESSES
%token PROCESSCREAT
%token UPPERTHRESHOLD
%token LOWERTHRESHOLD
%token OR
%token OBRACE
%token EBRACE
%token <dstr> STRING
%%
configs:
| config configs
;
config: OBRACE STRINGS EBRACE
| USER OBRACE STRING EBRACE { MC_USER_SET(mc_startup->s_configinfo.c_uid, $3) }
| GROUP OBRACE STRING EBRACE { MC_GROUP_SET(mc_startup->s_configinfo.c_gid, $3) }
| PORT OBRACE STRING EBRACE { MC_DIGIT_SET(mc_startup->s_configinfo.c_port, $3) }
| CIPHER OBRACE STRING EBRACE { MC_STRING_SET(mc_startup->s_configinfo.c_cipher, $3) }
| BACKLOG OBRACE STRING EBRACE { MC_DIGIT_SET(mc_startup->s_configinfo.c_backlog, $3) }
| LOGFILE OBRACE STRING EBRACE { MC_STRING_SET(mc_startup->s_configinfo.c_logfile, $3) }
| PIDFILE OBRACE STRING EBRACE { MC_STRING_SET(mc_startup->s_configinfo.c_pidfile, $3) }
| KEYFILE OBRACE STRING EBRACE { MC_STRING_SET(mc_startup->s_configinfo.c_keyfile, $3) }
| CERTFILE OBRACE STRING EBRACE { MC_STRING_SET(mc_startup->s_configinfo.c_certfile, $3) }
| CYCLETIME OBRACE STRING EBRACE { MC_DIGIT_SET(mc_startup->s_configinfo.c_cycle, $3) }
| IPVERSION OBRACE STRING EBRACE { MC_DOMAIN_SET(mc_startup->s_configinfo.c_domain, $3) }
| MAXREQUESTS OBRACE STRING EBRACE { MC_DIGIT_SET(mc_startup->s_configinfo.c_schd.s_maxfds, $3) }
| MAXPROCESSES OBRACE STRING EBRACE { MC_DIGIT_SET(mc_startup->s_configinfo.c_schd.s_maxprocs, $3) }
| PROCESSCREAT OBRACE STRING EBRACE { MC_DIGIT_SET(mc_startup->s_configinfo.c_schd.s_medianprocs, $3) }
| UPPERTHRESHOLD OBRACE STRING EBRACE { MC_DIGIT_SET(mc_startup->s_configinfo.c_schd.s_thresholdfds_u, $3) }
| LOWERTHRESHOLD OBRACE STRING EBRACE { MC_DIGIT_SET(mc_startup->s_configinfo.c_schd.s_thresholdfds_l, $3) }
;
STRINGS:
| STRING STRINGS
| STRINGS OR STRINGS
;
%%
int
yyerror(char *s)
{
fprintf(stderr, "%s\n", s);
}
观察yacc的代码,首先第一步是重新定义默认的栈大小,因为默认的栈大小可能比较小会导致溢出,所以此处重新定义一个比较大的值。接下来是一些符号的声明。configs是一个递归的语法,config依次处理各个配置文件的值;OBRACE STRINGS EBRACE用于过滤不认识的配置,git上的代码并没有这么做,这种做法是mc_httpd上为了过滤其他模块所用的配置参数所用的办法,此处直接给出的yacc和lex代码都是根据mc_httpd的代码改写的。
下一篇博客讨论mc_echo的事件机制的代码以及多进程的模型