JohnTech-周洋的博客
Nothing left but blog
Nothing left but blog
Nov 9th
这个模块其目前主要用于xoyo江湖的sns与kingsoft_xoyo自主研发的TCSQL数据库做数据同步,当有feed插入sns数据库,使用触 发器调用该模块,向tcsql数据库发送同步数据。也可以使用该模块与其它使用socket接口的数据库或程序做转发与同步。
http_post模块主要使用mysql_udf接口,与curl库两部分技术。
mysql_udf是mysql为c语言提供的一个接口,通过这个接口,用户可以自定义mysql的函数,通过调用这些mysql函数,调用相应的c语言 模块来执行特定功能,实现mysql数据与外部应用的交互。curl库是一个比较常用的应用层网络协议库,主要用到的是其中的curl_multi异步通 信api,用来进行网络传输。
首先参考mysql官方提供的udf_example.c文件,建立3个主要的接口函数,分别是初始化函数,执行函数与析构函数。
//args是sql语句传回的参数,message是返回出错信息使用这些都是规定好的。
my_bool http_post_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
//主函数体
longlong http_post(UDF_INIT *initid, UDF_ARGS *args, char *is_null,char *error);
//析构函数体
void http_post_deinit(UDF_INIT *initid);
//args是sql语句传回的参数,message是返回出错信息,使用这些都是规定好的。
//初始化函数体 my_bool http_post_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
//主函数体 longlong http_post(UDF_INIT *initid, UDF_ARGS *args, char *is_null,char *error);
//析构函数体 void http_post_deinit(UDF_INIT *initid);
在mysql_udf接口中,主函数体中是不允许使用new或malloc动态分配内存,所以如果需要申请内存空间,必须用xxxx_init()函数申 请并将申请的地址赋给initid->ptr指针,然后在主函数体中使用,并在xxxx_deinit析构函数体中释放。另外对于 mysql_udf接口的调用好像当并发量超过一定程度,如果是使用动态分配内存,会出现double free的错误,为了避免这个错误,所以在我的程序里使用静态空间与动态申请空间相结合的方式,这样如果数据较小,并发量较大,不会出现double free错误。对于静态申请空间,最大约在160000~170000byte左右,我这里使用的160000,当mysql传送的数据大于这个数的时 候,才动态申请内存。初始化函数体如下:
my_bool http_post_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
{
if (args->arg_count != 2)
{
strcpy(message,"Wrong arguments to http_post; ");
return 1;
}
if(args->arg_count == 2 && args->args[1]!=NULL)
{
int flexibleLength = strlen(args->args[1]);
if(flexibleLength > 160000)
{
int allocLength = 200 + flexibleLength;
if (!(initid->ptr=(char*) malloc(allocLength) ) )
{
strcpy(message,"Couldn't allocate memory in http_post_init");
return 1;
}
return 0;
}
else
{
initid->ptr=NULL;
}
}
return 0;
}
其中http_post_init需要返回my_bool型。这个函数目的是给用户提供一个方式,检验由mysql参数传进来的数据是否正确,如果正确则 返回0,则mysql会自动调用定义的主函数,如果返回1,则mysql打印message信息退出,不会调用主函数。所以在设定返回值的时候一定注意。
主函数如下:
longlong http_post( UDF_INIT *initid, UDF_ARGS *args,
char *is_null __attribute__((unused)),
char *error __attribute__((unused)))
{
char* sendBuffer=NULL;
CURL *curl;
CURLM *multi_handle;
int still_running;
int times=0;//try times if select false
int TRY_TIMES=25;
struct timeval timeout;//set a suitable timeout to play around with
timeout.tv_sec = 0;
timeout.tv_usec = 100000;
char sendArray[160000] = "\0";//can not move this into the if
if(initid->ptr == NULL)
{
//char sendArray[160000] = "\0";//error
sendBuffer=sendArray;
}
else
{
sendBuffer = initid->ptr;
TRY_TIMES=100;
}
strcpy(sendBuffer,args->args[1]);
curl = curl_easy_init();
multi_handle = curl_multi_init();
if(curl && multi_handle)
{
/* what URL that receives this POST */
curl_easy_setopt(curl, CURLOPT_URL,args->args[0]);
curl_easy_setopt(curl, CURLOPT_HTTPPOST, 1);
curl_easy_setopt(curl,CURLOPT_POSTFIELDS,sendBuffer);
curl_multi_add_handle(multi_handle, curl);
while(CURLM_CALL_MULTI_PERFORM == curl_multi_perform(multi_handle,\ &still_running));
while(still_running && times< TRY_TIMES)
{
int rc; //select() return code
int maxfd;
fd_set fdread;
fd_set fdwrite;
fd_set fdexcep;
FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_ZERO(&fdexcep); //get file descriptors from the transfers
curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep,\ &maxfd);
rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);
switch(rc)
{
case -1://select error
break;
case 0:
default: // timeout
while(CURLM_CALL_MULTI_PERFORM !== curl_multi_perform(multi_handle, &still_running));
break;
}
times++;
}//end while
curl_multi_remove_handle(multi_handle,curl);
curl_multi_cleanup(multi_handle);//always cleanup
curl_easy_cleanup(curl);
if(times>=TRY_TIMES)
{
return 1;
}
return 0;
}//end if
return 1;
}
在主函数中,主要使用curl库进行通信,curl库分成3部分,easy是同步模式,multi是异步模式,share模式是多线程共享数据的模式。
对于easy发送完数据后,会阻塞等待服务器的response,如果没 有返回,就会一直阻塞,当然可以设置一个timeout,但如果这个时间设小了,easy发送大数据的时候就会中断,设太大了影响时间效率,另外当接收端 不发送response的时候,easy库即使发送完了数据,也会阻塞等待,有些时候对于发送端来讲不需要等待接收端的respons,当发送完毕就可以 结束了,这个时候easy就不适用。所以最后选择multi库。
如程序所示,首先得初始化,并设置easy句柄为post模式,指定需要post的数据,如下:
curl = curl_easy_init();
multi_handle = curl_multi_init();
curl_easy_setopt(curl, CURLOPT_URL,args->args[0]);
curl_easy_setopt(curl, CURLOPT_HTTPPOST, 1);
curl_easy_setopt(curl,CURLOPT_POSTFIELDS,sendBuffer);
由于要使用multi模式,必须也要初始化一个easy模式,并将这个easy模式的句柄放入所谓的multi函数执行栈:
curl_multi_add_handle(multi_handle, curl);
使用curl_multi_perform(multi_handle, &still_running),来进行异步传输,但如果该函数返回的不是CURLM_CALL_MULTI_PERFORM,则需要重新执行。直到循环while(CURLM_CALL_MULTI_PERFORM == curl_multi_perform(multi_handle, &still_running));结束。此时如果刚才函数体中的still_running被置为1,表明连接建立,正在发送数据。需要配合select机制来进行数据发送。
函数 curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);会将最大的描述符写入maxfd,
然后用select进行等待:rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);
最后如果select返回值不为-1(error)0(timeout)时候再次进行异步传输,即执行curl_multi_perform函数,直到
still_running为0,程序结束退出。
这里设置了一个最大执行次数的限制,如果服务器出现了问题,不能发送response,则still_running不会变为0,程序会死循环,
所以,设置一个最大循环次数TRY_TIMES,防止这种情况发生。但是这个次数设小了,数据可能没有发送完,就退出了,如设置太大了,程序发送完了,服务器没有response就会多执行多余循环。所以这个TRY_TIMES需要根据数据的大小和网络状况来设置,比正常
传输数据的次数略长。这里我小数据的时候循环设次数25,大数据循环设为100.
最后是析构函数体:
void http_post_deinit(UDF_INIT *initid)
{
if (initid!=NULL && initid->ptr!=NULL)
{
free(initid->ptr);
initid->ptr = NULL;
}
}
将初始化函数设置的内存释放。
编译执行过程如下:
将程序保存为http_post.c编译如下(请根据机器上的mysql路径进行调整):
gcc -wall -I/usr/local/webserver/mysql/include/mysql/ -shared http_post.c -o http_post.so -fPIC
//使用mysql提供的头文件生成动态链接库
cp -f http_post.so /usr/local/webserver/mysql/lib/mysql/plugin/http_post.so
//将生成的.so文件放入mysql的plugin文件夹下
//进入mysql对动态链接库中的函数进行安装
cd /usr/local/webserver/mysql/bin/mysql
./mysql
//在mysql命令行下输入如下命令:
mysql> DROP FUNCTION IF EXISTS http_post;
//其目的是如果系统内安装了同名函数先进性drop。
mysql> CREATE FUNCTION http_post RETURNS INTEGER SONAME ‘http_post.so’;
//生成http_post函数,并指明调用来源是http_post.so。
//最后调用函数,其目的是向指定ip和端口发送post数据。调用前先打开指定ip主机上的网络调试助手,并监听3888端。
mysql> select http_post(‘testpost.com/index.php’,'sfasfa’);
在网络助手中可以看到如下结果:

Mar 18th

如图所示
1 请求到达后首先进入预处理模块。
2->3->4 预处理调用控制器层的类中的函数对xweibo全局数据进行设置(如session)。
5->6->7 应用初始化,如模板皮肤,应用安装位置,log位置格式,ip过滤等操作,并调用model层,设置用户的相关信息。
8->9 路由层,将用户请求交给相应的控制器(controller)进行处理。
10->11->12->13 控制器调用open api获取相关数据。
14->15->16->17 控制器缓存相关数据或者获取缓存的相关信息,或者站点数据库内定制的信息(结果显示数量等)。
18 控制器包含相关模板(view)进行显示。
19->20->21->22 每个模板(view)包含自己需要显示的组件(子view),每个组件调用model层获取自己所需的数据,进行显示。
用户请求进入后,可以指定加载预处理模块,这些预处理模块就是执行一些controller中的成员函数,比如初始化站点信息,比如检验用户是否登录。加载预处理模块时可以指定哪些请求跳过预处理模块,比如登录请求就不需要加载登录校验模块。
初始化操作,初始化工作包括如下:
1.应用配置:log日志格式,获取程序的安装位置,是否来自ajax请求,等等。
2.访问控制检测,过滤预定义的ip或请求路由(不允许直接请求的controller)
3.初始化模板皮肤
4.执行用户定义的预处理模块
路由层,根据路由配置获取请求参数中相应的变量,加载相应的php文件,实例化相应的类,并将请求其他参数传入,如果访问地址无效,执行function中的error404函数。
在项目controllers目录下的所有文件为xweibo的控制器代码。
XWEIBO中的控制器按功能分为两种,一种是作为预加载模块,在初始化过程中调用,如account.mod.php.另一种是请求通过路由层,进入控制器,调用控制器的相关方法获取数据,显示相关的模板(VIEW)。
以微博广场为例:
http://apptest/xweibo_upload/index.php?m=pub
则会进入pub.mod.php,当没有指定action时候默认调用控制器的default_action(),获取数据并包含相应模板页。
微博广场上的随便看看标签url,
http://apptest/xweibo_upload/index.php?m=pub.look
该url指定了pub控制器的,其action为look.调用pub.mod.php的look方法,获取数据,包含相应模板。
对于控制器的每一个方法,在执行前和执行后会执行钩子函数。即调用方法前执行 “_before_方法名()”,在调用方法后执行“_after_方法名( )” 函数。例如在pub.mod.php的look方法前,会执行_before_look()方法,执行完look方法后,会再执行一个_after_look()。提供钩子方法的好处是:当需要时候可以对数据做预处理或者当函数执行完后,根据已经获取的数据选择性的执行相应操作。
TIPS:
可能存在疑问是,那无论预处理或者调用后处理都可以在函数里执行或者加判断,为什么用钩子?
我的理解是,有些方法会被其他方法或者其他类显式的调用,如果在方法中增加额外代码会影响使用该方法的其他类,使用钩子方法,可以只在用户请求控制层的时候执行相应的钩子操作,钩子调用可以在cfg.php中关闭。
在templates/default目录下的所有*.tpl.php文件都是模板文件。
xweibo controller层与VIEW层,在任务分配上界限不明显。这体现在VIEW层也可以调用model层的操作获取、组织、过滤相应数据,当然获取完数据后需要对获取的数据进行显示,使用html+php混合的写法。
TIPS
XWEIBO没有使用模板引擎很大原因,是因为它想让数据组织和显示更自由化,从哪获取,显示什么样子在一个模板文件里可以更改,一个模板文件可以被其他文件包含,实现相应模块。如果使用模板引擎,在调用核心文件(core)提供的方法上不自由,另外所有的模块在使用模板引擎情况下,按照使用习惯应该再拆成两部分一部分组织数据(php文件),一部分显示数据(tpl模板文件)。并将所有组织数据部分放到controller部分。
XWEIBO的model层是modules下的所有文件,提供数据库、缓存、open api等数据获取或改写操作。
例如需要从open api获取数据,需使用modules目录下xwb.com.php类中的getPublicTimeline方法。
$list = DR('xweibo/xwb.getPublicTimeline', '', null, false);
即调用modeles目录下 xweibo目录下的xwb.com.php文件中的getPublicTimeline方法。model层可以调用class或者function或者adapter目录下的相应函数或类来实现数据处理。
核心文件其实提供调用其它公用类(class文件夹下)、公用函数(funciton文件夹下)、model层函数(module文件夹下)的统一入口。提供所有实例的管理,单实例缓存,操作适配器选择,公用数据类(USER)的维护.
常用方法如下:
DS函数
调用形式:DS(‘xweibo/xwb.getUnread’);
调用module下xwb类的getUnread方法,出错后直接处理,跳转到相应错误处理页面。
DR函数
调用形式:$res = DR(‘xweibo/xwb.getUnread’);
调用后,如果出错后,不直接处理,而是将错误返回,由用户做相应处理。
DR与DS其实就是相当于提供了调用model层的统一入口。出错或者不出错的判定由xweibo做了统一判定处理。实际上这两个函数是两个宏,为了使用方便的缩写,实际上是调用了CORE文件中的数据交互组件管理类dsMgr来进行处理。
TIPS:使用数据交互组件dsMgr好处?
正常使用情况下,我直接建立类的实例,进行相应的数据请求就可以了,如果只是出错处理是否返回问题,无法再传一个参数罢了。数据交互组件好处除了错误统一处理外,还封装了对返回数据处理。因为model层的请求有的是数据库,有些是API,所以对于返回数据可能需要做额外的处理,比如说缓存,比如说数据过滤,数据格式化。如果没有这个组件,那么API的相关操作,数据库相关操作,每一个都要对数据单独做缓存、过滤、格式化等处理。
使用方法:
define('HTTP_ADAPTER','curl');
$this->http = APP::ADP('http');
ADP适配器实际将一些可选的操作类或者平台相关操作类封装了。
以http适配器为例,adapter/http/目录下的所有类都提供了相同的方法,无论适配器选择了哪个类,调用时候都返回相同的结果,只不过curl_http库使用了curl库,fsockopen_http使用的是自己拼装的http数据。sae_http应该是在sae平台上使用的http库。
TIPS
这里xweibo有一个我感觉可以调整地方,这里适配器都是使用常量定义的,如果我在项目中需要根据某些判定条件动态改变适配器,只能在宏定义那做判定,不方便,或者跳过ADP函数自己建一个操作类实例。感觉xweibo开发人员可以调整下。
XWEIBO为用户的数据和操作单独抽象了一层,提供了公用的方法,具体的操作代理给具体的类进行操作。
举例说明:
需要获取用户UID的信息,只需要调用,USER:uid(),具体如何获取用户uid,是从session还是cookie还是数据库,memcache,这些可以代理类实现。xweibo提供了一个用session保存用户信息的clientUser类在class目录下。在我们项目中,我们将代理类改成使用cookie加密保存用户信息,只需要重新建立一个cookieClientUser类,提供相同的方法,在USER类初始化时候,换成实例化cookieClientUser类即可。
Tips
其实在功能上来看这个用户类实际上应该算是model,提供和封装了针对用户的数据交互,不清楚xweibo设计人员为什么将它提到core这层来。core的文件已经很大(2200行),如果修改出现问题会对所有访问照成影响。另外其实建议对与完成实际用户操作的类,也提供一层适配器操作,这样开发用户可以定制自己的用户操作类,无论是session或者cookie或者session服务器等等。
是xweibo的核心类,提供了建立和调用其他类入口
function N($oRoute) //根据路由,获取一个类实例
function M($mRoute) //执行一个模块(包括对应钩子函数)
function &O($oRoute) //获取一个单例,调用后缓存对象
function F($fRoute) //根据路由调用funciton目录下的函数
/*获取还原后的 $_GET ,$_POST , $_FILES $_COOKIE $_REQUEST $_SERVER $_ENV中的变量,例如V('G:TEST/BB'); 表示获取 $_GET['TEST']['BB'],该函数内使用静态变量,会缓存已经获取的变量*/
function V($vRoute, $def_v=NULL, $setVar=false)
CORE|TPL类/IO类/cache类
提供了模板类,但模板只是将assign的变量转化为global,然后在display前,将全局的变量转化成局部变量,在模板所对应的文件直接使用。
extract($GLOBALS[V_GLOBAL_NAME]['TPL']);
提供了统一的IO操作,使用IO类来执行文件读写,增删等操作。
提供了统一缓存操作,通过适配器可以选择文件缓存,memcache缓存,目前xweibo主要缓存了,用户一些配置信息,比如皮肤一类的信息,调用API显示表示需要缓存数据,可以设置缓存时间。
感觉xweibo提供了一个完整的微博内容展示框架,内容以组件的形式表现,可以层层拼装,包含,模板在可复用性极强。提供了简单的管理后台,允许用户对显示规则进行定制,也方便二次开发。提供了完整的缓存策略,提供了对API封装和访问出错处理控制。提供了微博相关的各种实用函数和类,比如内容的format(文本中出现@等替换成链接),加解密处理(des),http的curl封装等等。
但就我个人使用感觉上,还有细节上需要改进的地方,比如说现在的类和函数这块应该也提供适配器,拿openapi举例,现在的第三方应用的api调用形式,根据需要各有不同(如认证),用户对于各种类的使用也各有不同,比如说format。虽然说放弃了smarty可以减少文件的层次(数据组织和显示),但前端开发人员可能更习惯于使用模板类,建议将templates下的所有模板再拆分两层,一层放到controller,一层完全是smarty模板(想办法也能继续使用plugin函数)。细节上把一些能公用的小功能再抽出来成一个util类,比如oauth文件里的对http_query头的封装,就可以单拿出一个静态函数。另外感觉xweibo为了兼容sae也加入不少代码,虽然通过配置一点不影响使用,但查看代码时候会遇到很多平台相关判断,开发者体验上怪点。感觉上如果人手充足针对sae平台单独设计优化,对外提供一个纯净版本的,呵呵,以上都是个人意见,对于xweibo自己也有很多地方要学习,很好很强大,第三方开发的好框架。
Jan 5th
1使用root登陆
su – ->输入用户名密码 ->gedit /etc/pam.d/gdm ->用#郝注释掉auth required pam_succeed_if.so user != root quiet ->保存退出
gedit /etc/pam.d/gdm-password& -> 用#郝注释掉auth required pam_succeed_if.so user != root quiet ->保存退出
2虚拟机下安装vmtool准备工作
yum -y install gcc gcc-c++ kernel-devel
yum -y update kernel
重新启动后,vmware->vm->install vm tools
把安装包从虚拟光驱中拷贝到home,解压缩。执行里面的脚本即可。
3安装输入法
安装
yum install ibus-sunpinyin
选择
applications->other->input method selector-> 选择useibus->preference->input Method
->select inputmethod->show all input method->拼音->重新登录即可
* *************************************************
* Created on :2011-12-11 17:35:48
* Encoding :UTF-8
* Description: 求最大子串和
* 求任意N个正负整数里面最大的连续和,要求复杂度为O(n)
* 认为当所有输入都为负数时,最大子串和为0
* 测试序列array(-2, 1, 3, 9, -4, 2, 3, 5, -3, -4, 1, 3);
* 输出结果19
* ************************************************
*/
function getMaxSequenceSum(array $sequence)
{
$maxSoFar = 0;
$maxEndingHere = 0;
for ( $i = 0; $i < count($sequence); $i++ )
{
$maxEndingHere = $maxEndingHere + $sequence[$i];
if ( $maxEndingHere < 0 ) {
$maxEndingHere = 0;
} else {
if ( $maxEndingHere > $maxSoFar ) $maxSoFar = $maxEndingHere;
}
}
return $maxSoFar;
}
$sequence = array(-2, 1, 3, 9, -4, 2, 3, 5, -3, -4, 1, 3);
$max = getMaxSequenceSum($sequence);
var_dump($max);
该题目有4种解法,复杂度分别为O(n*n*n) O(n*n) O(nlogn) O(n)
分别对应三次变量,记住当前和的两次遍历,使用分治算法和一次遍历算法。