网络开发中,经常会碰到一些内存转换,如下面的场景:
#define PACKAGE_PARSE_ERROR -1
#define PACKAGE_PARSE_OK 0
int parse_package( int* a, int* b, int* c, int* d, char* buf, int buf_len )
{
if( !buf || buf_len < 16 ){
return PACKAGE_PARSE_ERROR;
}
memcpy( a, buf, 4 );
memcpy( b, buf + 4, 4 );
memcpy( c, buf + 8, 4 );
memcpy( d, buf + 12, 4 );
return PACKAGE_PARSE_OK;
}
这是网络解包的过程,封包的过程则是逆过程。
像这样的应用其实完全可以用整型强制转换来代替,而且效率会至少提高一倍。
为了说明问题,我们举个简单的例子:
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
int main()
{
int s;
char buffer[4];
memcpy(&s, buffer, 4 );
s = *(int*)(buffer);
return 0;
}第10行和第11行的效果是一样的,10行采用的是内存复制,11行采用的是强制转换,为了方便比较,我们看一下汇编代码:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
leaq -16(%rbp), %rcx
leaq -4(%rbp), %rax
movl $4, %edx
movq %rcx, %rsi
movq %rax, %rdi
call memcpy
leaq -16(%rbp), %rax
movl (%rax), %eax
movl %eax, -4(%rbp)
movl $0, %eax
leave代码中可以看出,内存复制方法占用了7-12行,共6行,强制转换占用了13-15行,共3行,指令上少了一半。
深究一下其实还不止,因为第12行其实是一个函数调用,必然会有栈的迁移,所以强制转换的效率比内存复制起码高一倍。
再看看glibc 的memcpy函数实现:
void *memcpy (void *dstpp, const void *srcpp, size_t len )
{
unsigned long int dstp = (long int) dstpp;
unsigned long int srcp = (long int) srcpp;
if (len >= OP_T_THRES)
{
len -= (-dstp) % OPSIZ;
BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ);
PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len);
WORD_COPY_FWD (dstp, srcp, len, len);
}
BYTE_COPY_FWD (dstp, srcp, len);
return dstpp;
}9-11行分别是三种处理方法,取决于 len 与 OP_T_THRES的比较,一般 OP_T_THRES 是8或16,对于len 小于OP_T_THRES的内存复制,glibc采用的是字节方式转换,即遍历每个字节,第个字节都要经过 “内存--寄存器--内存” 这个过程,CPU指令上可以说多了平空多了一倍。
从上面的分析可以看出,强制转换是节省了很大的运算时间,效率上至少提高一倍。不要小看这样的提升,在每秒几万并发的情况下,尤其每个并发都存在解包和封包的过程,这样的处理可以给我们带来相当大的性能提升。
开头中提到的解包过程,我们可以巧秒地运用强制转换,下面列出两种方法:
int parse_package( int* a, int* b, int* c, int* d, char* buf, int buf_len )
{
if( !buf || buf_len < 16 ){
return PACKAGE_PARSE_ERROR;
}
memcpy( a, buf, 4 );
memcpy( b, buf + 4, 4 );
memcpy( c, buf + 8, 4 );
memcpy( d, buf + 12, 4 );
return PACKAGE_PARSE_OK;
}
int parse_package2( int* a, int* b, int* c, int* d, char* buf, int buf_len )
{
int* ibuf;
if( !buf || buf_len < 16 ){
return PACKAGE_PARSE_ERROR;
}
ibuf = buf;
*a = ibuf[0];
*b = ibuf[1];
*c = ibuf[2];
*d = ibuf[3];
return PACKAGE_PARSE_OK;
}
parse_package汇编代码:
parse_package:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $48, %rsp
movq %rdi, -8(%rbp)
movq %rsi, -16(%rbp)
movq %rdx, -24(%rbp)
movq %rcx, -32(%rbp)
movq %r8, -40(%rbp)
movl %r9d, -44(%rbp)
cmpq $0, -40(%rbp)
je .L2
cmpl $15, -44(%rbp)
jg .L3
.L2:
movl $-1, %eax
jmp .L4.
L3:
movq -40(%rbp), %rcx
movq -8(%rbp), %rax
movl $4, %edx
movq %rcx, %rsi
movq %rax, %rdi
call memcpy
movq -40(%rbp), %rax
leaq 4(%rax), %rcx
movq -16(%rbp), %rax
movl $4, %edx
movq %rcx, %rsi
movq %rax, %rdi
call memcpy
movq -40(%rbp), %rax
leaq 8(%rax), %rcx
movq -24(%rbp), %rax
movl $4, %edx
movq %rcx, %rsi
movq %rax, %rdi
call memcpy
movq -40(%rbp), %rax
leaq 12(%rax), %rcx
movq -32(%rbp), %rax
movl $4, %edx
movq %rcx, %rsi
movq %rax, %rdi
call memcpy
movl $0, %eax
L3段是我们的主段落,对a的赋值:
24-28行都是在“压栈”,为了memcpy函数内取出来,加上29行一共是6条,memcpy 解栈指令数>=3, 去处指令数>=4,不加算返回指令,一共指令数>6+3+4=13。
parse_package2汇编代码:
parse_package2:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movq %rdi, -24(%rbp)
movq %rsi, -32(%rbp)
movq %rdx, -40(%rbp)
movq %rcx, -48(%rbp)
movq %r8, -56(%rbp)
movl %r9d, -60(%rbp)
cmpq $0, -56(%rbp)
je .L7
cmpl $15, -60(%rbp)
jg .L8
.L7:
movl $-1, %eax
jmp .L9
.L8:
movq -56(%rbp), %rax
movq %rax, -8(%rbp)
movq -8(%rbp), %rax
movl (%rax), %edx
movq -24(%rbp), %rax
movl %edx, (%rax)
movq -8(%rbp), %rax
addq $4, %rax
movl (%rax), %edx
movq -32(%rbp), %rax
movl %edx, (%rax)
movq -8(%rbp), %rax
addq $8, %rax
movl (%rax), %edx
movq -40(%rbp), %rax
movl %edx, (%rax)
movq -8(%rbp), %rax
addq $12, %rax
movl (%rax), %edx
movq -48(%rbp), %rax
movl %edx, (%rax)
movl $0, %eax
L8是主段落,对a的赋值:
26-29行,一共4行解决。
这个例子中强制转换(parse_package2) 比内存复制(parse_package)要少2倍的CPU指令,性能至少可以提高2倍。
因此,我们的开发中应该尽量减少对内存复制的使用,而应该采用强制转换,现在64位服务器上,我们甚至可以用8个字节的long,下面的例子:
long lv; char buffer[ 8 ]; memcpy( &lv, buffer, 8 ); lv = *(int*)(buffer);
这样就能更好的利用CPU的多字节指令提高性能。
服务器开发中不何或缺的要使用fastcgi,其使用方法如下:
while( FCGI_Accept() >= 0 ){
printf( "Content-type: text/plain \r\n"
"Content-Length: %d\r\n"
"Connection: close\r\n\r\n%s\r\n", strlen( buffer ), buffer );
}facgcgi 头文件有如下宏:
#undef fprintf #define fprintf FCGI_fprintf #undef printf #define printf FCGI_printf
可以看出,已经对printf函数进行了宏转向,在程序里的printf 不再是标准输出了。这样就有一个问题,如果想调试打印信息到stdout中,就不行了。
现实开发中,可能会出现各种问题,有时候的确需要打印出一些信息,这时候可以修改fcgi_stdio.h头文件,改成如下:
#undef _fprintf #define _fprintf FCGI_fprintf #undef _printf #define _printf FCGI_printf
在程序中的相应地方也要换成相应的宏。
fastcgi 的解析可以通过这个函数:
char *getenv(const char *name)
有如下几类参数:
CONTENT_TYPE
CONTENT_TYPE
得到请求类型
CONTENT_LENGTH
正文段长度
QUERY_STRING
请求字串
以如下一段HTTP请求报文为例:
GET /s?ie=utf-8&bs=%E8%BF%99%E6%98%AF&f=8&rsv_bp=1&rsv_spt=3&wd=%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%BC%80%E5%8F%91&rsv_sug3=11&rsv_sug=0&rsv_sug4=609&rsv_sug1=2&inputT=32681 HTTP/1.1 Host: www.baidu.com Connection: keep-alive Content-Type: text/html;charset=utf-8 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31 Referer: http://www.baidu.com/s?wd=%E8%BF%99%E6%98%AF&rsv_bp=0&ch=&tn=baidu&bar=&rsv_spt=3&ie=utf-8&rsv_sug3=4&rsv_sug=0&rsv_sug4=240&rsv_sug1=3&inputT=2835 Accept-Encoding: gzip,deflate,sdch Accept-Language: zh-CN,zh;q=0.8 Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3 Cookie: BAIDUID=A83324E58AE26486E46FC49ED127891B:FG=1; BDSVRTM=163; H_PS_PSSID=1439_2448_2454_2256_1788_2249; WWW_ST=1369271667063
CONTENT_TYPE 对应第5行,QUERY_STRING 对应第一行。
fastcgi 不是面向对象的,所有的任务都是在主循环内完成,包括解析,处理,响应。
在实际的开发中,为了提高效率,开发成员不用关心底层,我将其封装成一个类:fcgi_net_duty
#ifndef __FCGI_NET_H__
#define __FCGI_NET_H__
#define FCGI_NET_OK 0
#define FCGI_NET_ERROR 1
#define FCGI_NET_PARAM_ERROR 2
enum fcgi_net_type
{
FCGI_NET_TEXT = 0,
FCGI_NET_APP_STREAM,
FCGI_NET_IMAGE,
};
class fcgi_net_duty
{
public:
fcgi_net_duty();
~fcgi_net_duty();
void do_request();
protected:
void do_prev();
void do_cast();
virtual void do_handle();
void do_write();
void do_finish();
int get_req_int( const char* param, int *value );
int get_req_ll( const char* param, long long *value );
int get_req_str( const char* param, const char **value );
int get_cookie_int( const char* param, int *value );
int get_cookie_ll( const char* param, long long *value );
int get_cookie_str( const char* param, const char **value );
private:
std::map< const char*, const char* > m_req;
std::map< const char*, const char* > m_cookie;
int rtn;
int content_type;
int content_length;
char* content_buf;
};
主循环放在do_request 函数里
fcgi_net_duty.cpp
void fcgi_net_duty::do_request()
{
while( FCGI_Accept() >= 0 ){
do_prev();
do_cast();
do_handle();
do_write();
do_finish();
}
}
子类只需要关心相应的业务,每个业务可以用一个子类完成,如现有三个业务:busi1, busi2, busi3,我们以其中一个为例:
fcig_busi1.h
#ifndef __FCGI_BUSI1_H__
#define __FCGI_BUSI1_H__
class fcgi_busi1 : public fcgi_net_duty
{
public:
fcgi_busi1();
~fcgi_busi1();
void do_handle();
};
#endif //__FCGI_BUSI1_H__
void fcgi_busi1::do_handle()
{
const char* busi1;
int flag;
rtn = FCGI_NET_ERROR;
flag = get_req_str( "busi1", &busi1 );
if( flag ){
rtn = FCGI_NET_PARAM_ERROR;
return;
}
sprintf( content_buf, "%s","your busi is ok" );
rtn = FCGI_NET_OK;
}
int main()
{
fcgi_busi1 busi1;
busi1.do_request();
}
相应的makefile:
INC=-I./
LIB=-L/usr/lib64 -lfcgi
CPPFLAGS=-g -w $(INC)
CC=g++
BUSI1=./build/busi1
BUSI2=./build/busi2
BUSI3=./build/busi3
all: $(BUSI1) $(BUSI2) $(BUSI3)
$(BUSI1): ./fcgi_net_duty.o ./fcgi_busi1.o
$(CC) -O $@ $^ $(LIB)
$(BUSI2): ./fcgi_net_duty.o ./fcgi_busi2.o
$(CC) -O $@ $^ $(LIB)
$(BUSI3): ./fcgi_net_duty.o ./fcgi_busi3.o
$(CC) -O $@ $^ $(LIB)OK,现在一个轻型的fastcgi开发框架就搭建起来了,现实开发中,可以让一个成员开发网络处理,其他人专门做相应的业务处理,可以达到事半功倍的处理,我的理念就是让专业的人做专业的事,这样大家在专业技能上有更好的提升。
我写了一个nginx.lsp文件,可以通过传递参数来进行Nginx进程管理。
使用方法
1. 从nginx站点下载windows版本的程序,加压后,将newlisp.lsp文件复制到该目录。
2. 确保windows的system32目录下有newlisp.exe程序。
3. 运行程序, 目前支持5个参数
newlisp nginx.lsp start|stop|monitor|reload|view
比如下面用view来观察进程:
c:\newlisp nginx.lsp view Image Name PID Session Name Session# Mem Usage ========================= ======== ================ =========== ============ nginx.exe 8048 Console 1 6,716 K nginx.exe 4512 Console 1 6,988 K nginx.exe 1684 Console 1 6,836 K nginx.exe 4652 Console 1 7,132 K nginx.exe 6512 Console 1 6,816 K nginx.exe 6868 Console 1 7,152 K
可以用stop参数关闭所有nginx进程。
具体实现代码如下:
;; parse argument as one of the following
;; start|stop|reload|monitor|view
(define (start)
(process "start.bat")
)
(define (kill-process e)
(if (regex "^nginx.exe" e)
(begin
(set 'pid ((regex "^nginx.exe[ ]+([0123456789]+)" e) 3))
(exec (append "Taskkill /PID " pid " /F")))))
(define (handle-element e)
(if (regex "^nginx.exe" e)
(set 'h true)))
(define (check-nginx-process)
(set 'cmd "tasklist /fi \"imagename eq nginx.exe\"")
(set 'result (exec cmd))
(set 'h nil)
(dolist (str result)
(handle-element str))
)
(define (kill-nginx-processes)
(set 'cmd "tasklist /fi \"imagename eq nginx.exe\"")
(set 'result (exec cmd))
(set 'h nil)
(dolist (str result)
(kill-process str))
)
(define (view)
(set 'cmd "tasklist /fi \"imagename eq nginx.exe\"")
(set 'result (exec cmd))
(dolist (str result)
(println str))
)
(define (stop)
(set 'cmd "tasklist /fi \"imagename eq nginx.exe\"")
(set 'result (exec cmd))
(check-nginx-process)
(if h
(begin
(println "stoping nginx now ...")
(exec "nginx -s stop")
(check-nginx-process)
(if h
(begin
(println "kill nginx processes ...")
(kill-nginx-processes)
)))
(println "do nothing because nginx is stopped"))
)
(define (reload)
(exec "nginx -s reload")
)
(define (monitor)
(while true
(begin
(sleep 10000)
(println "check nginx process at 10 seconds")
(check-nginx-process)
(unless h
(begin
(println "nginx is stopped, start it now ...")
(start))
(println "nginx process is alive")
)
)
)
)
(define (main-fun)
(set 'action ((main-args) 2))
(unless action
(begin
(println "please pass one of the arguments: start|stop|reload|monitor|view")
(exit)))
(if (= "start" action)
(start))
(if (= "stop" action)
(stop))
(if (= "reload" action)
(reload))
(if (= "monitor" action)
(monitor))
(if (= "view" action)
(view))
)
(main-fun)
(exit)