Bootstrap

三、网络服务器之inetd/xinetd服务

我们不难发现,前两章的例子有一个共同点:他们同时只能为一个单一客户端服务,但这样做导致我们的机器被大多数空闲的进程消耗掉大量内存,这是不符合实际的。我们有很多方法可以解决这个问题,一个方法使使用内部方法来处理多客户端(具体如何实现后续章节将进行介绍),另一个方法使每次有新客户端连接的时候,就启用一个服务器拷贝。

UNIX和类UNIX操作系统提供了一个叫做inted或xinetd的程序来解决这个问题。

注意:由于微软并没有随Windows捆绑任何inetd程序,本章节内容并不适用于Windows平台,若读者感兴趣,可自行安装Unix或Linux环境(虚拟机)对本章代码进行测试。

3.1 inetd/xinetd简介

inetd和xinetd都是Unix/Linux系统上的守护进程,用于管理网络服务。将inetd/xinetd程序打开,绑定、监听和接受来自服务器每一个端口的请求。当有客户端连接的时候,inted/xinted会根据客户端信息到达的端口号判断请求的是哪一个服务,接着inetd/xinetd会调用服务器程序并把socket传给它。

inetd(Internet service daemon)是最早的网络服务管理器,它在系统启动时启动并监听指定的网络端口。当有连接请求到达时,inetd会根据配置文件(通常是/etc/inetd.conf)启动相应的服务程序来处理连接。

xinetd(Extended Internet service daemon)是inetd的增强版,它提供了更多的功能和配置选项。xinetd在inetd的基础上增加了许多功能,包括访问控制、资源限制、日志记录等。xinetd的配置文件通常位于/etc/xinetd.conf或/etc/xinetd.d/目录下。我们接下来主要介绍xinetd服务的配置及相关应用。

3.2 配置xinetd

3.2.1 xinetd的基本服务选项

选项名称描述用法示例
service定义一个服务的开始,通常包括服务的名称和协议类型service echo {...}
socket_type定义套接字类型,可以是 stream(流套接字,如 TCP)或 dgram(数据报套接字,如 UDP)socket_type = stream
type服务类型,通常是 UNLISTED 或 INTERNAL。使用UNLISTED定义一个不再/etc/services列表上,而是在 xinetd 的配置文件中直接定义的服务。type = UNLISTED
flag特殊标记,例如 REUSE 允许重用套接字flag=REUSE
port如果设置了type = UNLISTED,必须再指定端口号port=12345
protocol定义使用的协议,可以是 tcp 或 udpprotocol = tcp
wait指定服务是否等待(等待客户端连接关闭前不接受新连接)。对于 dgram 服务,通常设置为 yes;对于 stream 服务,设置为 nowait = no
user指定运行服务的用户。user = nobody
group指定运行服务的用户组。group = nogroup
server指定提供服务的可执行文件路径server = /usr/sbin/in.telnetd
server_args指定传递给服务器程序的参数。server_args = -L /bin/login
log_type指定日志记录的类型,可以是 SYSLOG 或 FILE。log_type = SYSLOG daemon info
log_on_success指定记录成功连接的日志信息,包括 PID, HOST, USERID 等。log_on_success = HOST PID
log_on_failure指定记录失败连接的日志信息。log_on_failure = HOST
disale可以设置为 yes 或 no 来表示是否禁用该服务。disable = no
onlyfrom只允许特定主机访问服务。only_from = 192.168.0.0/24
no_access拒绝特定主机访问服务。no_access = 192.168.0.5
per_source限制每个源地址的最大连接数。per_source = 10

3.2.2 配置xinetd服务

1. 安装xinetd
确保系统上安装了xinetd

sudo apt-get update
sudo apt-get install xinetd

sudo apt-get update是一个关键的维护命令,它确保你的系统包管理器拥有最新的包信息,从而使你能够安装和更新最新版本的软件包。
sudo apt-get install xinetd是一个用于在 Ubuntu 或其他基于 Debian 的 Linux 发行版上安装 xinetd 软件包的命令。可以在 Ubuntu 或其他基于 Debian 的系统上安装 xinetd 服务管理器。

2. 创建Python服务脚本

#!/usr/bin/env python3
import sys

print("Hello from the Python service!")

#!/usr/bin/env python3告诉系统使用 Python 3 解释器来执行这个脚本。其中#!/usr/bin/env是一个特殊的语法,通常称为 shebang(也称为 hashbang)。它告诉操作系统,接下来的文本应该使用指定的解释器来运行。

3. 赋予脚本执行权限

sudo chmod +x /home/vonphy/udp_echo_server.py

这条命令用于给 /usr/local/bin/helloservice.py 文件添加执行权限。具体含义如下:
sudo:以超级用户权限运行命令,通常需要输入管理员密码。
chmod:是改变文件或目录权限的命令。
+x:表示给文件添加执行权限。
/usr/local/bin/helloservice.py:是要添加执行权限的文件路径。

4. 配置目录文件
我们首先找到xinetd配置文件的位置(/etc/xinetd.d或/etc/xinetd.conf)。
打开终端,我们尝试使用文本编辑器(如nano、vi)编辑/etc/xinetd.conf文件:

sudo nano /etc/xinetd.conf

①若成功进入,则说明配置文件目录为/etc/xinetd.conf,我们直接进入文件进行配置即可。
在这里插入图片描述

②若提示目录不存在,则配置文件在/etc/xinetd.d。我们此时需要通过命令在该目录下新建一个文件,在新建的文件中进行配置

sudo nano /etc/xinetd.d/helloservice

配置内容示例如下:

service helloservice
{
    type        = UNLISTED
    port        = 12345
    socket_type = stream
    protocol    = tcp
    wait        = no
    user        = nobody
    server      = /home/vonphy/udp_echo_server.py
    log_on_failure  += USERID
    disable     = no
}

5. 启用xinetd服务
确保xinetd服务正在运行,并且配置文件没有错误:

sudo service xinetd restart

检查xinetd服务状态,确保它正在运行:

sudo service xinetd status

6. 测试服务
使用telnet或者其他工具连接到服务并测试它:

telnet localhost 12345

我们将看到输出"Hello from the Python service!"
在这里插入图片描述

3.3 通过xinetd使用socket对象

通常socket对象是由socket.socket()的调用建立的,如果我们的服务器程序是由inetd/xinetd启动的,我们则需要根据inetd/xinetd传递给程序的文件描述符,通过调用socket.fromfd()建立一个socket对象。换句话来说,假设我们在使用 xinetd 配置一个服务,并希望使用 Python 处理客户端连接。xinetd 会将客户端连接传递给服务脚本,这时你可以使用 socket.fromfd() 方法将文件描述符转换为套接字对象。

关于文件描述符的详细说明请参考拓展一 文件描述符

下面将展示如何配置 xinetd 并使用 socket.fromfd() 在 Python 中处理客户端连接。

1. 编写服务脚本
依旧创建一个 Python 脚本 helloservice.py,该脚本将使用 socket.fromfd() 处理客户端连接:

#!usr/bin/env python3
import socket
import sys

# 获取标准输入的文件描述符
fd = sys.stdin.fileno()
# 从文件描述符创建套接字对象
sock = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM)
# 处理客户端请求
try:
    print('Hello from helloservice')
    sock.sendall(b'Hello from helloservice')
    while True:
        data = sock.recv()
        if not data:
            break
        print(f'Received: {data.decode()}')
        sock.sendall(b'Echo:' + data)
finally:
    sock.close()

确保该脚本文件具有可执行权限

sudo chmod +x /home/vonphy/udp_echo_server.py

2. 配置xinetd
配置方法前面已经进行介绍,配置内容如下:

service helloservice
{
    type        = UNLISTED
    port        = 12345
    socket_type = stream
    protocol    = tcp
    wait        = no
    user        = nobody
    server      = /home/vonphy/udp_echo_server.py
    server_args = 
    disable     = no
    flags       = REUSE
}

配置选项说明

选项说明
type = UNLISTED服务未列在 /etc/services 中。
port = 12345服务监听的端口号。
socket_type = stream指定套接字类型为流(即 TCP)。
protocol = tcp使用 TCP 协议。
wait = noxinetd 不会等待当前服务结束后再接受新请求。
user = nobody以 nobody 用户的身份运行服务。
server = /home/vonphy/udp_echo_server.py指定服务程序的路径。
server_args传递给服务程序的参数(此处为空)。
disable = no不禁用(启用)该服务。
flags = REUSE允许重用套接字。

3. 启动或重启 xinetd
4. 测试服务
①使用telnet测试

telnet localhost 12345

你应该会看到 Hello from helloservice! 消息,并且可以发送消息到服务脚本,脚本会将接收到的消息回显。
在这里插入图片描述
②使用netcat测试

echo -n "Hello World" | nc localhost 12345

会看到 Echo: Hello World 消息。
在这里插入图片描述

3.4 通过xinetd使用UDP

通过 xinetd 使用 UDP 协议来配置服务是一种常见的网络服务部署方式。下面详细介绍如何配置 xinetd 来处理 UDP 服务,包括配置文件的编写与服务的测试。

1. 编写UDP脚本
以下程序是一个简单的Echo服务
所在路径:/home/vonphy/udp_echo_server.py

import socket

host = 'localhost'
port = 12345
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((host, port))
print(f'UDP Echo Server started on {host}:{port}')
while True:
    data, addr = sock.recvfrom(1024)
    print(f'Received message from {addr}:{data.decode("utf-8")}')
    sock.sendto(data, addr)

确保该脚本是可执行的:

sudo chmod +x /home/vonphy/udp_echo_server.py

2. xinetd配置内容

service udp_echo
{
    type = UNLISTED
    port = 12345
    socket_type = dgram
    protocol = udp
    wait = yes
    user = nobody
    server = /home/vonphy/udp_echo_server.py
    disable = no
}

3.重新启动xinetd
4.测试 UDP 服务
为了确保我们可以看到服务端的输出,我们需要直接运行服务脚本,而不是通过xinetd。打开一个终端运行脚本。

python3 udp_echo_server.py

然后另外打开一个终端,使用 netcat (nc) 工具来测试配置的 UDP 服务

echo -n "Hello, UDP" | nc -u localhost 12345

在服务器端,我们能够看到接收到的消息并回显。
在这里插入图片描述

在 UDP 服务中通常设置 wait = yes ,是为了确保在处理每个数据报时,端口能够被阻塞,防止多个数据报同时处理,避免竞争条件,从而保证数据处理的正确性和顺序。这是 UDP 协议无连接特性和数据报独立性决定的。

;