初探

如果你平时用的 shell 是 bash, 那么可以用如下方式打开一个 socket 连接:

echo >/dev/tcp/{host}/{port}

比如如下命令:

echo >/dev/tcp/www.baidu.com/80

这个命令表示本机与 www.baidu.com 主机的 80 端口建立 socket 连接, 且不处理连接返回的信息. (这种无视返回信息的命令的意义在于查看目标主机与端口的开放状态)

这时有小伙伴可能在脑子里大致拼凑出这个命令的逻辑了: 在系统中的/dev/tcp/ 下创建对应主机和端口的文件夹, 保存 socket 连接文件, 然后用来通信.

然而这时候如果你去查看系统中的/dev/tcp/这个目录, 可能会发现事情并不简单:

[root@VM-12-13-centos ~]# ll /dev/tcp
ls: cannot access /dev/tcp: No such file or directory

惊不惊喜? 系统中根本没有这个路径.

其实 /dev/tcp/ 只是 bash 给你设计的一个 feature, 并非是系统路径, 充其量只是长得像而已.

你可以理解成像这样子的参数式命令: echo > tcp -h www.baidu.com -p 80 (注: 此为伪代码, 并非实际命令, 仅仅用于理解)

btw, 我个人清楚却也非常不理解这样子的功能设计, /dev/tcp/ 这种写法很难让人不认为这是个 linux 设备文件, 而且万一在系统中建立相同的文件路径, 也无法进行 echo, 非常的傻. 也许这只是 bash 开发人员的恶趣味?

用法

那么这样一个奇怪的 bash 功能有什么用呢? 有很多工具比它更强更方便也更容易理解不是吗?

不错, 在 Linux 上打开 socket 连接的方式有很多, 比如 curl, wget, telnet等, 这些方式可以用于检查特定的地址/端口是否可达、获取远程网页、调试 restful API、连接远程服务器等等. 但是如果所在的环境中没有提供这些工具, 且环境要求严格, 不能随意安装第三方工具时该怎么办呢? 这时候就能用上这个 bash 自带的 socket 连接工具了.

其实不光有 /dev/tcp/ , 还有 /dev/udp/ , 因为使用方式类似便不再赘述, 本文均以 tcp 连接举例.

1. 打开 socket

格式: exec ${file-descriptor}${operation}/dev/${protocol}/${host}/${port}

作用是将文件描述符绑定一个 socket, 其中:

  • file-descriptor: 与每个 socket 关联的唯一的非负整数, 其中0,1和2在Linux/Mac中分别保留给 stdin, stdout 和 stderr, 所以要指定大于2的且未被使用的数字(可以在/proc/self/fd 下看到已使用的 fd)
  • operation: <>读写, <只读, >只写
  • protocol: tcp 或者 udp
  • host 与 port: 没什么好说的, 目标地址与端口

比如:

exec 4<>/dev/tcp/www.baidu.com/80

2. 写入/读取 socket

写入:

echo "GET" >&4

读取:

cat <&4

样例中的4是 file-description.

3. 关闭 socket

关闭写入:

exec 4>&-

关闭读取:

exec 4<&-

样例中的4是 file-description.

最后

所用 bash 版本:

[root@VM-12-13-centos ~]# bash --version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

实测在这个版本下, file-description 与 & , < , > 符号间不能有空格.

在其它博客中发现不少存在空格的命令, 不知是否是写错或者是版本问题, 在此不再深究.