Skip to content

PTY介绍和应用实践

Published:

从run_erl的实现原理中,我们看到了一个概念pty。那么pty是什么呢?看看AI的回答:

PTY(Pseudo Terminal,伪终端) 是 Linux/Unix 系统中提供的一种特殊的终端设备,它可以让程序像在真实终端(TTY)中运行一样,但实际数据的输入输出是通过程序进行控制的。

换句话说,PTY 让一个程序可以充当“虚拟用户”,与另一个程序进行交互,就像一个真实用户在终端里输入命令一样。

PTY 由 两个部分 组成:

Master 端(主端,通常叫 master_fd)

Slave 端(从端,通常叫 slave_fd)

它们成对存在,数据从 master_fd 读出,会出现在 slave_fd 里,反之亦然。

有了概念后,我们再重新看看run_erl和erl -detached的表现。

分别输入

run_erl -daemon /tmp/erlang_pipe/ /tmp/erlang_log/ "erl"

erl -name [email protected] -setcookie mycookie -detached

然后ps看一下进程信息,输入ps aux| grep beam

$ps aux| grep beam
root     18381  0.0  1.0 2207560 19340 pts/2   Ssl+ Mar27   0:01 /usr/local/lib/erlang/erts-10.7.2.19/bin/beam.smp -- -root /usr/local/lib/erlang -progname erl -- -home /root --
root     24781  0.8  0.8 2201888 16640 ?       Sl   14:54   0:00 /usr/local/lib/erlang/erts-10.7.2.19/bin/beam.smp -- -root /usr/local/lib/erlang -progname erl -- -home /r
oot -- -name [email protected] -setcookie mycookie -noshell -noinput --

由run_erl启动的erlang进程有一个标识“pts/2”,而使用detached的erlang进程则显示“?”。

pts/2就是设备名称,对应/dev/pts/2

?意味着该进程不依赖于任何终端输入输出设备。常见的情况是后台进程、系统服务。

那么如果我是python开发者,并没有run_erl这种工具,那么怎样才能实现这种模拟终端效果呢?

Linux上最常见的工具就是screen。

我们试下使用screen启动python,输入screen -S my_erlang_session python

$screen -S my_erlang_session python
Python 2.7.5 (default, Nov 14 2023, 16:14:06) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-44)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 

成功启动python,按Ctrl + A,然后按 D,退出screen。

ps看一下进程信息,输入ps aux | grep python

$ps aux | grep python
root     29041  0.0  0.0 125884  1424 ?        Ss   15:42   0:00 SCREEN -S my_erlang_session python
root     29042  0.0  0.2 130728  4828 pts/6    Ss+  15:42   0:00 python

从进程信息中,我们再次看到了pts/6?,说明screen是以后台进程的形式运行,而python则是pty形式。

输入screen -r my_erlang_session,回到python控制台。

$screen -r my_erlang_session
Python 2.7.5 (default, Nov 14 2023, 16:14:06)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-44)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 

run_erl和screen都是采用pty形式,实现原理差不多。

这里我们试试使用python结合pty实现一个小工具,通过telnet与erlang交互。 新建server.py文件,输入以下代码(AI加持):

# -*- coding: utf-8 -*-
import sys
import os
import socket
import select
import threading
import pty
import subprocess

HOST = "0.0.0.0"  # 监听所有 IP
PORT = 4000       # 端口号

def handle_client(conn, master_fd):
    try:
        while True:
            ready, _, _ = select.select([conn, master_fd], [], [])

            # 处理客户端输入
            if conn in ready:
                data = conn.recv(1024)
                if not data:
                    break
                os.write(master_fd, data)  # 发送到 Erlang 进程

            # 读取 Erlang 的输出
            if master_fd in ready:
                try:
                    output = os.read(master_fd, 1024)
                    if output:
                        conn.sendall(output)  # 发送回客户端
                except OSError:
                    break
    finally:
        conn.close()

def run_server(cmd):
    master_fd, slave_fd = pty.openpty()
    process = subprocess.Popen([cmd], stdin=slave_fd, stdout=slave_fd, stderr=slave_fd, close_fds=True)

    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server_socket.bind((HOST, PORT))
    server_socket.listen(5)
    print("Run Cmd: {}".format(cmd))
    print("Server started. Host: {} Port:{}".format(HOST, PORT))

    try:
        while True:
            conn, addr = server_socket.accept()
            print("Connection from {}".format(addr))
            threading.Thread(target=handle_client, args=(conn, master_fd)).start()
    except KeyboardInterrupt:
        print("\nServer shutting down.")
    finally:
        server_socket.close()
        process.terminate()

if __name__ == "__main__":
    run_server(sys.argv[1])

保存然后运行,输入python server.py "/usr/local/bin/erl"

$python server.py "/usr/local/bin/erl"
Run Cmd: /usr/local/bin/erl
Server started. Host: 0.0.0.0 Port:4000

启动成功,ps看下进程信息,输入ps aux | grep erl

$ps aux | grep erl
root       442  0.4  0.4 141960  8076 pts/1    S+   16:35   0:00 python server.py /usr/local/bin/erl
root       443  2.4  0.9 2206496 17792 pts/1   Sl+  16:35   0:00 /usr/local/lib/erlang/erts-10.7.2.19/bin/beam.smp -- -root /usr/local/lib/erlang -progname erl -- -home /root --

再次看到pts/1,但没有?,因为这里没有实现python放后台运行。

现在通过telnet连接erlang,输入telnet 127.0.0.1 4000

$telnet 127.0.0.1 4000
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Erlang/OTP 22 [erts-10.7.2.19] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1]

Eshell V10.7.2.19  (abort with ^G)
1>  

看到熟悉的Erlang shell,输入self().

1> self().
self().
<0.79.0>
2> 

正常运行。


Previous Post
如何优雅地调整时间?faketime的使用和原理分析
Next Post
Erlang 工具run_erl使用介绍和原理