17 使用socket文件和TCP实现进程间通信

就像我之前提到的,zsh 脚本是可以直接使用 socket 文件(UNIX domain socket 所使用)或者 TCP 和其他进程通信的。如果进程都在本地,用 socket 文件效率更高些,并且不要占用端口,权限也更好控制。如果是在不同机器,可以使用 TCP。

Socket 文件

UNIX domain socket 是比管道更先进的进程通信方法,是全双工的方式,并且稳定性更好。但性能比管道差一些,不过一般性能瓶颈都不会出现在这里,不用考虑性能问题。而且在一个 socket 文件上可以建立多个连接,更容易管理。另外如果通信方式从 socket 文件改成 TCP,只需要修改很少的代码(建立和关闭连接的代码稍微改一下),而从管道改成 TCP 则要麻烦很多。

所以建议用 zsh 写进程交互脚本的话,直接使用 socket 文件,而不是命名管道(匿名管道就能满足需求的简单场景忽略不计)。

Socket 文件的用法:

# 监听连接端
# 首先要加载 socket 模块
% zmodload zsh/net/socket

% zsocket -l test.sock
% listenfd=$REPLY
# 此处阻塞等待连接
% zsocket -a $listenfd
# 连接建立完成
% fd=$REPLY
% echo $fd
5

# 然后 $fd 就可读可写
% cat <&$fd
good
# 发起连接端
# 首先要加载 socket 模块
% zmodload zsh/net/socket

% zsocket test.sock
# 连接建立完成
% fd=$REPLY
% echo $fd
4

# 然后 $fd 就可读可写
% echo good >&$fd

连接建立后,怎么用就随意了。实际使用时,要判断 fd 看连接是否正常建立了。通常使用 socket 文件要比在网络环境使用 TCP 稳定性高很多,一般不会连接中断或者出其他异常。另外可以在 zsocket 后加 -v 参数,查看详细的信息(比如使用的 fd 号)。

关闭连接:

# 发起连接端
# fd 是之前存放 fd 号的变量,不需要加 $
% exec {fd}>&-

# 监听连接端
% exec {listenfd}>&-
% exec {fd}>&-
# 删除 socket 文件即可,如果下次再使用会重新创建,该文件不能重复使用
% rm test.sock

TCP

使用 TCP 连接的方式和使用 socket 文件基本一样。

# 监听连接端
# 首先要加载 tcp 模块
% zmodload zsh/net/tcp

% ztcp -l 1234
% listenfd=$REPLY
# 此处阻塞等待连接
% ztcp -a $listenfd
# 连接建立完成
% fd=$REPLY
% echo $fd
3

# 然后 $fd 就可读可写
% cat <&$fd
good
# 发起连接端
# 首先要加载 tcp 模块
% zmodload zsh/net/tcp

% ztcp 127.0.0.1 1234
# 连接建立完成
% fd=$REPLY
% echo $fd
3

# 然后 $fd 就可读可写
% echo good >&$fd

关闭连接:

# 发起连接端
# fd 是之前存放 fd 号的变量
% ztcp -c $fd

# 监听连接端
% ztcp -c $listenfd
% ztcp -c $fd

程序样例

recv_tcp,监听指定端口,并输出发送过来的消息。使用方法:recv_tcp 端口

#!/bin/zsh

zmodload zsh/net/tcp

(($+1)) || {
    echo "Usage: ${0:t} port"
    exit 1
}

ztcp -l $1
listenfd=$REPLY

[[ $listenfd == <-> ]] || exit 1

while ((1)) {
    ztcp -a $listenfd
    fd=$REPLY
    [[ $fd == <-> ]] || continue

    cat <&$fd
    ztcp -c $fd
}

send_tcp,用来向指定机器的指定端口发一条消息。使用方法:send_tcp 机器名 端口 消息 (机器名可选,如果没有则发到本机,消息可以包含空格)

#!/bin/zsh

zmodload zsh/net/tcp

(($# >= 2)) || {
    echo "Usage: ${0:t} [hostname] port message"
    exit 1
}

if [[ $1 == <0-65535> ]] {
    ztcp 127.0.0.1 $1
} else {
    ztcp $1 $2
    shift
}

fd=$REPLY
[[ "$fd" == <-> ]] || exit 1

echo ${*[2,-1]} >&$fd
ztcp -c $fd

总结

本文介绍了使用 socket 文件或者 TCP 来实现两个脚本之间通信的方法。