问题
多线程libcurl运行一段时间后出现崩掉,没有确定的点,没有确定的URL。一直查看源代码没有问题,最后通过debug跟踪发现是在访问SSL的时候出现的crash。
才想起来openssl是不支持多线程的,要自己做加锁处理。而且libcurl中并没有支持相关的加锁操作。
解决办法:
在初始化libcurl的时候为openssl创建一个互斥锁函数,一个回调函数传给openss
openssl锁l函数原形 :void (* func )(int ,int , const char * ,int)
设置方式:CRYPTO_set_locking_callback(void (* func )(int ,int , const char * ,int));
设置这样一个函数还不够,另外还要配置一个锁id回调函数,这个可以参考openssl多线程下的使用相关。
id函数原形:unsigned int (*func)(void)
设置方式:CRYPTO_set_id_callback(unsigned int (*func)(void));
通过这两个设置就可以解决HTTPS多线程请求出现crash的问题。
代码示例:
下面是引用了libcurl示例的一个代码http://curl.haxx.se/libcurl/c/opensslthreadlock.html
最关键就是,两个callback的实现,还有初始化锁(init_locks)和释放锁(kill_locks)的位置
- #define USE_OPENSSL
-
- #include <stdio.h>
- #include <pthread.h>
- #include <curl/curl.h>
-
- #define NUMT 4
-
- /* we have this global to let the callback get easy access to it */
- static pthread_mutex_t *lockarray;
-
- #ifdef USE_OPENSSL
- #include <openssl/crypto.h>
- static void lock_callback(int mode, int type, char *file, int line)
- {
- (void)file;
- (void)line;
- if (mode & CRYPTO_LOCK) {
- pthread_mutex_lock(&(lockarray[type]));
- }
- else {
- pthread_mutex_unlock(&(lockarray[type]));
- }
- }
-
- static unsigned long thread_id(void)
- {
- unsigned long ret;
-
- ret=(unsigned long)pthread_self();
- return(ret);
- }
-
- static void init_locks(void)
- {
- int i;
-
- lockarray=(pthread_mutex_t *)OPENSSL_malloc(CRYPTO_num_locks() *
- sizeof(pthread_mutex_t));
- for (i=0; i<CRYPTO_num_locks(); i++) {
- pthread_mutex_init(&(lockarray[i]),NULL);
- }
-
- CRYPTO_set_id_callback((unsigned long (*)())thread_id);
- CRYPTO_set_locking_callback((void (*)())lock_callback);
- }
-
- static void kill_locks(void)
- {
- int i;
-
- CRYPTO_set_locking_callback(NULL);
- for (i=0; i<CRYPTO_num_locks(); i++)
- pthread_mutex_destroy(&(lockarray[i]));
-
- OPENSSL_free(lockarray);
- }
- #endif
-
- #ifdef USE_GNUTLS
- #include <gcrypt.h>
- #include <errno.h>
-
- GCRY_THREAD_OPTION_PTHREAD_IMPL;
-
- void init_locks(void)
- {
- gcry_control(GCRYCTL_SET_THREAD_CBS);
- }
-
- #define kill_locks()
- #endif
-
- /* List of URLs to fetch.*/
- const char * const urls[]= {
- "https://www.example.com/",
- "https://www2.example.com/",
- "https://www3.example.com/",
- "https://www4.example.com/",
- };
-
- static void *pull_one_url(void *url)
- {
- CURL *curl;
-
- curl = curl_easy_init();
- curl_easy_setopt(curl, CURLOPT_URL, url);
- /* this example doesn't verify the server's certificate, which means we
- might be downloading stuff from an impostor */
- curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
- curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
- curl_easy_perform(curl); /* ignores error */
- curl_easy_cleanup(curl);
-
- return NULL;
- }
-
- int main(int argc, char **argv)
- {
- pthread_t tid[NUMT];
- int i;
- int error;
- (void)argc; /* we don't use any arguments in this example */
- (void)argv;
-
- /* Must initialize libcurl before any threads are started */
- curl_global_init(CURL_GLOBAL_ALL);
-
- init_locks();
-
- for(i=0; i< NUMT; i++) {
- error = pthread_create(&tid[i],
- NULL, /* default attributes please */
- pull_one_url,
- (void *)urls[i]);
- if(0 != error)
- fprintf(stderr, "Couldn't run thread number %d, errno %d\n", i, error);
- else
- fprintf(stderr, "Thread %d, gets %s\n", i, urls[i]);
- }
-
- /* now wait for all threads to terminate */
- for(i=0; i< NUMT; i++) {
- error = pthread_join(tid[i], NULL);
- fprintf(stderr, "Thread %d terminated\n", i);
- }
-
- kill_locks();
-
- return 0;
- }
BIOS 即basic input output system,在计算机的开机过程中伴有很重要的角色。它的启动顺序如下:
第一步: 当我们按下电源开关时,电源就开始向主板和其它设备供电,此时电压还不太稳定,主板上的控制芯片组会向CPU发出并保持一个RESET(重置)信号,让 CPU内部自动恢复到初始状态,但CPU在此刻不会马上执行指令。当芯片组检测到电源已经开始稳定供电了(当然从不稳定到稳定的过程只是一瞬间的事情), 它便撤去RESET信号(如果是手工按下计算机面板上的Reset按钮来重启机器,那么松开该按钮时芯片组就会撤去RESET信号),CPU马上就从地址 FFFF0H处开始执行指令,从前面的介绍可知,这个地址实际上在系统BIOS的地址范围内,无论是Award BIOS还是AMI BIOS,放在这里的只是一条跳转指令,跳到系统BIOS中真正的启动代码处。
第二步: 系统BIOS的启动代码首先要做的事情就是进行POST(Power-On Self Test,加电后自检),POST的主要任务是检测系统中一些关键设备是否存在和能否正常工作,例如内存和显卡等设备。由 于POST是最早进行的检测过 程,此时显卡还没有初始化,如果系统BIOS在进行POST的过程中发现了一些致命错误,例如没有找到内存或者内存有问题(此时只会检查640K常规内 存),那么系统BIOS就会直接控制喇叭发声来报告错误,声音的长短和次数代表了错误的类型。在正常情况下,POST过程进行得非常快,我们几乎无法感觉 到它的存在,POST结束之后就会调用其它代码来进行更完整的硬件检测。
第三步: 接下来系统BIOS将查找显卡的BIOS,前面说过,存放显卡BIOS的ROM芯片的起始地址通常设在C0000H处,系统BIOS在这个地方找到显卡 BIOS之后就调用它的初始化代 码,由显卡BIOS来初始化显卡,此时多数显卡都会在屏幕上显示出一些初始化信息,介绍生产厂商、图形芯片类型等内容,不 过这个画面几乎是一闪而过。系统BIOS接着会查找其它设备的BIOS程序,找到之后同样要调用这些BIOS内部的初始化代码来初始化相关的设备。
第四步: 查找完所有其它设备的BIOS之后,系统BIOS将显示出它自己的启动画面,其中包括有系统BIOS的类型、序列号和版本号等内容。
第五步: 接着系统BIOS将检测和显示CPU的类型和工作频率,然后开始测试所有的RAM,并同时在屏幕上显示内存测试的进度,我们可以在CMOS设置中自行决定 使用简单耗时少或者详细耗时多的测试方式。
第六步: 内存测试通过之后,系统BIOS将开始检测系统中安装的一些标准硬件设备,包括硬盘、CD-ROM、串口、并口、软驱等设备,另外绝大多数较新版本的系统BIOS在这 一过程中还要自动检测和设置内存的定时参数、硬盘参数和访问模式等。
第七步: 标准设备检测完毕后,系统BIOS内部的支持即插即用的代码将开始检测和配置系统中安装的即插即用设备,每找到一个设备之后,系统BIOS都会在屏幕上显 示出设备的名称和型号等信息,同时为该设备分配中断、DMA通道和I/O端口等资源。
第八步: 到这一步为止,所有硬件都已经检测配置完毕了,多数系统BIOS会重新清屏并在屏幕上方显示出一个表格,其中概略地列出了系统中安装的各种标准硬件设备, 以及它们使用的资源和一些相关工作参数。
第九步: 接下来系统BIOS将更新ESCD(Extended System Configuration Data,扩展系统配置数据)。ESCD是系统BIOS用来与操作系统交换硬件配置信息的一种手段,这些数据被存放在CMOS(一小块特殊的RAM,由主 板上的电池来供电)之中。通常ESCD数据只在系统硬件配置发生改变后才会更新,所以不是每次启动机器时我们都能够看到“Update ESCD… Success”这样的信息,不过,某些主板的系统BIOS在保存ESCD数据时使用了与Windows 9x不相同的数据格式,于是Windows 9x在它自己的启动过程中会把ESCD数据修改成自己的格式,但在下一次启动机器时,即使硬件配置没有发生改变,系统BIOS也会把ESCD的数据格式改 回来,如此循环,将会导致在每次启动机器时,系统BIOS都要更新一遍ESCD,这就是为什么有些机器在每次启动时都会显示出相关信息的原因。
第十步: ESCD更新完毕后,系统BIOS的启动代码将进行它的最后一项工作,即根据用户指定的启动顺序从软盘、硬盘或光驱启动。 以从C盘启动为例,系统BIOS 将读取并执行硬盘上的主引导记录,主引导记录接着从分区表中找到第一个活动分区,然后读取并执行这个活动分区的分区引导记录,而分区引导记录将负责读取并 执行IO.SYS,这是DOS和Windows 9x最基本的系统文件。Windows 9x的IO.SYS首先要初始化一些重要的系统数据,然后就显示出我们熟悉的蓝天白云,在这幅画面之下,Windows将继续进行DOS部分和GUI(图 形用户界面)部分的引导和初始化工作。
1/ 多线程下程序蹦的问题
查找 crul_easy_getinfo
将上面的 int http_code = 0; 改为 long http_code = 0;
2/ 多线程下超时退出 signal 11问题
在 curl_easy_perform(curl);前加上 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
3/ 多线程环境下,程序启动时调用一次 curl_global_init(CURL_GLOBAL_ALL);
程序退出时调用curl_global_cleanup();
http://blog.csdn.net/jaylong35/article/details/6988690
----------------------------------------------------------------------------------------------
包含tb_player和79669的行
tb_player.*?79669
----------------------------------------------------------------------------------------------
数据库的PITR是一般数据库都必须满足的技术。其原理是依据之前的物理备份文件加上wal的预写日志模式备份做的恢复。该技术支持8.*及以上版本。下面主要概述PITR的准备和恢复过程。 测试环境
OS 环境:CentOS 6.2
数据库 :PostgreSQL 9.1.9
一、前期工作既要恢复,肯定是需要一个备份基础的,否则再怎么的巧妇也难为无米之炊。
1.修改数据库参数,修改postgresql.conf:
archive_mode = on
archive_timeout = 300 --单位是秒,此处以5分钟为限强制归档,仅作测试
archive_command = 'cp %p /data/pgbackup/archive/%f' -- 注意/data/pgbackup/archive/目录权限, chmod -R 777 /data/pgbackup/archive/
wal_level = archive
修改完重启下reload,DB
2.基于文件级别的持续备份,
a.基础备份
postgres=# select pg_start_backup('backup_2012_05_20_14:22:10');
b.打包备份pg_data
# cd /data
# tar -cvzf pgdata.tar ./postgres
mv pgdata.tar /data/pgbackup/base/
c.结束基础备份并切换归档
postgres=# select pg_stop_backup();
postgres=# select pg_switch_xlog();
pg_switch_xlog
----------------
0/C000020
(1 row)
postgres=# select pg_current_xlog_location();
pg_current_xlog_location
--------------------------
0/C000020
(1 row)
postgres=# create table test_1(id int,name varchar(50));
postgres=# insert into test_1 values (1,'kenyon');
INSERT 0 1
此时在pg_data路径下会产生一个label,可以查看内容有checkpoint时间,基础备份的开始和结束时间,以及标签名称等。因为之前已经设置了archive的三个参数,可以在archive的备份路径pg_home/archive下看到归档的文件会定时传过来。
二、恢复过程
停数据库
# pg_stop
假定数据库的崩溃场景,将pgdata数据删除
# rm -rf /database/pgdata
恢复之前备份的tar文件
# tar xvf pgdata.tar
删除pg_xlog文件夹并重建
# rm -rf pg_xlog
# mkdir -p pg_xlog/archive_status
新建recovery.conf文件并修改
# vi /data/postgres/recovery.conf
--新增内容,指定恢复文件和路径,%f,%p见上面说明
restore_command = 'cp /data/pgbackup/archive/%f "%p"'
启动数据库
# pg_start
[postgres@localhost archive]$ psql
spsql (9.1.3)
Type "help" for help.
postgres=# select * from test_1;
id | name
----+--------
1 | kenyon
(1 rows)
--恢复成功,会恢复到之前接收到的最后一个归档文件。另外recovery.conf会改名变成recovery.done
日志内容:
LOG: shutting down
LOG: database system is shut down
LOG: database system was interrupted; last known up at 2012-05-20 22:23:15 CST
LOG: starting archive recovery
LOG: restored log file "000000010000000000000002" from archive
LOG: redo starts at 0/8000078
LOG: consistent recovery state reached at 0/C000000
LOG: restored log file "000000010000000000000003" from archive
LOG: restored log file "000000010000000000000004" from archive
LOG: restored log file "000000010000000000000005" from archive
LOG: restored log file "000000010000000000000006" from archive
LOG: restored log file "000000010000000000000007" from archive
cp: cannot stat `/home/postgres/archive/000000010000000000000008': No such file or directory
LOG: could not open file "pg_xlog/000000010000000000000008" (log file 0, segment 8): No such file or directory
LOG: redo done at 0/1C000078
LOG: last completed transaction was at log time 2012-05-20 23:01:22.960591+08
LOG: restored log file "000000010000000000000007" from archive
cp: cannot stat `/home/postgres/archive/00000002.history': No such file or directory
LOG: selected new timeline ID: 2
cp: cannot stat `/home/postgres/archive/00000001.history': No such file or directory
LOG: archive recovery complete
LOG: database system is ready to accept connections
LOG: autovacuum launcher started
PS:若要恢复到指定时间,还需要再recovery.conf中设置recovrey_target_time,recovery_target_timeline等参数
总结:pitr技术对于7*24小时支撑是至关重要的,但是如果数据库非常小,增大pg_dump备份的频率可能更方便,但对于大数据库就需要了。
由于Cron 是Linux的内置服务,可以用以下的方法启动、关闭这个服务:
/sbin/service crond start //启动服务
/sbin/service crond stop //关闭服务
/sbin/service crond restart //重启服务
/sbin/service crond reload //重新载入配置
Crontab命令的格式为:crontab –l|-r|-e|-i [username],其参数含义如表一
crontab -e //编辑crontab文件
crontab 格式
第1列分钟1~59
第2列小时1~23(0表示子夜)
第3列日1~31
第4列月1~12
第5列星期0~6(0表示星期天)
第6列要运行的命令
下面是crontab的格式:
分 时 日 月 星期 要运行的命令
这里有crontab文件条目的一些例子:
30 21 * * * /usr/local/apache/bin/apachectl restart
上面的例子表示每晚的21:30重启apache。
45 4 1,10,22 * * /usr/local/apache/bin/apachectl restart
上面的例子表示每月1、10、22日的4 : 45重启apache。
10 1 * * 6,0 /usr/local/apache/bin/apachectl restart
上面的例子表示每周六、周日的1 : 10重启apache。
0,30 18-23 * * * /usr/local/apache/bin/apachectl restart
上面的例子表示在每天18 : 00至23 : 00之间每隔30分钟重启apache。
0 23 * * 6 /usr/local/apache/bin/apachectl restart
上面的例子表示每星期六的11 : 00 pm重启apache。
* */1 * * * /usr/local/apache/bin/apachectl restart
每一小时重启apache
* 23-7/1 * * * /usr/local/apache/bin/apachectl restart
晚上11点到早上7点之间,每隔一小时重启apache
0 11 4 * mon-wed /usr/local/apache/bin/apachectl restart
每月的4号与每周一到周三的11点重启apache
0 4 1 jan * /usr/local/apache/bin/apachectl restart
一月一号的4点重启apache
-----------------------------------------------------------------------------
grep 一次排除多个
grep -v 'aaaa\|bbbb' // 排除aaaa和bbbb
-----------------------------------------------------------------------------
查看文件数
ls |wc -l
-----------------------------------------------------------------------------
查看文件夹大小
du -sh *
du -sh /home
du -sh
------------------------------------------------------------------------------
查找最新的文件
(1)将文件按从新到旧排列,取第一个。
ls -t *.cpp | head -1
(2)将文件按从旧到新排列,取最后一个。
ls -rt *.cpp | tail -1
------------------------------------------------------------------------------
linux下递归删除某个文件夹或文件
今天给学校部署校庆网站,做网站的同学传给我的网站文件夹里,有很多exe文件,而且这些exe的文件名都是原来的目录名,看起来是他的机器中了病毒。虽然exe文件在linux下无法运行,但是还是要删除这些exe文件。在网上找了一下,《linux下递归删除某个文件夹或文件》给了我满意的方法,让我可以一次性删除某目录及其子目录下所有的exe文件。
find . -name '*.exe' -type f -print -exec rm -rf {} \;
(1) "." 表示从当前目录开始递归查找
(2) “ -name '*.exe' "根据名称来查找,要查找所有以.exe结尾的文件夹或者文件
(3) " -type f "查找的类型为文件
(4) "-print" 输出查找的文件目录名
(5) 最主要的是是-exec了,-exec选项后边跟着一个所要执行的命令,表示将find出来的文件或目录执行该命令。exec选项后面跟随着所要执行的命令或脚本,然后是一对儿{},一个空格和一个\,最后是一个分号
-----------------------------------------------------------------------------
yum install readline-devel
./configure -without-zlib
make
su
make install
mkdir -p /data/postgres
ln -s /data/postgres/ /home/
groupadd postgres
useradd -d /home/postgres -g postgres postgres
chown -R postgres:postgres /data/postgres/
su - postgres
/usr/local/pgsql/bin/initdb -A md5 --locale=en_US.utf8 --lc-ctype=en_US.utf8 -E UTF-8 -W /data/postgres
ln -s /usr/local/pgsql/bin/ ./
mkdir /data/postgres/pg_log
chown -R postgres:postgres /data/postgres/pg_log
//开启pg
bin/postgres -D /data/postgres >pg_log/logfile 2>&1 &
// 设为开机启动
cp /data/zc/postgresql-9.1.9/contrib/start-scripts/linux /etc/init.d/postgresql
chmod a+x /etc/init.d/postgresql
// 修改/etc/init.d/postgresql
prefix 设为postgres安装路径
PGDATA 设为数据库路径(/data/postgres)
chkconfig --add postgresql
前几篇文章中已经介绍过局部变量和环境变量的含义,接下来我们来拓展下,看看变量怎样实现在shell脚本
中的传递shell脚本其实是用当前shell的子shell去执行的,所以在shell脚本中定义的普通变量只适用于当前
shell的子shell环境,也就是说在当前shell环境中不适用,也不适用于这个shell脚本的子shell。
在shell脚本中定义的环境变量可以传承给它的子shell,但是也不能传递给当前shell(不能逆向传递)
如果在一个脚本中需要执行另一个脚本,并且运用其中的变量,改如何申明变量呢,我们来看一个例子:
/root/test1.sh内容如下:
#!/bin/bash
aaa=yuanfaxiang
echo "test1:$aaa"
/root/test2.sh内容如下:
#!/bin/bash
/root/test1.sh
echo "test2:$aaa"
执行test2.sh结果如下:
[root@centos ~]# sh test2.sh
test1:yuanfaxiang
test2:
从结果可以看出test1.sh没有把变量aaa的值传递给test2.sh
我们把test2.sh改成:
#!/bin/bash
source /root/test1.sh
echo "test2:$aaa"
执行test2.sh结果如下:
[root@centos ~]# sh test2.sh
test1:yuanfaxiang
test2:yuanfaxiang
结果显示test2.sh继承了test1.sh中定义的变量aaa。
原因分析:在第一次执行test2.sh时,test1.sh被作为了test2.sh的子shell来执行,其中定义的变量只
在test1.sh中起效,不能逆向传递到test2.sh中;而在第二次执行中,采用source来执行test1.sh,意思
是直接把test1.sh在当前的test2.sh中执行,没有作为子shell去执行,test1.sh中定义的变量,就影响
到了test2.sh。
如果我们再建一个test3.sh
#!/bin/bash
echo "test3:$aaa"
把test2.sh改成:
#!/bin/bash
source /root/test1.sh
echo "test2:$aaa"
/root/test3.sh
执行test2.sh:
[root@shenji ~]# sh test2.sh
test1:yuanfaxiang
test2:yuanfaxiang
test3:
结果显示test3.sh没有继承test1.sh中申明的变量,因为source /root/test1.sh只是让test1.sh
中的变量在test2.sh中生效,aaa毕竟还是个普通局部变量,并不能被test3.sh这个子shell所继承,
所以我们可以想到环境变量,把aaa变成test2.sh这个脚本的环境变量,让test2.sh的子进程也能继承。
将test1.sh改成:
#!/bin/bash
export aaa=yuanfaxiang
echo "test1:$aaa"
执行test2.sh后有如下结果:
[root@shenji ~]# sh test2.sh
test1:yuanfaxiang
test2:yuanfaxiang
test3:yuanfaxiang
在test1.sh中声明了环境变量也就是全局变量,在test2.sh中用source执行test1.sh,将变量带到了
test2.sh中,并使之成为test2.sh执行过程中的环境变量,可以被test2.sh的子进程继承,起到了顺向
传递效果。
最近项目要上QQ平台, 用到腾迅开放平台的SDK, SDK使用的是libcurl. 发现很多bug,
其中最大的问题是, sdk在单线程下正常, 在多线程下就蹦, 后来研究发现 是curl_easy_getinfo第三个参数需要的是long, 而SDK传给它的是int, 其它地方还有类似的错误.
哎,腾迅也是坑爹啊.
-------------------------------------------------------------
1.libcurl中常用的API
#include <curl/curl.h>
CURL *curl_easy_init()
必须第一个调用,返回一个CURL指针,用于表示当前的curl会话。
如果之前没有执行curl_global_init(long flags),则它会自动先执行。
但是由于curl_global_init(long flags)和其调用的函数在多线程中是不安全的,所以多线程程序中必须在其它线程启动之前执行curl_global_init(long flags)。
void curl_easy_cleanup(CURL * handle )
会话结束后调用,以清除一个CURL * handle
CURLcode curl_easy_setopt(CURL *handle, CURLoption option, parameter)
设置libcurl执行参数
CURLcode curl_easy_perform(CURL * handle)
根据设置的参数执行网络数据传输,在初始化和参数设置后调用
CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ... )
获取所指向的libcurl会话的执行信息
2.libcurl最简单使用流程
#include <stdio.h>
#include <curl/curl.h>
int main(void)
{
CURL *curl;
CURLcode res;
int response;
res = curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "curl.haxx.se");
res = curl_easy_perform(curl);
res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
curl_easy_cleanup(curl);
}
curl_global_cleanup();
return 0;
}
3.libcurl使用实例
更多的使用示例可以参考官方网站上的以下网页
http://curl.haxx.se/libcurl/c/example.html
参考资料:
http://curl.haxx.se