经常会在系统中使用gettimeofday获取当前系统时间,然后在另外一处再次调用该系统函数获取系统时间,来判断一段代码的执行时间。

但由于gettimeofday的精度问题,可能存在短暂的“时光倒流”(Time Backward),如下一段程序实验

int is_less(struct timeval a, struct timeval b)
{
    if(a.tv_sec < b.tv_sec) {
        return 1;
    }
    if(a.tv_sec == b.tv_sec && a.tv_usec < b.tv_usec) {
        return 1;
    }

    return 0;
}

int main(void)
{
    while(1) {
        struct timeval last;
        gettimeofday(&last, NULL);
        struct timeval now;
        gettimeofday(&now, NULL);
        if (is_less(now, last) == 1) {
            printf("now[%lu, %lu] < last[%lu, %lu]\n",
                now.tv_sec, now.tv_usec, last.tv_sec, last.tv_usec);
            break;
        }
    }
    return 0;
}

在运行了接近12个小时后,出现了一次Time backward

详细的信息可以参考下面这个blog说明,使用time(NULL)获取时间也一样会有这个问题,实现上time函数底层就调用的gettimeofday。

http://blog.habets.pp.se/2010/09/gettimeofday-should-never-be-used-to-measure-time

当我们需要对某段读写文件并进行处理的程序进行性能测试时,文件会被系统cache住从而影响I/O的效率,必须清理cache中的对应文件的才能正确的进行性能测试。通常清理内存可以采用下面的这条命令,但这条命令只有root才能使用,另外一方面这个会清理所有的cache,也许会影响其他程序的性能。

echo 3>/proc/sys/vm/drop_caches

linux下有一个posix_fadvise函数可以用来对cache中的文件进行清理,有关posix_fadvise的详细说明查看man手册。

int posix_fadvise(int fd, off_t offset, off_t len, int advice);

”Programs  can  use  posix_fadvise  to  announce an intention to access file data in a specific pattern in the future, thus allowing the kernel to perform appropriate optimisations”

fd是文件的描述符,用于清理对应文件cache的advice值选取POSIX_FADV_DONTNEED,利用此函数编写下面程序进行文件的清理。

int clear_file_cache(const char *filename)
{
	struct stat st;
	if(stat(filename , &st) < 0) {
		fprintf(stderr , "stat localfile failed, path:%s\n",filename);
		return -1;
	}

	int fd = open(filename, O_RDONLY);
	if( fd < 0 ) {
		fprintf(stderr , "open localfile failed, path:%s\n",filename);
		return -1;
	}

	//clear cache by posix_fadvise

	if( posix_fadvise(fd,0,st.st_size,POSIX_FADV_DONTNEED) != 0) {
		printf("Cache FADV_DONTNEED failed, %s\n",strerror(errno));
	}
	else {
		printf("Cache FADV_DONTNEED done\n");
	}

	return 0;
}

此外,linux-ftools这个工具也可以帮助清理并查看文件的内存状态,主页上也有详细的使用说明。编译后我们利用fincore这个工具来查看文件在内存中的状态,有关fincore的实现可以在linux下man mincore,mincore是根据缓存buffer指针来其指向的缓冲区判断在cache中的状态,fincore就是在mincore的基础上直接操作文件,就是通过对文件mmap获得指针,再调用mincore来判断。

首先我们通过cp命令拷贝了一个相对有点容量的文件,然后利用fincore查看文件在内存中的cache情况,可以看到大概cache了99.55%。

image

接着执行上面那段代码的运行程序,之后再执行命令查看该文件的缓存状态

image

可以看到,该文件在内存中已经没有被cache了。实际的清理效果也可以通过一些占用I/O的读文件程序来测试,一般程序第二遍运行时候由于文件已经被cache,实际程序运行的速度会比较快,通过上面的posix_fadivse清理之后,又会恢复和第一遍差不多的时间。

, ,

强制类型转换

MySQL中提供了两个函数来实现强制类型转换,分别为convert和cast,这两个函数在将一个数值转换为其他类型时的作用几乎一样,写法上略有不同,但convert函数可以将一个值转为其他编码。参考MySQL手册说明:

编码转换:CONVERT(expr  USING transcoding_name),如

image

类型转换:CONVERT(expr , type) 或者 CAST(expr AS type),其中type为以下类型中的一种

  • BINARY [(N)]  转为二进制字符串,后面N为限定长度,有关BINARY类型的参考点这里
  • CHAR [(N)]
  • DATE   日期
  • DATETIME  日期时间
  • DECIMAL [(M[,D])]   转为浮点数,M为数字的长度(包括整数部分和小数部分),N为小数点后位数

转为DECIMAL类似C语言中的atoi函数,即从头扫描字符串直到第一个不为数字的字符截至,对截断的那一位进行四舍五入取整。

默认不限定M,N,转换是转为整数,按照小数点后第一位进行四舍五入。

image

限定M不限定N,则根据数字的位数输出整数,需要注意的是,当M的长度小于实际的数字位数时,会转化成设定为最大的,如下面这个转换,由于设置M为1,即显示不了21这个两位数,就会显示 1位数中最大的9。如果对于设定了小数点后位数N的限定,也会这样转换,同时必须先满足小数点后的N位小数。

image 位数不够显示全部,则

image  满足了1位小数,再取1位整数

image 必须先满足2位小数,不取整数部分。

正常的转换如下,尽可能的将M设置大,而N根据自己的需要选择保留几位小数。

image保留一位小数,第二位四舍五入

image保留两位小数

image保留三位小数,加0补齐位数

需要注意,尽管M可以设置的足够大而不影响数字的长度,但转换依然会先满足小数位数N的需要

image 满足9位小数,只剩1位整数只能取9。

  • SIGNED 有符号整数
  • TIME 日期的时间部分
  • UNSIGNED 无符号整数

image

其中上面的’2’-3由MySQL自动做了一次强制类型转换,根据四则运算将’2’转为了数字2。

自动类型转换

在进行一些四则运算以及逻辑运算时,MySQL也会根据计算的类型以及操作数的类型进行自动转换。

image 四则运算,转为数字操作

image 逻辑运算,转为0或1

image 位操作,转为数字的二进制位。

也可以一些自动进行数字以及字符串向date的转换,例如下面的操作

image

写码过程中碰到这样一个需求。一个table中一列是varchar类型,但是输入的类似是’1.23/张’ 这样的数据,即前面是一个类似单价的东西,后面只是加了一个说明,另外一列为int,需要计算两列的乘积。有了MySQL这样的自动类型转换,就不需要select后逐条的进行atoi的操作,并计算结果进行update了,特别是其中某些列中的数据不全,即varchar为NULL或者int为NULL,会自动不处理,相当的方便哈。如下。

image

最后一句,大爱MySQL哈!!!

, , , ,

apache2.2提供了mod_proxy,可以直接利用proxy ajp进行tomcat和apache的整合,过程很简单

httpd.conf中注释掉

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so

直接添加虚拟主机

<VirtualHost   *:80>
ServerAdmin   webmaster@leoncomzh.com
DocumentRoot  /var/www/htdocs
ProxyPass /blog ! #设置/blog目录不跳转
ProxyPass   /   ajp://localhost:8009/
ProxyPassReverse   /   ajp://localhost:8009/
ServerName  jsp.leoncomzh.com
</VirtualHost>

输入jsp.leoncomzh.com可以之间跳转到tomcat的welcome page了,不跳转的选项必须写在跳转请求的前边

P.S: 如果出现的是503错误,error.log中是否是

(OS 10061)由于目标计算机积极拒绝,无法连接。  : proxy: AJP: attempt to connect to 127.0.0.1:8009 (127.0.0.1) failed

出现这个错误,修改tomcat的server.xml,给Connector加上address=”localhost”即可

<Connector port=”8009″  address=”localhost”
enableLookups=”false” redirectPort=”8443″ protocol=”AJP/1.3″ />

最后这个错误折腾了一晚上,google半天都没见说要改tomcat的conf…

, ,

修改FLEA.php的510行开始的getCache,writeCache,purgeCache一共3个函数,使用memcache库。

不过好像这样对于我的wb的优化感觉不大,还是仔细考虑,自己在程序逻辑上查询量大、更新少的需要的地方添加memcache比较重要。话说查询最多的可能就是Tweets了,可是实时变化。。唉。。。

memcached的使用很方便,但是原理很牛逼,推荐看mixi.jp人写的东西

http://tech.idv2.com/2008/07/10/memcached-001/

 function getCache($cacheId, $time = 900, $timeIsLifetime = true, $cacheIdIsFilename = false)
 {
 $memcache_obj = new Memcache;
 $memcache_obj->connect('localhost', 11211);
 $data = $memcache_obj->get($cacheId);
 if(!$data) return false;

 return $data !== false ? unserialize($data) : false;
 }

 function writeCache($cacheId, $data, $cacheIdIsFilename = false)
 {
 $data = serialize($data);

 $memcache_obj = new Memcache;
 $memcache_obj->connect('localhost', 11211);
 return $memcache_obj->set($data, $cacheId, MEMCACHE_COMPRESSED, 900);

 }

 function purgeCache($cacheId, $cacheIdIsFilename = false)
 {
 $memcache_obj = new Memcache;
 $memcache_obj->connect('localhost', 11211);
 return $memcache_obj->delete($cacheId);
 }

任何TCP服务器都应该设置此选项,避免出现当服务器listen父进程崩溃后但已经存在子进程处理客户连接,在此情况下重启问题。

我的inilisten函数,用于建立listen的sockfd

int initlisten(int port)
{
  int listenfd;
  struct sockaddr_in servaddr;
  if( (listenfd = socket(AF_INET,SOCK_STREAM,0))< 0)
  {
       printf("socket error! %s\n",strerror(errno));
       return -1;
  }
  bzero(&servaddr,sizeof(struct sockaddr_in));
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(port);
  int optval = 1;
  if(-1 == (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))) )
  {
      printf("set REUSEADDR error! %s,%d\n",strerror(errno),port);
  }
  if( -1 == (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))) )
  {
      printf("bind error! %s,%d\n",strerror(errno),port);
      return -1;
  }
  if( -1 == (listen(listenfd,1024)))
  {
      printf("listen error! %s,%d\n",strerror(errno),port);
      return -1;
  }
   return listenfd;
}

利用WNOHANG的waitpid来处理同时多个child终止,但unix只投递一个SIG_CHLD的情况。

当accept慢速系统调用被EINTR中断后应重启。


void sig_chld(int signo){
   pid_t pid;
   int stat;
   while((pid = waitpid(-1,&stat,WNOHANG)) >0 ){
      printf("child %d process terminated.\n",pid);
   }
   return;
}

int main(void){
   ...
   signal(SIGCHLD,sig_chld);
   while(1)
  {
     if((connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&clilen)) < 0)
     {
         if(errno == EINTR)
             continue;
         else
             printf("accept error! %s",strerror(errno));
             return errno;
      }
      printf("get a connection\n");
      if((handlerpid = fork()) == 0)
      {
          handler(connfd);
          exit(0);
      }
      close(connfd);
  }
  ...
}

修改struct ftpuser结构体如下,将原来的connfd直接写到整个结构体中

struct ftpuser{
	char username[20];
	char userpass[20];
	char dataip[20];			//save ip for port mode
	char basedir[MAXLINE];			//basedir of current user
	int dataport;				//save port for port mode
	int loginok;				//whether user login
	int userok;				//user ok but no pass
	int transfor_mode;			//port or pasv
	int write_flag;				//write access control
	int maxspeed;				//speed limit
	int pasv_sockfd;			//pasv sockfd
	pthread_t pasv_accept;			//pasv listen thread id
        int connfd;                         //control sockfd
};
[/code]
pasv函数如下,当执行STOR,RETR,LIST后要关闭pasv_sockfd,特别是LIST用了一个子进程实现数据通信,父进程中也要关闭
[code=c]
void *pasv_listen(void *arg)		//create listen_thread for pasv data port
{
	struct ftpuser *myftp = (struct ftpuser *)arg;
	srand(time(NULL));
	int listenfd = -1;
	while(listenfd == -1)
	{
		myftp->dataport = rand()%45535 + 20000;			//generate a random port
		listenfd = initlisten(myftp->dataport);
	}
	char notifylisten[MAXLINE];
	memset(notifylisten,'\0',MAXLINE);
	snprintf(notifylisten,MAXLINE,"227 Entering Passive Mode (202,117,7,222,%d,%d)\r\n",(myftp->dataport)/256,(myftp->dataport)%256);
	write(myftp->connfd,notifylisten,strlen(notifylisten));
	int connfd = accept(listenfd,NULL,NULL);
	pthread_mutex_lock(&lock);
	myftp->pasv_sockfd = connfd;
	pthread_mutex_unlock(&lock);
	myftp->transfor_mode = 2;
	return;
}

void do_pasv(int connfd,struct ftpuser *myftp)
{
	cleanmode(myftp);
	//char *notsupport="500 passive mode not supported.\r\n";
	if(myftp->pasv_accept != 0)
		pthread_cancel(myftp->pasv_accept);					//ingore the error while the tid is not exist
	pthread_t tid;
	pthread_create(&tid,NULL,pasv_listen,(void *)myftp);
	myftp->pasv_accept = tid;
	return;
}

今天第一次知道这个,以前没用过,就想当然有了,看来碰到问题后才能累计和发现啊~~~

c99开始有了<stdbool.h>可以使用bool,true,false了,但其实只是一种扩展宏定义而已。


#ifndef _STDBOOL_H
#define _STDBOOL_H

#ifndef __cplusplus

#define bool    _Bool
#define true    1
#define false    0

#else /* __cplusplus */
/* Supporting <stdbool.h> in C++ is a GCC extension.  */
#define _Bool    bool
#define bool    bool
#define false    false
#define true    true

#endif /* __cplusplus */

/* Signal that all the definitions are present.  */
#define __bool_true_false_are_defined    1

#endif    /* stdbool.h */

其实这些功能函数实现都没什么技术含量,直接系统调用就好,只要umask设置对就OK。

实现了这些基本的功能,应该该建立一个权限模型的时候了,这个要好好学习,复杂啊…

不然所有的操作都是unix下运行ftpserver进程的用户权限,没意义,只能个人使用了。


void do_mkdir(int connfd,char *dirname)  //mkdir
{
char *mkdok = "257 diretory created ok.\r\n";
char *mkderror = "550 Create directory operation failed.\r\n";
umask(S_IWGRP | S_IWOTH);
if( mkdir(dirname,RWXRWXRWX) == 0 )
write(connfd,mkdok,strlen(mkdok));
else
write(connfd,mkderror,strlen(mkderror));
return;
}

void do_dele(int connfd,char *delname)   //rmdir or delete file
{
char *deleok = "250 Delete operation successful.\r\n";
char *delerror = "550 Delete operation failed.\r\n";
if( remove(delname) == 0)
write(connfd,deleok,strlen(deleok));
else
write(connfd,delerror,strlen(delerror));
return;
}