使用Vim编辑文本文件非常方便,但是打开一个二进制文件的时候会显示乱码,而不能像UltraEdit那样显示十六进制的数据,我们可以用一个小trick实现。
一般情况下,打开一个二进制文件如下图所示,我们打开一个 .o 文件

要让其显示为十六进制的,可以在命令模式键入下面的命令:

:%!xxd

这里是运行一个外部命令xxd

xxd - make a hexdump or do the reverse.

运行这个命令之后,vim显示如下:

如果要返回之前的现实,运行下面的命令

:%!xxd -r

这个trick的原理就是使用外部命令xxd生成一个hexdump。

一台以前用来自己玩的虚拟机,只分配了6G的存储空间,今天进行系统更新的时候报磁盘空间不足。系统总共6G多一点点,主要空间都挂在在 / 下,由于很久没有更新系统了,这次更新需要下载300多M内容,在执行 yum updata 的时候就报磁盘空间不足了。

首先使用df看下空间 df -h 发现确实剩余空间不多了,但是这台机器只是我自己用来玩的,没有其他的东西,估计是记了很多日志吧,使用du看下是哪个目录占用比较多。

cd /
sudo du -d 1 -h

du命令的 -d 1 参数表示深度为1, -h使用用户友好的方式返回结果。使用了 -d 就不需要使用 -s 参数了。

查看发现 /usr 目录占用了4G多, /home/var 目录倒都不算大。进usr目录在du看看。

cd /usr
du -d 1 -h

发现 lib/mongdb 目录占了很大的空间,再进去看,发现是一个journal文件夹占了3G左右。(如果对Linux比较熟的话,应该认识journal了,journal可以认为是一种日志记录方式,journal程序可以直接查询)

原来是很久以前玩mongodb的时候测试过插百万条数据什么的,估计就留下了这么大的文件。个人认为这是mongodb的事物日志记录,我现在不用mongo了,可以用下面的方法安全地删除掉。

sudo service mongod stop
sudo vim /etc/mongod.conf

插入一行配置 smallfiles=true
然后就可以删除那些journal文件了。

sudo rm /var/lib/mongo/journal/*
sudo service mongod start

这样就空闲了3G的空间了,自己练练手玩还是够的。

Go原生支持Unicode,字符集与编码的问题在编程语言中是很重要的,尤其对于CJK(中日韩)开发者来说。以前很多开发推荐使用GBK编码,现在更加推荐使用UTF-8,虽然UTF-8编码需要更大的存储空间,但是它带来的便利性实在远超过那一些存储空间。现代语言一般都支持Unicode了。着一篇争取对Unicode,UTF-8,UTF-16,ISO-8859有一个系统的了解。

介绍

最早的计算机系统都是使用 EBCDIC(扩展的二进制的十进制转换码) 和 ASCII 编码,因为那时候只是用一些英文字母数字,加减号和其他一些字符,字符并不多,但是随着Internet的发展,网络遍布全球。全球有大概6000种语言(其中3000种在巴布亚新几内亚…) ,为了更好地服务更多的人,我们需要为不同语言的用户提供不同的语言支持。

如果世界只需要 ASCII 编码,那样将会简单很多。但事实却是非常复杂的,现代项目都应该考虑 i18n 和 l10n, 分别代表 internationalisation 和 localisation。i18n指应用能处理各种各样不同的语言和文化, l10n 则是为某个特定的文化团体定制你的国际化应用。

更广泛的i8n不仅仅是文字编码的区别,还要考虑诸如颜色、音乐在不同语言文化中的不同代表意义。但这个领域太过复杂,暂时一般不考虑。

定义

Character

Character 就是字符,

“the smallest component of written language that has a semantic
value

当然字符也包括自然语言符号之外的一些控制字符。关于字符的内容,可以查看 维基百科

字符集,character code 字符编码就是将一个字符映射到一个整数,比如最常见的 ASCII编码,将 a 编码为 97(编码点,code point), A编码为65.编码仍然是抽象的,它仍不是我们在文本或者TCP包中多见到的。

Character encoding 注意code和encoding是不同的。字符编码(encoding)表示的是其二进制编码。诸如补码,反码等。代表字符 A 在二进制上是怎么存储的。

字符编码(英语:Character encoding)、字集码是把字符集中的字符编码为指定集合中某一对象(例如:比特模式、自然数序列、8位组或者电脉冲),以便文本在计算机中存储和通过通信网络的传递。常见的例子包括将拉丁字母表编码成摩斯电码和ASCII。其中,ASCII将字母、数字和其它符号编号,并用7比特的二进制来表示这个整数。通常会额外使用一个扩充的比特,以便于以1个字节的方式存储。

按照惯例,人们认为字符集和字符编码是同义词。

传输编码/Transport encoding对一段在网络传输的数据需要如何确认它的字符集和编码呢?可以在传输的头部包含一段内容编码信息的内容,就像HTTP头那样。

Content-Type: text/html; charset=ISO-8859-4
Content-Encoding: gzip

但是我们要怎么读到这一段内容呢?因为它们也是要编码的,这就是一个现有鸡还是先有蛋的问题了。但是我们可以做一个约定,这些信息都使用 ASCII 编码,更明确一点就是 US ASCII。因为这一部分信息的内容的功能使用ASCII就能搞定了。

常用编码

ASCII

ASCII 编码大家都很熟悉了,大一的时候专业基础课接触到的就是这些内容。作为最常用的编码,ASCII使用的编码点使用7-bit。

ISO 8859

现在一个字节的标准是8个比特位,作为ASCII的扩展多出来128个编码点。一些列不同的编码集使用了这128个编码点,它们被一些欧洲语言使用,合起来就是 ISO-8859系列。 ISO-8859-1也就是常说的Latin-1,它涵盖了大部分的欧洲语言。
ISO-8859是一个系列,系列中所有的低128个编码点就是ASCII编码,以实现兼容。

早期的HTML标准推荐使用ISO-8859-1字符集。不过在 HTML 4 之后就推荐使用 Unicode了。

Unicode

像ASCII和ISO8859这样的编码在象形文字(中日韩)语言面前显得就太小气了。中文常用的字多大几千个,至少需要两个字节才能涵括。最初没有统一的国际标准,因此出现了很多的2字节编码方案,比如台湾的Big5,国内的GB2312,BGK;再考虑日本的JIS X 0208等等,字符集简直是一个大乱斗。

Unicode是一个包含所有主要当前在用字符的新的标准,它包括了欧洲,亚洲,印度等等各种语言,Unicode的好处是它是可扩展的。到5.2版本,一共有超过 107000个字符。

Unicode编码点是兼容ISO8859的,也就是说它的前256个编码点就是ISO 8859-1.

要在计算机系统表示一个Unicode字符,需要使用一种编码方式。UCS(通用编码方式)使用两个字节进行编码,然而随着Unicod囊括的字符越来越多,UCS不再使用,而是使用 UTF-*的编码。UTF即 Unicode Transformation format.常见的编码方式如下:

  • UTF-32 试用4字节编码,不常用,尤其是 HTML5 规范明确反对使用它
  • UTF-16 将最常用的字符编码到2字节,应该也不常用
  • UTF-8 一个字符使用1-4字节,不定长度,最常用的,对英文字符基本和ISO-8859对应,中文编码为3字符,相比GBK多一个字节
  • UTF-7 有时使用,但不常用

要理解Unicode和UTF-8之间的区别和联系。知道UTF的名字是Unicode转换格式就可以了。Unicode是一个字符集,UTF-8是这个字符集的一种编码方式。

Go与字符集

UTF-8

UTF-8 是当前最常用的字符编码,据Google统计50%的网页使用UTF-8编码。我们知道UTF-8的发明者就是Go的作者,也就是Unix的作者Ken。Go使用UTF-8编码的字符组成其字符串。Go的字符串的每一个字符称为一个 rune,它是 int32的别名,因为一个Unicode字符的长度可能是1,2,3,4个字节,如果要统计字符数,就需要计算的是rune的个数而不是字节数了。字符数和字节数只有在是字符串只由ASCII字符组成时才是一样的。

下面是一个中文字符串的示例,它表示出Go中字符串中不同字符占用长度不一样。

str := "百度一下,你就知道"
fmt.Println("string len:", len([]rune(str))) // 9
fmt.Println("Byte len", len(str)) // 27

Go默认就是utf-8编码,所以如果你的项目要使用UTF-8的话,不需要做额外的字符集处理就可以了。

UTF-16

ISO 8859

这两天状态不太好,这一篇写的很糟糕

在端到端的通信中,是两个程序之间的通信,除了TCP/UDP这样的传输层协议外,我们需要应用之间的通信的协议,这样才能利用/理解传输的数据,这就是我们这一篇的应用层协议。

设计

应用之间的通信协议设计要考虑下面的问题:

  • 广播还是点对点?广播必须是UDP,本地多播或者更加实验性质的MBONE,点对点则可以是UDP和TCP
  • 有状态还是无状态的?有状态的连接对应用更简便,但是要考虑连接奔溃的时候怎么办
  • 传输层协议是否是可靠的?可靠连接一般更慢,但是就不需要担心丢包了,在内网的话UDP的可靠性也是非常高的
  • 是否需要回复(reply)?接收端是否需要给发送方返回一个收到确认,如果要的话要考虑确认丢失,可能要使用超时
  • 使用什么样的数据格式?通常会使用MIME规定的类型,或者使用字节编码数据
  • 是否有突发性大流量的情况?这要考虑QoS(服务质量),如果突然流量爆发如何处理。
  • 多个流是否有同步的要求?比如视频和音频需要同步时间轴
  • 是一个独立的应用还是为其他人提供的库?

设计一个应用层协议,上面是最基本要考虑的点。

数据格式

消息的数据有两种主要的数据格式,一种是字节编码(byte encoded),一种是字符编码(character encoded).

字节格式

使用字节编码格式的数据一般使用第一个字节来区分消息类型。消息的处理器(handler)通过检查第一个字节转向不同的处理流程(不同的消息对应不同的请求)。其他字节是根据某种规定的规则序列化的数据体。

使用字节的优势是数据密度高也因此速度很快,其缺点是数据不透明,这其实也算一个好处,这样别人就难以逆向工程了。如果出现错误将很难被发现,很难调试。常用使用这种格式的是DNS、NFS到Skype。

针对字节数据,Conn 类型提供了读取字节的方法:

func (c Conn) Read(b []byte) (n int, err os.Error)
func (c Conn) Write(b []byte) (n int err os.Error)

字符格式

使用base64编码后或者将二进制流转变成7-bit使用ascii字符发送。
字符格式通常使用多行形式的消息。第一行通常代表消息类型,剩余的数据是消息体数据。常使用行处理的函数。
在Go中没有管理字符流的直接支持,只能手动执行“换行”,要注意的是不同操作系统的换行很不一样, Unix系统使用’\n’ 代表换行,Windows系统使用”\r\n”换行,在internet上”\r\n”更加常用

状态管理

应用需要管理状态信息来简化当前的操作,可以使用状态来保存应用信息。状态可以由服务器管理也可以由客户端管理。
可以使用应用状态转移图表设计状态系统。状态转移图可以跟踪应用的状态以及何时转移到新的状态。

这一章主要是概念性的东西,状态不太好,记的很乱,ごめ

gitconfig的配置非常重要,有时候换到一台新机器由于没有自己配置搞得很不顺手,干脆把gitconfig摘出来,方便以后配置。
主要是命令别名的配置。

[user]
name = wuxu92
email = wuxu92@163.com
[alias]
lg = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset [%an]' --abbrev-commit --date=relative
ls = ls --show-control-chars --color=auto
go = "! bash -c \"git pull && git add .; if [ \\\"\\\" == \\\"\\\" ]; then git commit -a; else git commit -am \\\\¡\\\\¡; fi; git push origin master:yy
our-id;\""
ci= commit
co = checkout
br = branch
st = status
list = log --pretty=format:\"%h %ad | %s%d [%an]\" --graph --date=short
undo = reset --hard
llog = log --date=local
changes = diff --name-status -r
diffst = diff --stat -r
conf = diff --name-only --diff-filter=U
[http]
# proxy = 127.0.0.1:1080

[https]
# proxy = 127.0.0.1:1080

[push]
default = simple

注意git go 的别名需要自己修改一下your-id

这一篇主要是关于Go序列化数据。数据序列化在网络编程中非常重要,在网络传输数据时,如果数据是对象,数组等等,需要先把对象序列化为才能传递,同时接收方需要进行反序列化才能使用这些数据。一个比较常见的是json数据。 序列化和反序列化操作又成为 marshalling 和 unmarshalling 。

序列化方法

序列化其实简单地说就是一种把变量似乎用字节流表示出来的规范,我们可以定义自己的规范,只要接收方能使用相应的方法反序列化出来原来的对象就可以了。但是我们要考虑序列化方法的通用性,希望对任意的数据可以使用同样的序列化和反序列化方法实现。
早期很著名的系列化方法是 XDR(external data representation),它最早用于Sun的RPC中。Go没有对不透数据的marshalling 和 unmarshalling 有明确的支持。RPC包中没有使用XDR,而是使用gob
要实现对数据统一的序列化方法,需要实现一套 自描述数据的规范。像xml就是一类自描述的数据,能通过节点属性对数据进行描述。xml还有很强的扩展性。这也是为什么很多项目中会使用xml作为数据传输的格式。

阅读全文 »

求婚大作战

  • 光想不做是不行的
  • 以为不说对方也会懂就大错特错
  • 总是说明天再做的是笨蛋

东京爱情故事

  • 恋爱这东西,有趣的是在于参与,即使失败了也是很有味道的。因为,你爱上一个人的那个瞬间,是会永远永远留在心里的。这瞬间,便是生活的勇气,便是黑夜里点亮的一盏明灯
  • 恋爱就像一本参考书。即使不成功,人和人相爱的瞬间,那种感觉会永远留下去,只有这样才会让人有继续生存下去的勇气,才能变成一把照亮黑暗的手电筒。要努力喔,我也一起努力。
  • 不是因为身边没有人而寂寞,而是因为身边没有某个人而寂寞。
  • 莉香: (眼中含着泪)你会后悔的,你会后悔的,心里只能有一个爱人,不能有两个,爱我的你在哪儿?24小时都爱我,工作时,玩耍时都爱我,你不是曾经说过这样的话吗?将我抓的牢牢的,把我盯得紧紧的,不然的话,我会离你而去的!
  • 这种事习惯不了的。和大家分别的时候,心里总是很难过的。所以我,和大家在一起的时候,总是尽情的欢乐。我总是告诫自己,跟谁都要欢欢喜喜的,我想也许再也不会见面了,于是我就拼命的……微笑。
  • 我总认为,要真正爱上一个人是件非常不容易的事情。爱情只是一瞬间的,所以,我觉得,和永尾君的那段感情是很珍贵的,是这样,我爱上了你,你爱上了我,那情景珍藏在我心里,不管明天会发生什么,我的爱是义无返顾的,过去的我是如此,现在的我也是如此,回首往事,我只想说一句话:“青春无悔!”是的,无悔!
  • 完治: 喂,我一直搞不懂,背这么大的包,里面都放了些什么东西呀? 莉香: 爱情和希望!
  • 已经不行了,瞧这儿(胸口),电池已经耗尽了……虽然贴的这么近,……却感到离得那么远……是什么原因 ?总算来了,谢谢你,我好高兴,真的,你来了。不知怎么的……真想和你在一起……但是,为什么呢?……

美丽人生

恋爱世纪

  • 偶尔不吵架的话,太安静了,会很寂寞。
  • 喜欢一个人,讨厌一个人都是一瞬间的事,可是相信一个人的心情不是那么容易恢复的。
  • 不知怎的,大家都把恋爱幻想得很美丽,想圣诞节两个人要这样过,约会选在那个地方……专注于两人的世界,都不往别的方向看。以同样的心情凝望对方,以为这就是理想的恋爱……实际上又吵架,又伤害对方。互相迁就。……即使这样,还是不想让它这么容易就结束了。恋爱就是这样吧。
  • 我在东京也想过,你昨天说的——自己一个人根本不行,怎么还想要去照顾别人。但是如果那样想的话,两个人一辈子都不能在一起了。所以,其实没什么大不了,一个人不能坚强。所以你也别再说什么要自己坚强起来的话。两个人一起坚强起来不就好了?等我们变成老公公老婆婆,再一起坐在这里看流星吧。
  • 我非常清楚你不可爱的地方,经常也觉得你很可恶,可是无论怎样也不会讨厌你的。这样的心情,我想几十年也不会改变的。所以,请看着我变成老伯吧。我也会看着你的,看你变成老婆婆。
  • 我并不是想表现自己或者是想别人称赞,只是想做一些能向自己交代的工作……我还不想放弃。不想为工作改变自己,所以我想啊,做一些只有自己能胜任的工作。
  • 出人头地。不随波逐流,不理会公司,只做自己认为应该做的事。就是这样,我想变成这样。
  • 绘理香:因为哲平你太温柔了。可是,这种半途而废的温柔只会伤害女人的心。

没有玫瑰的花店

  • 为了你自己能够幸福,你应该尝试对一个人温柔。即使最开始不是真心的也没关系,总有一天你会变成一个心地温柔的人。因为对人温柔是一件很幸福的事儿。

Good Luck

  • 一公升的眼泪

悠长假期

#

其他

  • 回过头来看,那些曾经让自己寝食难安的事,大多败给了想象。

UDP数据报

关于UDP的部分在 这篇 文章里已经介绍了很多了,这里只记录在npwg中出现的内容。

监听多个socket

一个server一般式监听多个端口服务多个客户端的,这种情况一般会用到轮训的方式。在C中使用 select() 调用让内核去做这个工作。在Go中,为每一个端口分配一个Goroutine来实现。

Conn, PacketConn, Listener 类型

前面的TCP和UDP使用了不同的API,比如TCPConn和UDPConn其实它们都是实现了 Conn 接口。所以之前的写法也都可以直接使用 Conn 实现,这样可以不需要在代码中区分 TCP和 UDP而直接使用 Dial 方法。

func Dial(net, laddr, raddr string) (c Conn, e os.Error)

需要注意的是,这里的 laddr 和 raddr 都是字符串,也就是 host:port 这样的形式的字符串,而不需要向之前那样先使用 net.ResolveUDPAddr 或者 net.ResolveTCPAddr 先解析出地址。因为可以兼顾TCP/UDP/IP数据,所以net参数可以为 tcp, tcp4, tcp6, udp, udp4, udp6, ip, ip4, ip6。这个方法返回一个Conn接口的实例。

conn, _ := net.Dial("tcp", "zhihu.com:80")
conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n"))
var buffer = make([]byte, 512)
if n, err := conn.Read(buffer); err == nil {
fmt.Println("response", n, "\n", string(buffer))
} else {
fmt.Println("err occur", err)
}

一个更加简化的使用接口实现的Head程序。但是这样写有一个问题,如果服务器端返回的内容超多了512个字节,那么buffer不能存储这么多,那么就只会读到前512个字节的内容,后面的被丢弃。我们需要是用一个缓冲区来存储(这部分可以封装为一个方法,读取所有返回)。Read部分改如下:

result := bytes.NewBuffer(nil)
var buf [512]byte
for {
n, err := conn.Read(buf[0:])
result.Write(buf[0:n])
if err != nil {
if err == io.EOF {
break
}
fmt.Pritnln("err", err)
}
}
fmt.Println("res", result.Bytes())

而对于server端,可以不需要ListenUDP或者ListenTCP而直接使用 Listen 方法,它会返回一个实现了 Listener 接口的对象。其对应的方法签名如下:

func Listen(net, laddr string) (l Listener, err os.Error)
func (l Listener) Accept() (c Conn, err os.Error)

同样需要注意Listen的laddr是一个字符串了,而不是解析过的TCPAddr或者UDPAddr。
对应UDP使用 ListenPacket(net, laddr string) (c PacketConn, err os.Error) 方法。

使用更原始的IP套接字

这一节我们讨论一下一般情况下不太需要的原始套接字,这可以构建我们自己的IP协议或者使用TCP和UDP之外的协议。
TCP和UDP不是IP层上仅有的协议。现在大概有140种这样的协议(参看 /etc/protocls)。
Go可以通过原始套接字使用户使用这些协议进行通信。

我们可以使用原始套接字实现一个icmp客户端程序,也就是ping命令。我们要注意的是ping不是传输层应用,不使用TCP或者UDP也就没有端口的概念

func main() {
addr, _ := net.ResolveIPAddr("ip", "baidu.com")
conn, err = net.DialIP("ip4:icmp", nil, addr)
CheckErr(err)

var msg [512]byte // icmp 报文
msg[0] = 8
msg[1] = 0
msg[2] = 0
msg[3] = 0
msg[4] = 0
msg[5] = 13
msg[6] = 0
msg[7] = 37
len := 8
check := checkSum(msg[:len])
msg[2] = byte(check >> 8)
msg[3] = byte(check & 255)
_, err = conn.Write(msg[0:len])
CheckErr(err)
fmt.Println("wites", (msg[0:len]))
n, err := conn.Read(msg[0:])
CheckErr(err)
fmt.Println("got ping res", n)
if msg[5] == 13 {
fmt.Println("ping identifier matches")
}
if msg[7] == 37 {
fmt.Println("ping sequence matches")
}
}
// 计算校验和
func checkSum(msg []byte) uint16 {
sum := 0
for n := 1; n < len(msg)-1; n += 2 {
sum += int(msg[n])*256 + int(msg[n+1])
}
sum = (sum >> 16) + (sum & 0xffff)
sum += (sum >> 16)
var ans uint16 = uint16(^sum)
return ans
}

func CheckErr(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "err %s", err.Error())
os.Exit(1)
}
}

这段代码在Windows下使用管理员运行也没有结果(使用非管理员运行会提示权限不足),会在Read(msg[:]) 那里卡住,但是在Linux运行是可以的,不过后面那端matches的不知道干什么的,读到的返回的数据的内容大概是这样的: [69 0 0 28 45 250 64 0 64 1 216 33 10 4 16 95 10 4 16 95 8 0 242 255 0 13 0 37]

下一张 数据序列化

Go很“先进”的一点在于它的文档和提供的文档工具 godoc ,但是由于golang的官网被 万能的GFW 干掉了,有时候要查文档很不方便。虽然翻墙也不麻烦,但是如果能有个本地的文档那就更方便了。

以往的本地文档都是以PDF或者CHM的形式存在,使用的应该都能感觉他们的缺点,PDF缺乏组织,查找/索引很不方便,而CHM限于Windows的技术支持,并且有时候也不是很方便。

实际上Go的安装包里提供了基础包的文档,他就保存在 GOROOT/doc(好像不是这个目录) 下,是一些html文件,当然我们可以直接浏览器打开,有一种更见好用的方式是用godoc提供的http服务,在命令行运行:

godoc -http:6060 &
// & 表示后台运行,windows下可能需要保持命令行窗口不关闭,如果要关闭窗口且保持服务运行
// 需要先运行 eixt 然后再关闭窗口,要关闭进程可以新开一个bash,ps找到PID后Kill

这条命令会监听6060端口的http请求,提供和golang.org同样的页面服务 。浏览器打开 http://localhost:6060 就可以方便的查看文档啦。