简单的(学前班)图片验证码识别

最近遇到了一个验证码识别的问题,不过经过观察该验证码相当的简陋,且看下面几个验证码图片的例子:

事实上,上面我已经把所有可能的字符都列出来了,包含0~9A-Z共36个字符。这16张图片都有下面几个共同特征:

  1. 背景色都是白色或者是纯色
  2. 前景色都不是白色
  3. 文字都很规则,没有做过扭曲处理
  4. 图片大小都是40×10个像素
  5. 每张图片上都只有4个字符
  6. 结合特征4和特征5来看,每个字符都是占据了10×10个像素
  7. 没有噪音

就上面这些特征决定了,我们要识别这些验证非常的简单,识别率绝对是100%。这种验证码就是纯同虚设。
下面我们就用简单的代码来识别它。
首先采集36个字符的图像特征到一个数据库(广义的,我们这里就用一个简单的Map来存储):
首先我们把上面包含了所有36个字符的16个图片存放在同一个目录下,并将其文件名定为字符上的文字,比如第一张图片就叫“0JQT.bmp”。
接着我们写一个小程序来将所有字符的编码采集下来并保存成一个映射文件,采集特征信息的大体过程是先把一张40×10的图片分割成4张10×10的图片(因为一个10×10上就是一个字符),然后分别扫描每个10×10的图片,记录每个像素的特征,假如这个像素是白色(背景是白色,经过代码计算发现这个背景并不是那么的白,而是255,250,250,所以你看到代码中判断是否是白色用的是 >= 250 而不是 255)那么记录为1,否则记录为0,然后把10×10=100个像素的0、1标识拼接成一个字符串,这个字符串就代表一个字符了。

/**
* Created on 2007-11-18 下午03:33:28
*/
import java.awt.Image;
import java.awt.image.PixelGrabber;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.imageio.ImageIO;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* @author sutra
*
*/
public class Gather {
private static final Log log = LogFactory.getLog(Gather.class);

private static int handleSinglePixel(int x, int y, int pixel) {
// int alpha = (pixel >> 24) & 0xff;
int red = (pixel >> 16) & 0xff;
int green = (pixel >> 8) & 0xff;
int blue = (pixel) & 0xff;
// Deal with the pixel as necessary…
log.debug(x + "," + y + ":" + red + "," + green + "," + blue);
int white = 0;
if (red >= 250 && green >= 250 && blue >= 250) {
white = 1;
}
// System.out.println(String.format("%1$s,%2$s:%3$s", x, y, w));
return white;
}

public static String[] gather(Image src) throws InterruptedException {
int width = src.getWidth(null); // 得到源图宽
int height = src.getHeight(null); // 得到源图长
log.debug("width: " + width);
log.debug("height: " + height);
int pixels[] = new int[width * height];
PixelGrabber pg = new PixelGrabber(src, 0, 0, width, height, pixels, 0,
width);
pg.grabPixels();

String[] ret = new String[4];

for (int x = 0; x < 40; x += 10) {
int y = 0;
StringBuilder sb = new StringBuilder();
for (int j = 0; j < height; j++) {
for (int i = x; i < x + 10; i++) {
int w = handleSinglePixel(x + i, y + j, pixels[j * width
+ i]);
sb.append(w);
}
}
log.debug(x + ":" + sb.toString());
ret[x / 10] = sb.toString();
}

return ret;
}

/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
File bmpDir = new File("src/main/bmp/");
File[] bmps = bmpDir.listFiles(new FilenameFilter() {

public boolean accept(File dir, String name) {
return "bmp".equalsIgnoreCase(FilenameUtils.getExtension(name));
}

});
Map codes = new HashMap();
for (File bmp : bmps) {
log.debug("bmp: " + bmp);
Image src = ImageIO.read(bmp); // 构造Image对象
String filename = bmp.getName();
String[] charCodes = gather(src);
for (int i = 0; i < 4; i++) {
char ch = filename.charAt(i);
String code = charCodes[i];
String old;
if ((old = codes.get(ch)) != null) {
if (!old.equals(code)) {
throw new Exception("如果发生这样的异常,说明我们的假设有问题。");
} else {
log.debug("old equals new");
}
} else {
codes.put(ch, code);
}
}
}

char[] allChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
Properties codesDb = new Properties();
for (char ch : allChars) {
log.debug("ch: " + ch);
String code = codes.get(ch);
if (code == null) {
// 做点检查,如果缺少的话,你需要去收集更多的图片
throw new Exception("缺少 " + ch);
}
codesDb.put(new String(new char[] { ch }), codes.get(ch));
}
codesDb.list(System.out);
codesDb.store(new FileOutputStream("codes.db"), "codes");
}
}

这样我们就得到这样一个映射表:

0:1110000111110111101111011110111101001011110100101111010010111101001011110111101111011110111110000111
1:1111011111110001111111110111111111011111111101111111110111111111011111111101111111110111111100000111
2:1110000111110111101111011110111111111011111111011111111011111111011111111011111111011110111100000011
3:1110000111110111101111011110111111110111111100111111111101111111111011110111101111011110111110000111
4:1111101111111110111111110011111110101111110110111111011011111100000011111110111111111011111111000011
5:1100000011110111111111011111111101000111110011101111111110111111111011110111101111011110111110000111
6:1111000111111011101111011111111101111111110100011111001110111101111011110111101111011110111110000111
7:1100000011110111011111011101111111101111111110111111110111111111011111111101111111110111111111011111
8:1110000111110111101111011110111101111011111000011111101101111101111011110111101111011110111110000111
9:1110001111110111011111011110111101111011110111001111100010111111111011111111101111011101111110001111
A:1111011111111101111111101011111110101111111010111111101011111100000111110111011111011101111000100011
B:1000000111110111101111011110111101110111110000111111011101111101111011110111101111011110111000000111
C:1110000011110111101110111110111011111111101111111110111111111011111111101111101111011101111110001111
D:1000001111110111011111011110111101111011110111101111011110111101111011110111101111011101111000001111
E:1000000111110111101111011011111101101111110000111111011011111101101111110111111111011110111000000111
F:1000000111110111101111011011111101101111110000111111011011111101101111110111111111011111111000111111
G:1110000111110111011110111101111011111111101111111110111111111011100011101111011111011101111110001111
H:1000100011110111011111011101111101110111110000011111011101111101110111110111011111011101111000100011
I:1100000111111101111111110111111111011111111101111111110111111111011111111101111111110111111100000111
J:1110000011111110111111111011111111101111111110111111111011111111101111111110111110111011111000011111
K:1000100011110111011111011011111101011111110001111111010111111101101111110110111111011101111000100011
L:1000111111110111111111011111111101111111110111111111011111111101111111110111111111011110111000000011
M:1000100011110010011111001001111100100111110101011111010101111101010111110101011111010101111001010011
N:1000100011110011011111001101111101010111110101011111010101111101100111110110011111011001111000110111
O:1110001111110111011110111110111011111011101111101110111110111011111011101111101111011101111110001111
P:1000000111110111101111011110111101111011110000011111011111111101111111110111111111011111111000111111
Q:1110001111110111011110111110111011111011101111101110111110111011111011101001101111011001111110001011
R:1000001111110111011111011101111101110111110000111111010111111101101111110110111111011101111000110011
S:1110000011110111101111011110111101111111111001111111111001111111111011110111101111011110111100000111
T:1000000011101101101111110111111111011111111101111111110111111111011111111101111111110111111110001111
U:1000100011110111011111011101111101110111110111011111011101111101110111110111011111011101111110001111
V:1000100011110111011111011101111101110111111010111111101011111110101111111010111111110111111111011111
W:1001010011110101011111010101111101010111110101011111001001111110101111111010111111101011111110101111
X:1000100011110111011111101011111110101111111101111111110111111110101111111010111111011101111000100011
Y:1000100011110111011111011101111110101111111010111111110111111111011111111101111111110111111110001111
Z:1100000011110111011111111101111111101111111110111111110111111111011111111011111111101110111100000011

然后写一个根据这个保存好的映射文件来识别图片的 ImageParser 吧,也很简单:

/**
* Created on 2007-11-18 下午04:59:12
*/
import java.awt.Image;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* @author sutra
*
*/
public class ImageParser {
@SuppressWarnings("unused")
private static final Log log = LogFactory.getLog(ImageParser.class);

private static class SingletonHolder {
public static final ImageParser instance;
static {
try {
instance = new ImageParser();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

private Map codes;

/**
* @throws IOException
*
*/
private ImageParser() throws IOException {
codes = new HashMap(36);
Properties p = new Properties();
p.load(ImageParser.class.getResourceAsStream("/code.db"));
Enumeration e = p.keys();
while (e.hasMoreElements()) {
String n = (String) e.nextElement();
String v = p.getProperty(n);
codes.put(v, n);
}
}

public static ImageParser getInstance() {
return SingletonHolder.instance;
}

public String parse(Image src) throws InterruptedException {
String[] codes = Gather.gather(src);
StringBuilder sb = new StringBuilder();
for (String s : codes) {
sb.append(this.codes.get(s));
}
return sb.toString();
}
}

最后就是如何来调用这个 ImageParser ,看它的一个单元测试就明白了:

/**
* Created on 2007-11-22 下午11:18:17
*/
import static org.junit.Assert.assertEquals;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;

import javax.imageio.ImageIO;

import org.apache.commons.io.FilenameUtils;
import org.junit.Test;

/**
* @author sutra
*
*/
public class ImageParserTest {

/**
* {@link ImageParser#parse(java.awt.Image)} 的测试方法。
*
* @throws IOException
* @throws InterruptedException
*/
@Test
public void testParse() throws InterruptedException, IOException {
File bmpDir = new File("src/main/bmp/");
File[] bmps = bmpDir.listFiles(new FilenameFilter() {

public boolean accept(File dir, String name) {
return "bmp".equalsIgnoreCase(FilenameUtils.getExtension(name));
}

});
for (File bmp : bmps) {
assertEquals(FilenameUtils.getBaseName(bmp.getName()), ImageParser
.getInstance().parse(ImageIO.read(bmp)));
}
}

}

这样简单的验证码还存在吗?存在的,确实存在的,有些程序员根本就没有理解验证码的目的,也许他只不过是看人家弄一个他也弄一个。这样的程序员存在吗?存在的,确实存在的,而且必将永远存在下去。
或者他弄个简单的验证码的目的就是为了让某些人去识别。

Advertisements

在 FreeBSD 7.0 上运行 Qemu,并通过 tap 联网

原文: Qemu with tap networking on FreeBSD Current(译者注:原文标题为在 FreeBSD CURRENT 版本上运行 Qemu,这里翻译成 FreeBSD 7.0 的原因是当时的 CURRENT 即目前的 7.0,而目前描述 CURRENT 已是 8.0-CURRENT)
作者: scottro
翻译: Sutra Zhou

根据大家的回复,我做了一些修改。
————————————–
修改记录
2007年1月17日

修改 qemu-ifup 脚本,0.0.0.0 地址是不必要的。
补充了一些资料,关于 bridge0 接口的 IP 地址。
修正一个拼写错误,我把 guest 操作系统的 IP 和 host 的IP 写成一样的了。(对不起朋友们)
改了一些编辑风格。
添加了介绍 qemu 网络的页面的链接。

2007年1月19日

对操作 ifconfig bridge0 addm tap0 进行了更详细的补充

2007年1月21日

添加启动和关闭脚本(来自kludgy)
给 tap 接口获取权限添加更好的解释

2007年1月27日

对卸载模块做了一些补充

2007年1月29日

对卸载模块做了更多补充

2007年2月3日

对 slirp 又开始工作作了补充描述

2007年4月9日

关于本指南所介绍的方法在当前的 CURRENT 版本(译者注:FreeBSD CURRENT 版本)上不工作了注解,然后晚上又添加了如何解决这个问题的办法(并去掉顶部的注意事项,因为那个已经没有用了)

2007年4月10日

添加关于 net.link.tap.up_on_open 的信息

2007年6月1日

添加一个在 freebsd-emulation 上的关于桥接的讨论。

2007年6月2日

修正桥接的配置–删除一个可能坏的设备,在指定了网桥的 IP 地址后–非常感谢 Per Hedeland。也在 qemu-ifup 脚本中添加了 exit 0,再次感谢 Per。最后,让 qemu-ifup 脚本更智能,再次地,感谢 Per。

————————————-

假设你已经阅读了vermaden‘s excellent qemu howto ,并已经安装了客户机操作系统。默认情况下,qemu 将采用我们称为用户模式的网络启动──客户机操作系统将自动指定 IP 地址为 10.0.2.x ,这样 pinging 就不会工作,而大部分其它工作可以进行──你可以浏览互联网,通过 ssh 从客户机连接到宿主机,等等。

Qemu 的网络有多种形式。使用哪种方式由你的需要决定。我一直使用着默认的用户模式直到它在 CURRENT (译者注:CURRENT 指 FreeBSD CURRENT,这里指 FreeBSD 7.0-CURRENT,下同)上不能工作。要浏览各种不同的方式,你可以随时查看非 qemu 官方的维基页面网络配置页

截至2月上旬,最新的 qemu 再次工作在默认的用户模式网络上(更恰当地应称为slirp)。但是,一旦你设置了 tap,它可能变得更便利。客户机可以简单地成为你网络上的另外一个节点。

Tap 网络方式增加了许多新的特性。Pingging 会工作,VPN网络看起来工作得更好(至少在 Windows 客户机上可以使用 Nortel VPN 客户端)。

这个指南只适用于 FreeBSD-7。设置 tap 网络的方法已经改变。如果你是 FreeBSD-6.x,则请看 acidos 网站上的文章

我要给我最诚挚的谢意给 Bakul Shah,他的帮助省去了我大量的 googling (译者注:用 Google 搜索)和错误的开始。所有的错误,当然,我的。Bakul 的设置要远远比我的准确。

这里是非常简单的设置。我们打算通过手动配置 IP 地址和网关在宿主机的同一个子网里拥有一个 qemu 客户机。不管怎样,我需要这样的配置。

好了,假设你已经有一个能够启动起来的 qemu 客户机,首先我们有几件事要做。

我们必需允许一个普通用户使用 tap 网络。我们称这个用户为 John,用户名为 john。我假设各位都能熟练使用 sudo。用户 john 已经在 sudoers 文件中配置好了。大量的准备工作将不得不由 root 用户来做或者需要 root 权限来做。

在 /etc/devfs.conf 添加下面一行

own tap0 john:john

重启 devfs

/etc/rc.d/devfs restart

如果这些模块尚未加载,加载它们。

kldload kqemu aio if_tap if_bridge

使用户能够打开 tap 设备

sysctl net.link.tap.user_open=1

(你将看到屏幕打印一条消息: net.link.tap.user_open is changed from 0 to 1.)
创建一个网桥。

ifconfig bridge0 create

添加物理网络接口到网桥。在我这里,我的网卡是廉价的板载的网卡 vr0 。根据你的网卡自己修改。

ifconfig bridge0 addm vr0 up

在相当长的时间里,该指南在上面这行的up后面给网桥指定了一个 IP 地址。Per Hedeland 指出了这个错误。
如果你还没有启动 qemu,就不会有 tap0 网络接口,如果你之前有一个 qemu 会话然后停止了它,那么你现在可以添加 tap0 网络接口。

ifconfig bridge0 addm tap0

如果这是你机器启动后第一次启动 qemu,或者如果当你最后一次关闭 qemu 时,你已经卸载了 tap 模块,等等,你就会得到一个 tap0 不存在的错误信息。那么,你就不得不等到你启动了 qemu 后再把它加为网桥的成员。(这将在下面详述)
如果你想在机器启动时创建网桥(通过添加一些配置到 /etc/rc.conf ──这将在下面详述)你不必启用你的网卡。而是,简单地使用 addm 而不要在行末添加 up。

关于这个,在 freebsd-emulation 的邮件列表 里,我和 Bakul,Per Hedeland 之间有一个有趣的讨论(查找 2007年6月3日的那一周)。Per 指出我在原来的指南中的错误。

回到我们的话题。

创建一个 /etc/qemu-ifup 脚本。内容如下:

#!/bin/sh
sudo /sbin/ifconfig ${1} up

让这个脚本可以执行

chmod 755 /etc/qemu-ifup

有趣的是,在2007年4月上旬之前,并不需要在 ifconfig 的行末添加 “up” 。我以为 tap 网络不能用了,但是仅仅需要在 ifconfig 这行上添加一个“up”就又能正常了。非常感谢 Tobias Grosser,是他指出了这个解决方法。

你可以修改 sysctl 变量而不是添加在 ifconfig 这行后面添加“up”。这个变量是

net.link.tap.up_on_open

一旦你通过 kldload if_tap 加载了 if_tap 模块。这个变量会默认设置为 0.把它修改成 1

net.link.tap.up_on_open=1

也会工作。你可以只设置其中一个或者另一个。重要的一点是 if_tap 在创建后不再自动启用。非常感谢Bruce M. Simpson,是他告诉了我关于 sysctl 变量这个事情。(在我在 current@FreeBSD.org 和 emulation@FreeBSD.org 上询问为什么 if_tap 不工作时,他和 Tobias 回复了我的问题)
启动 qemu──在这个例子里,我们的 qemu 映像是和 Per vermaden 的文章中一样的 ~/qemu/win2k.img。

决定给客户机什么样的网络地址。在这个例子中,在我的 192.168.1.x 网络中,我们将给客户机一个 192.168.1.40 的地址。这个网络的网关(你也可以通过 netstat -r 来检查)是192.168.1.1。

vermaden 的指南给出了在启动 qemu 时的更多的选项,比如使用 -localtime,调整内存,声音等等。在这里,我们将不会理会这些,我们只是测试网络。

qemu -net nic -net tap -hda ~/qemu/win2k.img

有些时候,我们会得到一个错误信息说 tap9 无法被打开。如果发生了这个,表明忘记去修改 sysctl net.link.tap.user_open 变量为 1 或者 devfs.conf 需要重启。(当然也需要加载 if_tap 模块,才能使在 devfs.conf 中添加的那行生效)

除非你已经在 sudoers 中给 qemu-ifup 脚本添加了 NOPASSWD 选项,那么系统将询问你的密码──记住,qemu-ifup 脚本使用了 sudo。

在客户机系统启动后,tap0 网络接口就会被启动(通过 qemu-ifup 脚本)。这样,现在添加 tap0 到网桥。

ifconfig bridge0 addm tap0

至于 arun 提到的,可以将 bridge0 addm tap0 添加到 qemu-ifup 脚本中。(${1} 在他的例子中是 tap0 网络接口。)如果你倾向于频繁地停止和启动 qemu,并且不想每次都卸载 if_tap 模块,这就会引起一个问题,你将由于 tap0 网络接口已经是网桥的成员而得到一个错误。虽然,这个错误信息可以被忽略,但是(感谢 Per 指出这点)你不得不像我下面提出的那样添加 exit 0。否则,你将得到一个错误提示说 tap 已经存在,或者已经是一个成员,并且脚本执行失败。

我将在启动脚本这节涵盖这一步。如果你总是能在完成 qemu 会话后记得卸载 if_tap 模块,或者你对那些错误提示并不恼火的话,那些修改 qemu-ifup 脚本为:

#!/bin/sh
sudo /sbin/ifconfig ${1} up
sudo /sbin/ifconfig bridge0 addm ${1}
exit 0

你也可以把脚本变得更智能,(再次感谢 Per Hedeland)检查 tap0 是否已经是一个成员,并且这将消除错误信息。

#!/bin/sh

sudo /sbin/ifconfig $1 up
case "`/sbin/ifconfig bridge0`" in
*" $1 "*) ;; # already in the bridge
*) sudo /sbin/ifconfig bridge0 addm $1 ;;
esac
exit 0

这可以避免 ifconfig bridge0 addm tap0 作为一个单独的命令执行。(注意,如果你是使用 net.link.tap.up_on_open 的方式来保证 tap0 启用的话,你不需要添加 “up”这个词在 ifconfig ${0} 的行末。)

调整客户机操作系统的网络。但愿,你知道如何去做。正像上面所述的 IP 地址为 192.168.1.40。子网掩码,DNS 服务器和网关都和宿主机的设置一样。

再检查一下,宿主机的设置如下
IP地址 192.168.1.50
子网掩码 255.255.255.0
默认网关 192.168.1.1
DNS 服务器 192.168.1.80

然后,我们给客户机操作系统的设置如下:
IP 地址 192.168.1.40
子网掩码 255.255.255.0
默认网关 192.168.1.1
DNS 服务器 192.168.1.80

换句话说,它就和局域网上其它的物理主机一样。

如果你需要保存这些设置的话,我们可以修改一些配置文件。

对于 sysctl 变量添加下面一行到 /etc/sysctl.conf

net.link.tap.user_open=1

网桥可以在 /etc/rc.conf 中设置。再次地,我这里使用的是 vr0 网卡,添加下面这些到 /etc/rc.conf

cloned_interfaces="bridge0"
ifconfig_bridge0="addm vr0"

注意,我们在这里只需要添加物理网络接口,而不是 tap0。

添加上面这些到 /etc/rc.conf 也会在启动时加载 if_bridge 模块。要在启动时加载 kqemu 和 aio 添加这些

kqemu_enable="YES"

到 /etc/rc.conf。

要在启动时加载 if_tap 模块,添加

if_tap_load="YES"

到 /boot/loader.conf

我不是经常运行 qemu,所以对于我,使用一个简单的脚本比较方便。我这里有点复杂,例如,在加载模块前需要检查是否已经加载了。

我使用了两个脚本,qemuprep.sh 和 qemudown.sh

qemuprep.sh 内容如下

#!/bin/sh

#load modules if not loaded.
sudo kldload if_tap if_bridge kqemu aio

#create the bridge and configure it.

sudo ifconfig bridge0 create

sudo ifconfig bridge0 addm vr0 up

#fix sysctl
sudo sysctl net.link.tap.user_open=1

#restart devfs to work with tap
sudo /etc/rc.d/devfs restart
exit 0

注意,如果模块已经加载,虽然你会得到一些错误信息,但是不会中断任何东西。
例如,一旦加载了 aio 模块,即使 qemu 已经停止了,再尝试去卸载它也会得到一个错误信息:

kldunload: can’t unload file: Operation not supported

因此,如果我在重启机器前运行 qemu 多次,当我执行 qemuprep.sh 脚本时,将会得到一个错误提示说 aio 模块已经加载了。它并无大碍。如果你觉得它碍眼,你可以执行:

kldstat | grep -q aio
if [ "$?" -gt 0 ]; then
sudo kldload if_tap if_bridge kqemu aio

#if it’s already loaded, then just load the others

else

sudo kldload if_tap if_bridge kqemu

来替代 sudo kldload if_tap if_bridge kqemu aio 那一行。

我把宿主机的网络接口添加为成员。(如果你一直没有想通,通过 addm 选项来添加成员到网络接口。可以看 man(8) ifconfig。)

由于我的关闭脚本卸载了 if_tap 模块,devfs.conf 将不得不被重新读取。因此,我在脚本的最后重启 devfs 。你可以在其最后添加下面一行,

qemu -net nic -net tap -hda ~/qemu/"${1}" -localtime

但是由于我大部分时候需要使用 CD 启动,我没有添加这行。如果你只用一个 qemu 的 .img 文件,并且总是使用这个映像来启动,你可以使用你的映像文件来替换其中的 “${1}” 。(我也可以做其它的修改,比如准备一个列表,然后使用时选择一个,或者选择从CD启动)。

当我结束使用 qemu 是,我想一处模块和网桥。当我关闭 qemu 客户机会话是,我运行 qemudown.sh

#bring down the bridge
sudo ifconfig bridge0 destroy

#unload the unused modules.

sudo kldunload kqemu
sleep 1
sudo kldunload if_tap
sleep 1
sudo kldunload if_bridge

exit 0

注意,我没有卸载 aio,因为它不会被卸载。我也发现卸载 if_tap 模块会使 devfs.conf 里增加的那行无效,所以没有必要删除 devfs.conf 里的那一行。

我在卸载 tap 和 bridge 模块前 sleep 1 的原因是可能只是在我的系统里有点怪异。我发现仅仅 kldunload kqemu if_tap if_bridge,tap 和 bridge 模块不会被卸载。不管它是正常的事情还是怪异的事情,我不知道。反正只要 sleep 1 再卸载模块看起来工作正常。
我可以在卸载 if_tap 时添加一些检查,比如 if_tap 是否已经被正确地定义等等,试了一下,但是看起来有点问题。使用 kdlstat 来检查是否已经卸载,或者 if_tap 还没有卸载的话,就再次卸载它。不过,那也是在一些罕见的情况下。

记住,如果 tap 没有被卸载,那么下次启动 qemu,你的 qemu-ifup 脚本将会给出一个错误。所以,如果你启动 qemu 得到一个错误说由于 tap 已经被加载了而无法被添加为网桥 bridge0 的成员的话,你可以注释掉 /etc/qemu-ifup 中的 addm tap0 那一行或者 kldunload 直到你看到 if_tap 被正确卸载。不过,这不是必需,只要你在 qemu-ifup 脚本的最后一行有 exit 0 这一行,这个错误信息不会引起问题。

The above two scripts are rather kludgy. At some point, if I ever get around to it, I hope to make them a bit more sophisticated. See dev1’s script in this thread. Lame as these scripts are, they do the job for me. As arun mentions in his post below, they also save a reasonable amount of typing.(译者注,这段没啥用,不翻了,有点累~)

以上是一个非常简单的 tap 网络配置。 Bakul 的配置,例如,让全部的 qemu 客户机在它们自己的子网里,宿主机给它们提供 dhcp 服务,转发和 nat。他的 qemu-ifup 脚本执行添加 tap0 到网桥的任务。他也运行着多个客户机。

有各种其他方法来做到这一点。你可以根据本指南做出符合自己的脚本。

#freebsd, #qemu

把盗版的 Microsoft Windows 2003 Server 从我的引导区删除了

大学的时候从网上下载了一个 Microsoft Windows 2003 Server 简体中文版,一直用这个版本的 Windows 没有换过。
这次这个操作系统从安装上去到现在,再过一个星期就整一年了。其实半年前它就不能正常地进行 Windows Update 了,不过我平时不用它,一直用 FreeBSD 6.2。
在用 Windows 2003 Server 之前,我还用过盗版的 Windows 2000 (Advanced) Server。其它版本的 Windows 可能也用过,但安装在我机器上连续时间绝对不超过一个星期。不过虽然都是盗版的,它都能上微软的 Windows Update 站点上进行操作系统更新。不过这次,不行了,虽然我还是能够上 Windows Update 站点上进行更新下载,但是更新操作总是失败。我放弃了,已经半年没有更新它了。让病毒来袭击我吧。
在 Vista 的盗版在网上出现后,我也曾试图安装一个玩玩,不过用那个什么能否流畅运行 Vista 测试的结果是,我电脑应该扔到电子垃圾场去。
经过了近一个星期的备份操作,我终于可以把原先分给 Windows 的磁盘重新 Fdisk 了,本来想昨晚来做这件事的,不过一边瞌睡一边做,很危险。
今天上午,我用 root 身份登录了 FreeBSD,用 sysinstall 很快就把 160 Gigabytes 的磁盘初始化成 UFS 格式了。现在正在恢复备份。看看我的分区表:

sutra# df -h
Filesystem Size Used Avail Capacity Mounted on
/dev/ad0s1a 496M 254M 202M 56% /
devfs 1.0K 1.0K 0B 100% /dev
/dev/ad0s1e 496M 76M 380M 17% /tmp
/dev/ad0s1f 67G 15G 47G 24% /usr
/dev/ad0s1d 1.9G 279M 1.5G 15% /var
linprocfs 4.0K 4.0K 0B 100% /usr/compat/linux/proc
/dev/ad4s1 143G 56G 76G 42% /mnt/ad4s1
devfs 1.0K 1.0K 0B 100% /var/named/dev

虽然删除了 Windows,但是我依然不能完全离开 Windows,最近在给别人做一个东西,他们是用的 SQL Server 2000 的数据库,我又不得不用 Qemu 虚拟出一个 CPU 来安装一个 Windows 2003 Server,并在上面安装盗版的 SQL Server 2000。

第一次“享受”联想的服务

概要:我的电脑耳机插到孔里一半有两边声音,全部插进只有一边有声音。

我的电脑:

主机编号: ES00616649 主机名: 锋行K7060A A64 3400+ 512160sB(D) 生产日期: 2005/07/06

其实在一年前其声音就只有左边响,我以为是我的音箱坏了呢,还把右边的那个拆开了研究了半天,没发现什么故障,我也试过耳机,也认为耳机坏了,打算买个新的耳机把旧的认了呢,不过一直没空成行。我一直忍受着只有一边响的音效。
前几天我把音箱插到笔记本上的时候,惊喜地发现,我的音箱左右都响,我很诧异,赶紧测试耳机,原来我的耳机也完好无缺!
怎么回事?声卡坏了,今天在准备到网上淘个声卡的时候,我打开了机箱随便看了看,没想到当把音箱插到孔的一半时,两边都有声音了!
我去网上搜索“耳机插到一半”,原来很多人遇到了和我相同的问题,网上的朋友列出了几种可能:
主板坏了、声卡那个什么焊脚松了……
焊接主板这种事我还没有能耐,要是让我焊接个简单的电路板倒是没问题,电脑主板这种事我还是一窍不通。
本想能搜到更多相关的信息把耳机两个字删掉,以期能把音箱也包含了,没想到 Google 搜索结果第一页还给我列出了一个黄色网站!

我不想继续搜了,看来我是解决不了这个问题了。
找联想的客服问下?
在联想网站,找呀找,本想找个客服电话,那样直接,没找到,只找到一个“网上报修”,报修吧,我用的 Firefox,竟然无法选择“期望回复时间”,

无法选择就无法选择吧,多久回复没啥重要,我等着,虽然它右边有个红色的小星号。填好我就提交了,这就是我提交的结果:

Server Error in ‘/wss’ Application.
Object reference not set to an instance of an object.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.

Source Error:

An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

Stack Trace:

[NullReferenceException: Object reference not set to an instance of an object.]
WIOS.WS_REPAIRSubmits.btnSave_Click(Object sender, EventArgs e) in d:07wios_pd20070731wioswebportws_repairws_repairsubmit.aspx.cs:317
System.Web.UI.WebControls.Button.OnClick(EventArgs e) +108
System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument) +57
System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) +18
System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData) +33
System.Web.UI.Page.ProcessRequestMain() +1292

Version Information: Microsoft .NET Framework Version:1.1.4322.2407; ASP.NET Version:1.1.4322.2407

我晕,算了我用 IE 试试吧,“期望回复时间”依然无法选择。

你到底想让我用什么来报修?