DeaDBeeF

2012年4月3日 星期二

一直以来断断续续用着Deadbeef和Gnome默认的Rhythmbox。Rhythmbox的废柴程度不必多说,Deadbeef和各种桌面环境的继承程度不高(因为没有MPRIS支持),而且最主要的是Deadbeef不支持媒体键很困扰我,虽说Deadbeef内置全局快捷键插件,可以绑定Ctrl,Alt,但是就是不能绑定到媒体键上。据说Amarok很牛逼,可是一知道是Qt写的就放弃了,没办法,我有很强的UI洁癖,如果我用KDE的话绝对没问题。但是Deadbeef其不可替代的一点是CUE支持,目前已知的像样的G系播放器只有Deadbeef做到了;另外Deadbeef的devel分支上还提供了自定义UI布局的能力(完全就是另一个foobar2000了嘛!)。

后来发过邮件问了Deadbeef的作者,说是有个Gnome媒体键的插件deadbeef_gnome_mmkey,和一个MPRIS的插件DeadBeeF-MPRIS-plugin,但是没有集成到源码树中。但是两个插件合在一起用总是有些诡异的问题,亲自gdb调试、放狗搜索之后才知道DBus官方的dbus-glib的实现没有保证线程安全,GLib后来自己实现了DBus,曰GDBus,集成到了GIO库中。deadbeef_gnome_mmkey用的是前者(而DeaDBeeF-MPRIS-plugin用得是后者,这冲突怎么产生便不得而知),想要多线程使用DBus,只能使用GDBus。

于是我看了一眼deadbeef_gnome_mmkey的代码,不长,100来行,用GDBus的接口来重写的话还能避免自动生成两个不必要的文件。最后把代码托管在Github上,另外Deadbeef的devel分支的代码不兼容GTK3,于是顺手也将Deadbeef的UI插件移植到GTK3上,增加了个媒体键的插件,代码托管在Github上。

另外,至于MPRIS,其实就是将播放器当前正在播放的曲目的元数据和播放器状态广播到DBus上,下图是安装了Media player indicator这个Gnome shell扩展之后的效果(图片已阵亡)。

MPRIS插件的作者也是gtkqq的作者,插件的代码也托管在Github上,其插件的代码是和DeaDBeef源码树分离的。

至此DeaDBeeF终于没有什么好抱怨的了。

开启终端256色

2012年1月5日 星期四

一般的Linux发行版默认的终端都是16色的,但事实上几乎所有的终端都支持256色终端。只需要把环境变量TERM设为xterm-256color(xterm算是终端模拟器的一个标准,Gnome Terminal用gnome-256color,Konsole可以用konsole-256color)。开了256色终端之后vim的语法高亮就让我感动的一塌糊涂。

但是export TERM=xterm-256color不应该写到.bashrc里,而是支持xterm的色彩编码模式的终端模拟器才能设置这个环境变量,比如在tty下TERM的应该为linux,如果在.bashrc里设置环境变量的话,那么tty下仅有的16色可能也支持不了了。

按照道理来说Gnome Terminal的配置中应当有开启256色的选项,或者给个预设环境变量的选项;但是没有Gnome Terminal太弱了,连这些选项都不给。于是乎写了个文件~/.gnome-terminal-wrapper

#/bin/sh
export TERM=gnome-256color
exec $SHELL

保存后加上执行权限,然后在Gnome Terminal的首选项中,设置启动命令为/home/username/.gnome-terminal-wrapper(在这里连~都不支持)。

另外,在没有研究关于终端一些原理之前,我主要通过在vim的配置文件里加上set t_co=256这个命令,来打开256色模式。虽然这么做没什么问题,但理论上来说这么做不怎么好。比如,你是通过ssh连到服务器上,然后打开服务器上的vim,服务器怎么知道你的终端是否支持256色呢,正确的做法应该是在~/.ssh/config中,加上SendEnv TERM,来给服务器传递这个环境变量。

 

2012要到了,于是换了个主题

2011年12月30日 星期五

在Mantra主题基础上改了下CSS,对与原作者对字体的癖好表示有些不满,为中文习惯加上了段落首行缩紧,同时适当加了些阴影(IE8之前估计都是出不来效果了,But who care?),目前还能接受吧。

PS: 不过博客内容倒一直更新得很慢。

吐槽下联想的BIOS升级程序

2011年12月3日 星期六

首先,好久没有什么更新了。

上个月由于种种理由重新装了系统。于是乎稍微激进的折腾了下UEFI。首先英特尔的显卡驱动在UEFI的Windows必须打SP1才能安装,这我就不说了。

坑爹的是联想的BIOS固件升级程序,官网上给了两个选择,光盘升级镜像,或者Windows的升级程序,我抱着省一张光盘的态度下了一个可执行文件(其实我估计这东西在UEFI版的Windows上跑不动),结果运行得貌似很好。结果接下来就苦逼了一个月:

  • 整整一个月Windows都不能正常关机,关机进程还没有结束电源就啪的一声断掉(其实我会告诉你这是硬盘的声音吗)
  • 不论是Linux还是Windows,一旦进入休眠,同样啪的一声,电源会断掉,并且,会尝试重启,在屏幕背光亮起之后电源再次断掉,如此反复。

突然今天兴起就想改了下BIOS(其实现在是个UEFI),进了BIOS菜单,修改完毕后发现不能保存。突然就想到了上次BIOS固件升级也许是失败的(但是联想你什么也不说啊混蛋)。于是乎只好刻了张光盘,重新刷了一次固件,终于我的本有救了。

发现神物kpartx

2011年9月5日 星期一

Linux下可以用losetup来吧映像文件当作挂接到loop块设备去,比如有个光盘映像livecd.iso,可以以root权限执行下面的命令来把这个映像挂载道/mnt

losetup /dev/loop0 livecd.iso
mount /dev/loop0 /mnt

对于简单的映像文件,losetup足以胜任,但是如果映像文件里面包含分区表的话(比如硬盘的映像文件),losetup就很难处理了,据说可以用fdist或者parted等工具来查看各个分区的偏移量和大小,然后在mount的时候加参数,不过总归麻烦。
Google后发现了个东西叫kpartx,此神物就是专门处理硬盘映像文件的。比如我给我的U盘分了两个去,一个NTFS,一个EXT4。然后用dd制作映像

dd if=/dev/sdb of=flashdisk.img bs=512

然后可以用这个命令来装载映像

kpartd -av flashdisk.img

参数-a是指示kparted去装载影响flashdisk.img,-v是verbos,命令的回显会说明这个映像被装载到某个loop设备,比如/dev/loop0,并列出所有的分区,这些分区对应的块设备都放到/dev/mapper/loop0pX,

比如我这个映像在装载后会生成/dev/mapper/loop0p1和/dev/mapper/loop0p2,分别对应我U盘上的两个分区。

Linux Socket编程:Address Already In Use及其背后的问题

2011年8月8日 星期一

写Socket程序的时候经常会遇到这个问题:如果自己的程序不小心崩溃了,重新启动程序的时候往往会在bind调用上失败,错误原因为Address Already In Use,往往要等待两分钟才能再次绑定。但是在很多的程序(比如nginx)中好像并不存在这个问题,就算被KILL了也能立刻重启。这个区别还是比较头痛的。

其实我猜Unix Socket编程这样的书上有讨论过这方面的问题,不过我竟然没有这方面的书籍(完全靠man看来也是行不通啊)。我曾经天真的以为,在收到SIGTERM这样的信号的时候把所有套接字全部关闭可以解决问题。后来才发现无济于事。Google了这方面的文章才知道,解决这个问题理论上有三种办法。

  1. SOCK_REUSEADDR:曾经在研究内网穿透的时候跟这个东西打过交道。如果一个监听的套接字被设置为允许地址复用,那么套接字绑定到的地址不会被独占,所以必然不会存在Address already in use的问题。而且如果有两个套接字绑定到同一个端口,也只允许一个套接字进行监听,后一个套接字在调用listen的时候会报错。因此这个方案显然是最佳方案。对于完全不知道我在说什么的童鞋,你们只要这么做:
    s = socket(AF_INET, SOCK_STREAM, 0);
    /* What you need to do is add the following two line to your code */
    unsigned value = 1;
    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
    /* Then do other things */
    listen(s, SOMAXCONN);
    /* ... */

    不过据说这样的做法容易产生安全性问题,某些操作系统(没有指明Linux或是BSD或是Windows)允许分别属于两个进程的两个套接字绑定到两个地址的同一个端口。大多数程序把套接字绑定到0.0.0.0这个地址上进行监听(也即监听所有网络设备),这种情形下另外一个进程可以绑定到127.0.0.1或者是其他网络设备的IP地址的同一个端口,并进行监听,就可能把外部的连接给拦截下来(因为127.0.0.1这样的IP是属于特定设备的,比0.0.0.0这虚拟设备更specific)。
    而解决这个安全性的问题的方法其实也不难(其实换个没问题的操作系统就可以了不是?),只要把套接字绑定绑定到具体的网络设备的IP地址(比如绑定到127.0.0.1,或者a.b.c.d)即可,大不了为每个网络设备建一个套接字。如果实施起来有难度,只能考虑后面的两种方法。

  2. 让客户端先关闭连接。如果所有的连接关闭事件都在客户端首先发生,那么也不会存在这个问题。不过这种做法可能需要修改协议,而且貌似很容易恶意连接攻击。
  3. 修改系统TCP超时时间,这种方法很不推荐。

CUE切分工具

2011年6月10日 星期五

CUE是个好东西,不过Linux下的播放器很少有支持的,仅存的几个支持CUE的播放器要么支持的格式不够,要么编码容易出问题。还是切分成单个文件比较好。

切分音频,用Windows的话Foobar2000可以很轻松搞定,但Linux下面一直没有这么犀利的东西(Foobar2000是少数Windows下能让我产生依赖的程序),同时我非常反感装WINE这种东西。

于是最后还是写了个切分脚本出来,需要的可以下载。切分脚本会自动把CUE提取到的信息作为元数据,包括艺术家、标题、专辑、音轨编号。实测了6个CUE没有问题。不过对于CUE的格式我没有找到详尽的文档,有一些不常见的特性也就没有实现,比如CUE的ISRC项(好像是用来作为媒体文件的唯一标识的)就没有支持也难以保证不会出BUG。为了支持更多的媒体格式,切分脚本后端调的是FFMPEG的命令行工具,所以要用这个脚本的话需要先装上这个东西。

如果发现脚本有什么问题的,可以联系我。

(更新:caspar推荐的cue2tracks比我的要专业得多,还是用那个吧)。

UDP Tunnel与TCP内网穿透

2011年5月2日 星期一

前些天在Google Code上发现了一个东西:udptunnel,此物可以在UDP协议上模拟TCP通信。用法很简单,udptunnel分为客户端和服务端,假设服务端的ip是a.b.c.d,客户端需要通过udptunnel连接服务端的ssh(端口22)。

服务端:

#打开本地4444端口等待连接
udptunnel -s 0.0.0.0 4444
客户端:
# 127.0.0.1 3333是本地的TCP代理
# a.b.c.d 4444是服务端的地址和端口
# 127.0.0.1 22是相对服务端而言的目标IP地址(这里是服务端本地的ssh服务)
udptunnel -c 127.0.0.1 3333 a.b.c.d 4444 127.0.0.1 22
#连接服务端的ssh
ssh -p 3333 user@127.0.0.1

这样就可以通过udptunnel连上服务端的ssh。

同时最近正好在考虑TCP内网穿透的问题,于是产生了一个想法:TCP over NAT-Traversaled udptunnel。其实这想法肯定不是我第一个想到的,但是我尚未在实际应用中看到这样做的,因为很多情况下UDP足矣。但是有些应用必须用TCP进行连接,比如SSH,VNC。

总所周知,因为NAT的关系,P2P网络多了一个需要考虑的东西,就是如何突破网关。关于NAT的和相关的背景我不在这里罗嗦了。内网穿透有两种:分别对应与TCP和UDP。UDP内网穿透是很早就开发出来的技术,只要网关的类型是Cone NAT就可以,对于Symmetric NAT,理论上只要能猜出端口号也没有问题。

对于Cone NAT,事实上还可以分为三个小类:

  1. Full Cone NAT: 一旦内网的主机的地址和端口ip1:port1被NAT映射到外网地址和端口ip2:port2,外网任意主机都可以通过ip2:port2向ip1:port1发送数据。
  2. Restrict Cone NAT: 内网的主机ip1:port1通过NAT映射到外网ip2:port2后与某台主机通信后,对其他主机ip3:port3来说,只有先有从ip1:port1发往ip3:port的数据经过NAT(不考虑ip3:port3是否收到),NAT才允许ip3:port3的数据通过ip2:port2发给ip1:port1,否则会被NAT忽略。也就是说NAT会记住与内网通信的主机IP地址,但是不考虑端口。
  3. Port Restrict Cone NAT: 内网的主机的地址和端口ip1:port1通过NAT映射到外网地址和端口ip2:port2后与某台主机通信后,对其他主机ip3:port3来说,只 有从ip1:port1发往ip3:port的数据经过NAT后(不考虑ip3:port3是否收到),NAT才允许ip3:anyport(这个anyport表示任意端口)的数据通过 ip2:port2发给ip1:port1,否则会被NAT忽略。也就是说NAT会记住与内网通信的主机IP和端口。

对于UDP来说,这三种类型都是一样的,首先UDP不需要建立连接,也就是UDP协议设计时就考虑了一对多通信的可能(能够进行一对多通信是内网穿透的条件之一),所以只要P2P的双方通过中介得知对方的IP和端口,在正式通信前双方先发一个无效的数据包“通知”各自的NAT即可,各自的NAT就会为为连接的双方放行。

但是TCP内网穿透比UDP要复杂,而且对路由器的要求也更为苛刻。只有在Full Cone NAT的路由器上,TCP内网穿透才可以进行,尽管TCP是有连接的,不能进行一对多通信,P2P程序可以通过端口复用来模拟一对多通信。但对于对于Restrict Cone NAT和Port Restrict Cone NAT来说,模拟一对多通信是不可能的,因为各自的NAT目前只承认到中介服务器的连接,也就没有办法完成TCP握手,NAT就不给对方放。所以TCP内网穿透只能在Full Cone NAT路由器上进行(理论上只要有一方为Full Cone NAT即可)。

说了这么多,问题就在于TCP进行内网穿透的条件太过严格。回到udptunnel,udptunnel是在UDP上模拟TCP通信,因此理论上,借助udptunnel,在任何类型的Cone NAT路由器上,都可以进行TCP跨NAT通信。两种可行的方式是:

  1. 将udptunnel相关源代码移植嵌入P2P程序中:问题是工程量较大,另外udptunnel是以GPL协议发布,非开源的项目显然就不能使用这种方法了。
  2. 通过UDP端口复用技术:对udptunnel和P2P程序都需稍作修改,工程量小,但是逻辑稍微复杂。

这里简单说下第二种方法:

  1. 首先P2P程序建立udp套接字,并且将这个套接字设为允许地址重用, 并将这个向中介服务器注册,使得其他客户端能通过中介服务器找到自己。
  2. 修改udptunnel,使得其支持打开支持地址重用的套接字。然后得到步骤一中的套接字的所绑定的端口号,使用这个端口号绑定到udptunnel的服务端的套接字。
  3. 连接发起者向中介服务器查询目标的地址和端口,开始udptunnel的客户端,建立隧道。

这样就可以建立穿越所有类型的Cone NAT(甚至如果能猜出端口号,Symmetric NAT的路由器也没问题)的TCP连接了。

另外关于端口复用的实现,我在这里用Python举个TCP端口复用的例子,UDP套接字要做的完全一样。

import socket
tcp1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#在绑定前调用setsockopt让套接字允许地址重用
tcp1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
tcp2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
 #接下来两个套接字都也可以绑定到同一个端口上
tcp1.bind(('0.0.0.0', 12345))
tcp2.bind(('0.0.0.0', 12345))

Shell的作业控制

2011年4月8日 星期五

一直以来用着shell,也仅仅知道在命令行后加一个“&”来把一个作业放到后台。前几天看了APUE的作业控制,发现原来还可以对作业进行前后台切换和同步。

举个例子,在Shell下输入:

$cat > output &

然后终端会给你这样的返回:

 

[1] 3442
$

这个3442是cat进程的pid,1是这个作业的id。cat显然这个时候会需要从stdin获得输入,但是后台进程无法从读取终端,这时候会发一个SIGTTIN的信号,如果再次回车(或者敲一个不相干的命令),Shell就会提示你cat已经停止了。

[1]+ Stopped(SIGTTIN)        cat
$

如果我们想要cat继续执行,就必须把cat放到前台来继续运行,这时可以通过作业控制命令

fg %1

把标号为1的作业放到前台来运行。

同样的,有时候在前台执行了一个很耗计算,或是其他一直占据终端的程序,突然想起还可以先做别的事情,这时候不必把进程杀掉(否则前功尽弃),但除了再开一个终端之外,也有其他的方法可行。比如拿cat举例

cat

但这时候会阻塞终端要求输入。如果这是执行按下Ctrl+Z,会退回shell,但是前台进程并没有被杀掉,只是被暂停了,同时终端会显示

[1]+ Stopped(SIGTSTP)      cat

接下来如果要把cat放到后台运行(当然这很蛋疼),可以通过

bg %1

同时,不论进程是被暂停还是被放到后台,都可以通过

fg %1

来把进程再次放回前台。

其实这些技巧都是当年还没有X,终端占据整个屏幕的时候才用的上的(就算如此,还可以Ctrl+Alt+Fn切换tty),现在完全可以打开十几个X终端放在桌面上。但作业之间的同步在写Shell脚本的时候还是可以用得上的。一个简单的例子

sleep 10 & pid=$!
wait $pid

十秒之后,Shell会提示我们1号后台作业已经结束,同时wait也会停止:

[1]-  Done            sleep 10

曾经有某人跟我说过通过轮询检查

while(ps | grep $pid)
do
    sleep 1
done

来等待指定进程,相比之下还是wait方便。但执行脚本的时候wait会一直阻塞,而轮询的方法中间等待的sleep 1可以被换成其他有意义的事情,不过一般的shell脚本还没有这么高级的需求。

关于这方面更多的细节可以参考Advanced Programming in the UNIX Environment第九章 进程关系。

成功SSL认证,ssl.thynson.me自动走https协议

2011年3月9日 星期三

首先严重感谢Tydus(tydus.org)。

从StartSSL上搞到了证书,具体过程就不再详述。

证书给认证两个域名,于是认证了thynson.me和ssl.thynson.me,然后就在想办法把发往http(s)://ssl.thynson.me/的请求全部转向https://thynson.me。一开始想在http和https的配置中用正则匹配出ssl.thynson.me然后转发到https://thynson.me,后来突然想到可以直接加一节server,专门针对http(s)://ssl.thynson.me,然后直接一个rewrite请求转发到https://thynson.me。试了一下果然效果良好。

一般有ssl的站点nginx配置文件大概是这样子

server
{
    listen 0.0.0.0:80;
    server_name example.com;      #http://example.com
    ....
}
 
server
{
    listen 0.0.0.0:443;
    server_name example.com;      #https://example.com
    ....
    #ssl_stuff
}

如果要把http://ssl.example.com转发到HTTPS,只要再加一节:

server
{
    listen 0.0.0.0:80;
    listen 0.0.0.0:443;
    server_name ssl.example.com; #http://ssl.example.com
    #ssl_stuff.....
    rewrite ^ https://example.com/$request_uri? permanent;
}

也就是匹配所有url直接rewrite成https://thynson.me/….。

 

Powered by Wordpress and MySQL. Theme by Shlomi Noach, openark.org

Improved for Chinese by Thynson, fork this themes at github