在 bash 中编写 TCP 服务器

在 bash 中编写 TCP 服务器

所以,我最近发现了如何通过文件发送 TCP 数据包/dev/tcp/hostname/port,但我的主要问题是如何接收它们。我想制作一个可以托管 TCP 服务器的 bash 脚本,以及另一个作为 TCP 客户端的脚本。一些帮助会很好。我还想指出,我这样做是作为一种学习经历,这就是为什么我不使用sshtelnet作为执行我想要的任务的方式。

答案1

根据, 不。你做不到,因为bash你没有尝试bind(2)套接字,但只是尝试connect(2)

以下是启动netcat服务器的几种方法:

(我用nmapncat因为GNUnc自 2004 年以来就没有看到更新)


local $ :|{  ncat -l 9999 --keep-open --allow localhost |
local >      PS1='ncat $ '  PS2='ncat > ' dash +m -i 2>&1
local >   }  1<>/dev/fd/0
[1] + Running  : | { ncat -l 9999 --keep-open --allow localhost | PS1='ncat $ ' PS2='ncat > ' dash +m -i 2>&1; } 1<>/dev/fd/0

我借用了:|最后的管道 -dash写入它并ncat读取它,因为:null 命令当然不需要它。有时比 更容易mkfifo

无论如何,重点是所有ncat写入的内容都通过管道传输到dash的标准输入,并且所有dash写入的内容都通过管道传输到ncat的标准输入。我知道,这不是bash,但我会解决这个问题。但至少请注意该+m选项。


local $ ncat localhost 9999
ncat $ echo $0 $$
dash 31231
ncat $ ls -l /dev/fd/[012]
lr-x------ 1 mikeserv mikeserv 64 Dec 11 23:28 /dev/fd/0 -> pipe:[3563412]
l-wx------ 1 mikeserv mikeserv 64 Dec 11 23:28 /dev/fd/1 -> pipe:[3567682]
l-wx------ 1 mikeserv mikeserv 64 Dec 11 23:28 /dev/fd/2 -> pipe:[3567682]
ncat $ ^C
local $ 

我必须使用,+m因为-m受监控的作业控制 shell - 通常默认情况下将-i交互式 shell 设置为打开 - 会尝试从其控制终端读取数据,并且会发送一个SIGTTIN- 默认情况下会自动挂起它,或者别的(如果它试图忽略或阻止它)杀了它。


[1] + Stopped(SIGTTIN) : | { ncat -l 9999 | dash -i 2>&1; } 1<>/dev/fd/0

但这里没有终端 - 正如您在输出中看到的那样,这些只是管道ls- 因此-m监视是不行的。

bash碰巧的是,即使在禁用状态下,其初始交互式终端关联似乎也没有那么灵活readline


[1] + Stopped(SIGTTIN) : | { ncat -l 9999 | bash --noediting +m -i 2>&1 ; } 1<>/dev/fd/0

那么该怎么办?好吧,我们需要一个伪终端。就像我们在模拟器中的东西一样。


local $ ls -l /dev/fd/[012]
lrwx------ 1 mikeserv mikeserv 64 Dec 11 23:30 /dev/fd/0 -> /dev/pts/0
lrwx------ 1 mikeserv mikeserv 64 Dec 11 23:30 /dev/fd/1 -> /dev/pts/0
lrwx------ 1 mikeserv mikeserv 64 Dec 11 23:30 /dev/fd/2 -> /dev/pts/0

/dev/ptmx我们可以从Linux 系统上的主设备获取一个tty(尽管如果您不想先成为该组的成员,您的用户可能需要成为该组的成员chown。要是我们< open(2)ptmx 设备将创建一个新的伪终端,如果我们之后unlockpt(3)新的设备文件,我们可以对其进行读写。

不久前,我了解到使用 shell 确实没有一种简单的方法可以做到这一点,所以我为它写了一个小C程序。您会找到一些解释和简单的构建说明(实际上只是复制/粘贴到 shell 提示符中)在那个链接 - 这是pts我在下面使用的程序。


local $ { 9>&- setsid -c -- bash <> "$({ pts && 
local >   >&9  ncat -l 9999 -k --allow localhost
local > } <&9  &)" 2>&0 >&2 ; } 9<> /dev/ptmx
local $

有一个bash开始为会议主持人在伪终端上pts解锁并命名为其标准输出 - 它替换了<>重定向中的标准bash。不过,显然什么也没有发生,因为所有的 i/o 都在其他地方 - 新的伪终端 - 唯一的链接是ncat在端口 9999 上启动的服务器。-i这里不需要交互式开关 -bash将自动在它自己的有限公司。


local $ ncat localhost 9999
[mikeserv@desktop ~]$ echo hey
echo hey
hey
[mikeserv@desktop ~]$ ls -l /dev/fd/[012]
ls -l /dev/fd/[012]
lrwx------ 1 mikeserv mikeserv 64 Dec 12 00:27 /dev/fd/0 -> /dev/pts/4
lrwx------ 1 mikeserv mikeserv 64 Dec 12 00:27 /dev/fd/1 -> /dev/pts/4
lrwx------ 1 mikeserv mikeserv 64 Dec 12 00:27 /dev/fd/2 -> /dev/pts/4
[mikeserv@desktop ~]$ ^[[A^[[A

好吧,我们快到了。所以我们肯定有一个套接字化的bash- ,但你可能注意到那里有奇怪的双回声,最后一个提示符处有趣的转义实际上是我按下了 ^up 箭头键。这里的问题是我们有两级伪终端 - 我的ncat客户端运行的伪终端设置stty echo为与服务器运行的伪终端相同。而且由于客户端终端是面向行的,并且每个输入换行符刷新输出一次,并且还根据默认stty echoctl设置打印 ctrl-char 转义符,bash因此根本不会收到 ^up 箭头键,我们只看到有趣的小逃脱。

好的。我们也可以处理这个问题。


local $ ncatsh()(
local >    ${2+":"} set  localhost "${1:?ncatsh(): No port number provided!}"
local >    stty="   stty -F'$(tty)'" || unset stty
local >    trap " ${stty- ncat '${1##*\'*}' '${2##*\'*}' </dev/null} \
local >           ${stty+$(for a in -g 'raw -echo isig intr "^A" quit "" susp ""'
local >                    do  eval "$stty $a";done)}
local >             trap - 0 1 2; exit"    0 1 2
local >    ncat  "$@"
local > )

该函数将检查其标准输入是否为终端(您的本地终端),如果不是,则仅将输入传递给客户ncat端。但如果是这样的trap出口,挂断, 和打断信号在退出时恢复其标准的终端状态。这是一件好事,因为它也改变ncat在调用标准之前的状态。

本地回显被禁用,本地终端否则设置为生的模式 - 因此每次按键都会ncat立即发送到服务器。事实上,所有特殊的本地模式键都被禁用除了本地的整数- 通常是CTRL+C,但这里配置为CTRL+A相反 - 因为CTRL+C将由bash服务器解释。

我会再做ls一次,但只需按 ^ 向上箭头即可RETURN


local $ ncatsh 9999

[mikeserv@desktop ~]$ ls -l /dev/fd/[012]
lrwx------ 1 mikeserv mikeserv 64 Dec 12 01:00 /dev/fd/0 -> /dev/pts/4
lrwx------ 1 mikeserv mikeserv 64 Dec 12 01:00 /dev/fd/1 -> /dev/pts/4
lrwx------ 1 mikeserv mikeserv 64 Dec 12 01:00 /dev/fd/2 -> /dev/pts/4
[mikeserv@desktop ~]$ ^C
[mikeserv@desktop ~]$ ^C
[mikeserv@desktop ~]$ ^C
[mikeserv@desktop ~]$
local $

你在那里看不到它,因为按下它时本地回显被禁用,但我按下了CTRL+A中断会话并返回本地提示符,此时所有本地终端配置都恢复为正确状态。不过,服务器bash仍然存在,ncatsh 9999如果我愿意的话,它会立即带我回去。

答案2

为了简化您的工作,请尝试使用“netcat”工具。它可以用作服务器或客户端,并且能够发送和接收任意数据。

那就不用费心处理低级的东西了..

#server listening on port 8000/tcp
$>netcat -l 8000

#client sending stuff to localhost 8000/tcp
$>netcat localhost 8000
blah

答案3

bash的虚拟/dev/tcp/host/port文件(最初是 ksh 功能)只能用于连接TCP 端口的套接字。

如果您想做更多事情,例如创建侦听套接字并从中生成接受套接字,则无法通过重定向来完成。

zshzsh/net/tcp通过其模块和内置对此提供支持ztcp

zmodload zsh/net/tcp
handle-connection() (
  IFS= read -ru4 line || exit
  print -r "Got: $line"
  print -ru4 "Hello $line"
  ztcp -c 4
)

ztcp -l -d 3 1234 # create a TCP listening socket on port 1234 on fd 3
while ztcp -ad4 3 # accept connection on fd 4
do handle-connection & exec 4>&-
done

它仍然受到限制,因为您无法指定要绑定的地址或侦听队列的大小,或仅关闭一个方向,或设置套接字选项...它也不执行 UDP 或 SCTP(您可以执行 Unix域套接字虽然使用zsocket命令)。

如果你想要更高级的东西或者如果你没有 zsh,你可以使用socat.例如,这里有bash导出函数:

handle_connection() {
  IFS= read -r line &&
    printf 'Hello %s\n' "$line"
}
export -f handle_connection
socat tcp-listen:1234,reuseaddr,fork 'exec:bash -c handle_connection'

使用socat,可能性是无限的,请参阅手册页了解详细信息。

例如,要让服务器生成 shell 会话:

socat tcp-listen:1234,reuseaddr,fork exec:bash,pty,ctty,setsid,stderr,sane

在客户端,您可以连接为:

socat -,raw,echo=0 tcp:host:1234

答案4

最简单的方法是创建自己的xinetd服务,如下所示:

/etc/services:

...
foo             500/tcp
...

/etc/xinetd.d/foo:

service foo
{
        disable         = no
        bind            = 127.0.0.1
        socket_type     = stream
        protocol        = tcp
        log_on_failure += USERID
        server          = /usr/local/lib/foo.sh
        user            = il
        instances       = UNLIMITED
        wait            = no
        log_on_success  =
}

/usr/local/lib/foo.sh:

read ...
...
echo ...

因此,您的 shell 脚本会针对每个客户端连接执行,并将 stdin/stdout 直接附加到套接字。

相关内容