C 语言 CGI 访问计数器

用 C 语言写的简单的 CGI 访问计数器程序,用文件保存的计数,我觉得比数据库高效一点吧,呵呵。

/* counter.c
 * Heihaier <admin@heiher.info>
 */
 
#include <stdio.h>
#include <stdlib.h>
 
int main(int argc, char * argv[])
{
	unsigned long long int counter = 0;
	FILE * f = NULL;
	char file[256];
 
	printf("Content-type: text/html;\n\n");
 
	char * query = getenv("QUERY_STRING");
	if(NULL == query)
	  goto error;
 
	snprintf(file, 256, "./data/%s", query);
 
	f = fopen(file, "rw+");
	if(NULL == f)
	  goto error;
 
	fread(&counter, sizeof(unsigned long long int), 1, f);
 
	printf("document.write(\"<b>%llu</b>\");", ++counter);
 
	fseek(f, 0, SEEK_SET);
	fwrite(&counter, sizeof(unsigned long long int), 1, f);
 
	fclose(f);
 
	return 0;
error:
	return -1;
}
gcc counter.c -o /var/web/cgi-bin/counter.cgi
mkdir /var/web/cgi-bin/data
touch /var/web/cgi-bin/data/www.heiher.info
chmod 666 /var/web/cgi-bin/data/www.heiher.info

Over!

为博客增加在线访问计数

Whoisonline 免费提供了在线访问的计数功能,赶快加入博客中吧。

在博客的合适位置增加上下面的代码就可以了,注意帮最后的URL修改成自己的。我是放到了页脚中。

<script type="text/javascript" src="http://www.whoisonline.net/?action=tracker&siteurl=http://www.heiher.info"></script></b>

Over

新版 WordPress 评论数字验证

在之前的版本上做了一点修改,使其除了支持 + 运算以外还支持 – 和 x 运算。改动不是很大的,就是做一个记录。

wp-comments-post.php
修改 WordPress 主目录中的 wp-comments-post.php 程序,增加下面的内容。

$comment_author       = ( isset($_POST['author']) )  ? trim(strip_tags($_POST['author'])) : null;
$comment_author_email = ( isset($_POST['email']) )   ? trim($_POST['email']) : null;
$comment_author_url   = ( isset($_POST['url']) )     ? trim($_POST['url']) : null;
$comment_content      = ( isset($_POST['comment']) ) ? trim($_POST['comment']) : null;
// 在其后加入如下代码
/* Comment Math Checker
 * Heihaier - admin@heiher.info 
 */
$comment_numa	= (isset($_POST['numa'])) ? trim($_POST['numa']) : null;
$comment_numb	= (isset($_POST['numb'])) ? trim($_POST['numb']) : null;
$comment_method = (isset($_POST['method'])) ? trim($_POST['method']) : null;
$comment_result	= (isset($_POST['result'])) ? trim($_POST['result']) : null;
if ( get_option('require_name_email') && !$user->ID ) {
	if ( 6 > strlen($comment_author_email) || '' == $comment_author )
		wp_die( __('Error: please fill the required fields (name, email).') );
	elseif ( !is_email($comment_author_email))
		wp_die( __('Error: please enter a valid email address.') );
}
 
// 在其后加入如下代码
/* Comment Math Checker
 * Heihaier - admin@heihaier.org 
 */
if(!$user->ID)		// guest user only
{
	switch($comment_method)
	{
	case 0:	// +
		if((($comment_numa + $comment_numb) != $comment_result) || empty($comment_result))
		  wp_die(__('Error: please type the correct results of the math problems.'));
		break;
	case 1: // -
		if((($comment_numa - $comment_numb) != $comment_result) || empty($comment_result))
		  wp_die(__('Error: please type the correct results of the math problems.'));
		break;
	case 2: // *
		if((($comment_numa * $comment_numb) != $comment_result) || empty($comment_result))
		  wp_die(__('Error: please type the correct results of the math problems.'));
		break;
	}
}

Continue reading 新版 WordPress 评论数字验证

MySQL C 语言应用程序接口开发教程(2)

从数据库中取回数据
在这个实例中我们从表中取回数据。
步骤:

  • 创建连接
  • 执行查询
  • 获取结果集
  • 提取所有可用的记录
  • 释放结果集
#include <my_global.h>
#include <mysql.h>
 
int main(int argc, char * argv[])
{
	MYSQL * conn;
	MYSQL_RES * result;
	MYSQL_ROW row;
	int num_fields;
	int i;
 
	conn = mysql_init(NULL);
	mysql_real_connect(conn, "localhost", "username", "password", "testdb", 0, NULL, 0);
 
	mysql_query(conn, "SELECT * FROM writers");
	result = mysql_store_result(conn);
 
	num_fields = mysql_num_fields(result);
 
	while ((row = mysql_fetch_row(result)))
	{
		for (i=0; i<num_fields; i++)
		  printf("%s ", row[i] ? row[i] : "NULL");
		printf("\n");
	}
 
	mysql_free_result(result);
	mysql_close(conn);
}

实例程序打印 writers 表中所有的记录(姓名)。

./select
Leo Tolstoy
Jack London
Honore de Balzac
Lion Feuchtwanger
Emile Zola
mysql_query(conn, "SELECT * FROM writers");

执行查询将取回表 writers 中所有的记录。

result = mysql_store_result(conn);

取得结果集。

num_fields = mysql_num_fields(result);

获得表中的字段数量。

while ((row = mysql_fetch_row(result)))
{
	for (i=0; i<num_fields; i++)
	  printf("%s ", row[i] ? row[i] : "NULL");
	printf("\n");
}

获取记录并打印到屏幕。

mysql_free_result(result);

释放资源。

字段名称
这个实例里将要打印数据并显示字段名称。
为此我们创建一个新的表 friends。

mysql> CREATE TABLE friends (id int not null primary key auto_increment,
								name varchar(20), age int);
mysql> insert into friends(name, age) values('Tom', 25);
mysql> insert into friends(name, age) values('Elisabeth', 32);
mysql> insert into friends(name, age) values('Jane', 22);
mysql> insert into friends(name, age) values('Luke', 28);

插入一些数据到表中。

#include <my_global.h>
#include <mysql.h>
 
int main(int argc, char * argv[])
{
	MYSQL * conn;
	MYSQL_RES * result;
	MYSQL_ROW row;
	MYSQL_FIELD * field;
 
	int num_fields;
	int i;
 
	conn = mysql_init(NULL);
	mysql_real_connect(conn, "localhost", "username", "password", "testdb", 0, NULL, 0);
 
	mysql_query(conn, "SELECT * FROM friends");
	result = mysql_store_result(conn);
 
	num_fields = mysql_num_fields(result);
 
	while ((row = mysql_fetch_row(result)))
	{
		for(i=0; i<num_fields; i++)
		{
			if (i == 0) {
				while(field = mysql_fetch_field(result)) {
					printf("%s ", field->name);
				}
				printf("\n");
			}
			printf("%s ", row[i] ? row[i] : "NULL");
		}
	}
	printf("\n");
 
	mysql_free_result(result);
	mysql_close(conn);
 
	return 0;
}

这个实例和之前有一点差别,仅仅增加了字段名称。

while(field = mysql_fetch_field(result)) {
	printf("%s ", field->name);
}

mysql_fetch_field() 返回一个 MYSQL_FIELD 结构。我们从这个结构中得到名称(name)。

./header
id name age
1  Tom  25
2  Elisabeth  32
3  Jane  22
4  Luke  28

这是程序输出结果。

插件图片到 MySQL 数据库
一些用户喜欢将图片存入数据库,还有一些用户喜欢将图片存入本地文件系统。图片是二进制数据,MySQL 有专用的数据类型 BLOB(Binary Large Object) 以存储二进制数据。

mysql> describe images;
+-------+------------+------+-----+---------+-------+
| Field | Type       | Null | Key | Default | Extra |
+-------+------------+------+-----+---------+-------+
| id    | int(11)    | NO   | PRI |         |       |
| data  | mediumblob | YES  |     | NULL    |       |
+-------+------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

这个表将在我们的实例中使用,通过下面的 SQL 语句可以创建它。

CREATE TABLE images(id int not null primary key, data mediumblob);
#include <my_global.h>
#include <mysql.h>
 
int main(int argc, char * argv[])
{
	MYSQL * conn;
 
	int len, size;
	char data[1000*1024];
	char chunk[2*1000*1024+1];
	char query[1024*5000];
 
	FILE * fp;
 
	conn = mysql_init(NULL);
	mysql_real_connect(conn, "localhost", "username", "password", "testdb", 0, NULL, 0);
 
	fp = fopen("image.png", "rb");
	size = fread(data, 1, 1024*1000, fp);
 
	mysql_real_escape_string(conn, chunk, data, size);
 
	char *stat = "INSERT INTO images(id, data) VALUES('1', '%s')";
	len = snprintf(query, sizeof(stat)+sizeof(chunk), stat, chunk);
 
	mysql_real_query(conn, query, len);
 
	fclose(fp);
 
	mysql_close(conn);
 
	return 0;
}

在这个实例中,我们插入一张图片到表 images 中,图片最大可以是 1MB。

fp = fopen("images.png", "rb");
size = fread(data, 1, 1024*1000, fp);

这里打开图片并读入数据集。

mysql_real_escape_string(conn, chunk, data, size);

二进制数据可以包含特殊字符,为了在 SQL 语句中不造成麻烦。我们必需避开它们。 mysql_real_escape_string() 函数将编码后的数据放入集合 chunk。这样,它们就可以是合法的语句了。这个函数还会在结尾增加一个 NULL 字符,这也是为什么集合 chunk 是集合 data两倍多一个字节。

char * stat = "INSERT INTO images(id, data) VALUES('1', '%s')";
len = snpritnf(query, sizeof(stat)+sizeof(chunk), stat, chunk);

这两行代码准备查询语句。

mysql_real_query(conn, query, len);

最后,我们执行语句。

从 MySQL 数据库中有选择的取出图片
在上一个实例中,我们在数据库中插入了图片。在本实例中,我们将有选择的取出这些插入的图片。

#include <my_global.h>
#include <mysql.h>
 
int main(int argc, char * argv[])
{
	MYSQL * conn;
	MYSQL_RES * result;
	MYSQL_ROW row;
 
	unsigned long * lengths;
	FILE * fp;
 
	conn = msyql_init(NULL);
	mysql_real_connect(conn, "localhost", "username", "password", "testdb", 0, NULL, 0);
 
	fp = fopen("image.png", "wb");
 
	mysql_query(conn, "SELECT data FROM images WHERE id=1");
	result = mysql_store_result(conn);
 
	row = mysql_fetch_row(result);
	lengths = mysql_fetch_lengths(result);
 
	fwrite(row[0], lengths[0], 1, fp);
	mysql_free_result(result);
 
	fclose(fp);
	mysql_close(conn);
 
	return 0;
}

这个实例中我们将数据库中 ID 为 1 的图片创建为文件 image.png

fp = fopen("image.png", "wb");

以可写的方式打开一个文件。

mysql_query(conn, "SELECT data FROM images WHERE id=1");

选择 ID 为 1 的图片。

row = mysql_fetch_row(result);

row 包含了原始数据。

lengths = mysql_fetch_lengths(result);

获取图片长度。

fwrite(row[0], lengths[0], 1, fp);

使用标准函数 fwrite() 将数据写入文件。

Over!

MySQL C 语言应用程序接口开发教程(1)

关于教程
这是一篇 MySQL 数据库的 C 语言开发教程。它讲述了用 C 语言开发 MySQL 应用程序的基本过程。

关于 MySQL 数据库
MySQL 是一个重要的开放源代码的、多用户的、多线程的数据库管理系统。MySQL 在 Web 应用中很流行。它是非常流行的 LAMP(Linux, Apache, MySQL, PHP) 架构的一部分。MySQL 被瑞典一家名为 MySQL AB 并对开源事业有杰出贡献的公司所有。MySQL 数据库可以运行在很多的操作系统上,如 BSD, Unix, Linux Windows 和 Mac。维基百科和 Youtube 都在使用 MySQL。这些站点一天有数百万将的查询。MySQL 有两个版本,分别是 MySQL 服务器系统和 MySQL 嵌入式系统。

第一个实例
我们第一个实例将要测试调用一个 MySQL 函数。

#include <my_global.h>
#include <mysql.h>
 
int main(int argc, char * argv[])
{
	printf("MySQL client version: %s\n", mysql_get_client_info());
	return 0;
}

函数 mysql_get_client_info()返回 MySQL 客户端版本信息。

gcc version.c -o version `mysql_config --cflags --libs`
./version

这是是如何编译和运行这个实例。

MySQL client version: 5.0.38
#include <my_global.h>
#include <mysql.h>

我们包含两个需要的头文件: mysql.h MySQL 函数调用的最重要的头文件。my_global.h 包含了一些全局的声明和标准输入/输出头文件。

printf("MySQL client version: %s\n", mysql_get_client_info());

这行代码输入了 MySQL 客户端的版本信息。我们调用了 mysql_get_client_info()函数。

创建一个数据库
在第二个实例中创建一个数据库。

#include <my_global.h>
#include <mysql.h>
 
int main(int argc, char * argv[])
{
	MYSQL * conn;
 
	conn = mysql_init(NULL);
 
	if (conn == NULL) {
		printf("Error %u: %s\n", mysql_errno(conn), mysql_error(conn));
		exit(1);
	}
 
	if (mysql_real_connect(conn, "localhost", "username", "password", NULL, 0, NULL, 0) == NULL) {
		printf("Error %u: %s\n", mysql_errno(conn), mysql_error(conn));
		exit(1);
	}
 
	if (mysql_query(conn, "CREATE DATABASE testdb")) {
		printf("Error %u: %s\n", mysql_errno(conn), mysql_error(conn));
		exit(1);
	}
 
	mysql_close(conn);
 
	return 0;
}

这段代码连接了 MySQL 数据库系统并创建了一个名为 testdb 的数据库。

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| testdb             |
+--------------------+
3 rows in set (0.00 sec)

检验的确创建了一个新的库。

在这段实例中我们包含了错误检测,检查可能出现的错误是非常重要的。在数据库开发领域里,很多地方都可能出错。为了有清晰的代码之后的实例中将不包含错误检查,我们假设所有人清楚错误检测是开发人员的责任

实例分为如下的部分:

  • 初始化一个连接对象结构
  • 创建一个连接
  • 执行一个查询
  • 关闭连接
MYSQL * conn;

我们声明一个 MYSQL 结构指针,这个结构是连接对象。

conn = mysql_init(NULL);

mysql_init() 函数返回一个连接对象。

if (conn == NULL) {
	printf("Error %u: %s\n", mysql_errno(conn), mysql_error(conn));
	exit(1);
}

我们检查返回值,如果 mysql_init() 调用失败打印错误消息并中止应用程序。

if (mysql_real_connect(conn, "localhost", "username", "password", NULL, 0, NULL, 0) == NULL) {
	printf("Error %u: %s\n", mysql_errno(conn), mysql_error(conn));
	exit(1);
}

mysql_real_connect() 函数建立了一个到数据库的连接。我们提供连接对象、主机名、用户名和密码参数。其它四个参数分别是数据库名、端口、Unix 套接字和客户端标记。

if (mysql_query(conn, "CREATE DATABASE testdb")) {
	printf("Error %u: %s\n", mysql_errno(conn), mysql_error(conn));
	exit(1);
}

mysql_query() 执行一个 SQL 语句。这里创建一个新的库。

mysql_close(conn);

最后,我们关闭数据库连接。

创建和操作表
第三个实例将要创建一个表并插入一些数据。

#include <my_global.h>
#include <mysql.h>
 
int main(int argc, char * argv[])
{
	MYSQL * conn;
 
	conn = mysql_init(NULL);
	mysql_real_connect(conn, "localhsot", "username", "password", "testdb", 0, NULL, 0);
 
	mysql_query(conn, "CREATE TABLE writers(name VARCHAR(25))");
 
	mysql_query(conn, "INSERT INTO writers VALUES('Leo Tolstoy')");
	mysql_query(conn, "INSERT INTO writers VALUES('Jack London')");
	mysql_query(conn, "INSERT INTO writers VALUES('Honore de Balzac')");
	mysql_query(conn, "INSERT INTO writers VALUES('Lion Feuchtwanger')");
	mysql_query(conn, "INSERT INTO writers VALUES('Emile Zole')");
 
	mysql_close(conn);
 
	return 0;
}

我们不使用任何新的 MySQL 函数。使用 mysql_query() 函数去创建表和插入数据。

mysql_real_connect(conn, "localhost", "username", "password", "testdb", 0, NULL, 0);

我们连接数据库 testdb,用户名 username 密码 password。

mysql_query(conn, "CREATE TABLE writers(name VARCHAR(25))");

这里我们创建一个名为 writers 的表,它有一个 varchar 类型的字段。

mysql_query(conn, "INSERT INTO writers VALUES('Leo Tolstoy')");

我们在 writers 表里插入一个记录。

mysql> show tables;
+------------------+
| Tables_in_testdb |
+------------------+
| writers          |
+------------------+
1 row in set (0.00 sec)

显示数据库里的表。

mysql> select * from writers;
+-------------------+
| name              |
+-------------------+
| Leo Tolstoy       |
| Jack London       |
| Honore de Balzac  |
| Lion Feuchtwanger |
| Emile Zola        |
+-------------------+
5 rows in set (0.00 sec)

枚举表中的记录。

Over!

下载指定格式的 Youtube 视频

复制视频的地址
当你需要下载一个视频时,先在浏览器的地址栏中找到这个视频的地址。如 http://www.youtube.com/watch?v=sw9EbA4KnlI

获取下载地址
方法1:接着打开 http://keepvid.com/ 在这个网站的 URL 输入栏中粘贴上之前复制的视频地址,再点击 Download 按钮,输入正确的验证码并点击 Submit
方法2:接着打开 http://vixy.net/ 在这个网站中的 URL 输入栏中粘贴上之前复制的视频地址,再选择一个合适的格式,点击 Start 后等待转换,一会就可以点击 Download 下载了。

下载MP4视频文件
完成上述的操作后,将会得到不同画质的视频文件的下载链接,选择你所需要的,点击 Download 即可。

Over!

防止 SSH 端口转发穿透防火墙

网络上很多文章在描述如何利用 SSH 的端口转发功能穿透防火墙,访问防火墙后面的服务。这里我们说说如何防止这样的行为。

通过 sshd_config
这种方法只适用于已知需要转发的端口并且数量不大,例如只让用户能够转发 127.0.0.1:8000 和 127.0.0.1:9000。在 /etc/ssh/sshd_config 中增加

PermitOpen 127.0.0.1:8000
PermitOpen 127.0.0.1:9000

通过 iptables
这种方法适用于只限制某基些主机端口对,例如做代理服务时希望禁止用户(fwd 组)转发 127.0.0.1 的所有端口,其它的都能转发。

sudo iptables -I OUTPUT -m owner --gid-owner fwd -o lo -j DROP

这里是禁止了 fwd 组用户的数据包从 lo 接口外出,iptables 可以 match 出用户(–uid-owner)、组(–gid-owner)、进程(–pid-owner)、命令(–cmd-owner),具体看 man iptables。

Over!

OpenSSH 端口转发原理

OpenSSH 通过其加密的数据通道可以创建三种类型的端口转发,分别是本地(Local)、远程(Remote)和动态(Dynamic)。那么这三种类型的端口转发具体是如何工作的呢?下面就分别说一下,只是说一下大致的工作过程,不是详细的实现。

本地(Local)

ssh -N -L bind_address:bind_port:host_name:host_port username@server_address

当上面的命令被执行后,运行在客户端的 ssh 进程先连接运行在服务器端(server_address) sshd 进程,并进行身份验证。如果验证成功了,ssh 和 sshd 之间会建立一个 TCP/IP 连接,用于传输数据,这个连接上面传输的数据是加密的。

接着 ssh 会在 bind_address:bind_port 上创建一个 TCP/IP 协议的 socket 并进行侦听,当收到数据后直接通过之前建立的连接传输给服务器上的 sshd 进程, sshd 收到数据后会新建一个 socket 连接 host_name:host_port 并向其发送之前收到的数据。

相反,当 sshd 收到数据后也会按类似的方式通道加密通道传输给 ssh 进程。这样通过加密通道的本地端口转发就工作起来了。

远程(Remote)

ssh -N -R bind_address:bind_port:host_name:host_port username@server_address

远程类型的端口转发和本地类型的原来是一样的,只是侦听的端口刚好相反。这种类型的端口转发是 sshd 进程创建一个 TCP/IP 协议的 socket 在 bind_address:bind_port 上侦听,而 ssh 则是在第一次收到数据后创建 socket 连接 host_name:host_port。

动态(Dynamic)

ssh -N -D bind_address:bind_port username@server_address

动态类型的转发是 ssh 创建一个 socks v5 的服务并在 bind_address:bind_port 上侦听,当收到数据后,解析出需要连接的主机和端口并通道加密通道发送给 sshd,sshd 转发数据后并返回结果数据。

特别的地方就在被连接的主机是通过 socks v5 进行动态确定的。这样的端口转发功能用作加密代理是不错的选择。

Over!

Windows 系统中使用 SSH 代理

现在网络环境很复杂,可能你不得不使用代理服务器来解决问题。今天说说如何在 Windows 系统中使用 SSH 代理。

软件需求

  1. Firefox
  2. PLink

开启代理服务
我们假设 plink.exe 在 D:\ 中,在命令行(cmd.exe)中执行下面的指令

D:\>plink -N -D port_number username@server_address

port_number 是你准备使用的代理服务的端口,如 6000
username 是你准备连接的 SSH 服务器的用户名
server_address 是你准备连接的 SSH 服务器的地址
命令执行后输入密码就可以了,不要关闭进程。

设置 Firefox
1. 在地址栏里输入 about:config
2. 设置 network.proxy.socks_remote_dns = True
3. 选择菜单中 [编辑]->[选项]
4. 转到 [高级]
5. 转到 [网络],点击 [设置]
6. 选择 [手动设置代理]
7. 在 SOCKS 主机里输入 localhost 端口输入上面 plink 参数中端口,如 6000
8. 选择 SOCKS v5
9. 确定保存

SSH 的代理类型就是 socks v5,支持这种代理服务的应用程序都可以使用。

Over!