`
guanhuaing
  • 浏览: 1239472 次
文章分类
社区版块
存档分类
最新评论

Linux Socket学习(十五)

 
阅读更多
使用inetd守护进程

运行在Unix下在的第一个服务器通常都会提供一个作为单独进程运行的服务。然而,当要提供的服务数量变得很大时,这会成为系统的一个负担。这是因为资源必须与每一个正在运行的服务器进程相关联,甚至是对当前正在提供的服务并没有请求时也是如此。

另外,我们可以观察到大多数据的服务器程序使用通常的进程来创建,绑定,监听,与接受新的客户连接。对于无连接的服务器操作与是相似的观察结果。

在这一章,我们将会了解以下内容:
什么是inetd守护进程?
inetd如何解决资源使用问题?
inetd如何简化服务器编写?

大多数服务器的通常步骤

如果我们回忆一下第8章,服务器的面向连接协议,我们就会回忆起一个面向连接的服务器用来建立与一个客户的连接的基本步骤如下:

1 创建一个套接口
2 将套接口绑定到一个已知的地址
3 监听客户端连接
4 接受客户端连接

现 在考虑一下两个不同的服务器,也就是telnet客户的telnetd,与ftp客户的ftpd。对于这两个服务器步骤1到步骤4有什么不同吗?答案是对 两者几乎相同。我们将会了解到inetd守护进程可以为任何的面向连接的服务器执行初始化步骤,节省了服务器编写者为这些步骤编写与调试代码的时间。 ineted守护进程的思想也可以扩展到处理无连接服务器的情况。

inetd简介

当我们的Linux系统第一次启动时,inetd守护进程是由一个启动脚本来启动的。

当inetd守护进程第一次启动时,他必须知道他要监听的网络服务,以及当请求到达时应将请求发送到哪个服务器。这是在启动文件/etc/inetd.conf文件是进行配置的。

/etc/inetd.conf配置文件

/etc/inetd.conf的文件而已组织为一个文本文件,每一个文本行代表一个记录,这个记录描述了一个网络服务。以#开始的行为注释。

文件描述如下表所示:

/etc/inetd.conf配置记录

域 描述 例子
1 网络服务名 telnet
2 套接口类型 stream,dgram
3 协议 tcp或udp
4 标记 nowait或wait
5 使用的用户ID root或nobody
6 可执行的路径名 /usr/sbin/in.telnetd
7 服务器参数 in.telnetd


网络服务名域

/etc/inetd.confi记录的网络服务名域只是/etc/services文件中一个简单的网络服务名,这个文件我们在第7章,客户端的面向无连接的协议,中进行了讨论。我们可以快速的查看一下/etc/services文件中的内容:

# grep telnet /etc/services
telnet 23/tcp
rtelnet 107/tcp # Remote Telnet
rtelnet 107/udp
#

在这里我们可以看,标记为telnet的服务配置为在端口号23上的一个tcp服务。这就是inetd守护进程如何来决他必须在哪个端口上进行监听。

相应的,我们也可以简单的指定一个端口号。我们将会在这一章的后面看到一个这样的例子。

套接口类型域

尽管Linux inetd守护进程可以接受许多的套接口类型,但是为了简单起见,我们在这里只讨论stream或是dgram。对于那些感兴趣的读者,inetd man手册页同时也列出raw,rdm以及seqpacket等其他可能的套接口类型。

stream类型对应着socket函数调用中的SOCK_STREAM类型。而dgram则对应着SOCK_DGRAM套接口类型。

协议域

正如我们所想到的,这会为套接口选择所使用的协议。这个值必须是出现在/etc/protocols文件的一个可用的实体。两个常用到的选择为:
TCP协议的tcp
UDP协议的udp

也存在其他的可能协议,但是这两个是最常用到的。

标记域

这个域只为数据报套接口可用。非数据报套接口(例如stream tcp)必须将这个值定为nowait。

面向数据报的服务器可以为两种类型。他们是:

持续读取UDP数据包直到超时退出的服务器(为这些类型指定wait)
读取一个数据包然后退出的服务器(为这些类型指定nowait)

这些信息是inetd所需要的,因为dgram的通信处理要面向流协议的处理复杂得多。这有助于守护进程确定当某一服务的服务器正在运行时如何处理以后的dgram连接。这一点我们会在后面进行详细的讨论。

用户ID域

inetd在root帐户下运行。如果需要,他可以将其标识改为其他帐户。为了安全的目的,推荐使用为完成工作所需权限最小的帐户来运行服务器。从而,服务器通常服务器运行在一个更为受限的用户ID下,如nobody。

然而,一些服务器必须以root用户来运行,所以有时我们会看到用户ID指定这种方式。

路径名域

这个域只是简单的通知inetd可执行文件的路径名是什么。这是守护进程调用fork之后由exec来执行的可执行文件。

服务器参数域

/etc/inetd.conf配置文件其余的域指定了要由exec所调用的服务器的命令行参数。一个常会引起迷惑的地方就是这些参数是由参数argv[0]来启动的。这会使得命令名与路径名相区别。当一个可执行文件依据其名字来限制不同的特性时,这是十分用的。

inetd服务器的设计参数

使 用inetd作为服务器前端的一个优点就是可以简化服务器编写者的工作。例如,对于stream tcp服务器不再有相同的socket,bind,listen,accpet调用的负担。相似的代码节省也可以用于dgram udp服务器。那么,当一个进程启动后,inetd服务器是如何处理连接到服务器进程的套接口的呢?

Unix简单优雅的作法是,启动的服务器在下面的文件单元(文件描述符)上处理客户端套接口:

文件单元0作为标准输入的客户端套接口
文件单元1作为标准输出的客户端套接口
文件单元2作为标准错误的客户端套接口

使用这样的设计,服务器就可以不需要单一的套接口函数调用。所有的服务器I/O可以全部在通常的标准输入,标准输出,标准错误文件单元上来执行。在后面,我们将会用一个程序来演示如何用这种方式来使用标准输出。

实现一个简单的stream tcp服务器

我们也许会回忆起第8章所介绍的一个小程序。现在我们稍做休息来回忆一下这个程序。下面是这个相同服务器的新代码,所不同的是使用inetd守护进程来设计的。
/*
* inetdserv.c
*
* Example inetd daytime server:
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>

/*
* This function reports the error and
* exits back to the shell:
*/
static void bail(char *on_what)
{
if( erron != 0 )
{
fputs(strerror(errno),stderr);
fputs(": ",stderr);
}
fputs(on_what,stderr);
fputc('/n',stderr);
exit(1);
}

int main(int argc,char **argv)
{
int z;
int n;
time_t td; /* Current date&time */
char dtbuf[128]; /* Date/Time info */

/*
* Generate a time stamp:
*/
time(&td);
n = (int) strftime(dtbuf,sizeof dtbuf,
"%A %b %d %H:%M:%S %Y/n",
localtime(&td));

/*
* Write result back to the client:
*/
z = write(1,dtbuf,n);
if(z==-1)
bail("write(2)");

return 0;
}

我们可以注意到,与第8章的程序相比,这个程序是多么的简单。注意下面的几点不同:
不再需要套接口的include文件
不再需要套接口地址结构
不再需要套接口调用。注意这个程序可以立即开始工作。

因为这个程序不再使用套接品函数,所以可以很容易的由Shell来进行测试:

$ make inetdserv
gcc -c -D_GNU_SOURCE -Wall -Wreturn-type inetdserv.c
gcc inetdserv.o -o inetdserv
$ ./inetdserv
Tuesday Nov 02 16:29:45 1999
$

这与13号端口上的daytime服务相类似:

$ telnet 192.168.0.1 13
Trying 192.168.0.1 . . .
Connected to 192.168.0.1.
Escape character is '^]'.
Tue Nov 2 16:31:09 1999
Connection closed by foreign host.
$

格式上唯一的真正的不同就在于我们的程序显示的全部的星期名字。现在是时候来演示这个程序如何来使用inetd守护进程。

配置/etc/inetd.conf来调用一个新的服务器

为了使得我们的简单的新服务器有用(或者至少与daytime作用相似),我们必须修改为inetd守护进程所用的配置文件。如果需要,我们可以回顾一下上面所介绍的配置表的内容。

创建可执行文件

现在这里我们假设我们在前面已经编译了inetdserv程序。为了简化步骤,在这里我们输入下面的命令:

$ cp inetdserv /tmp/inetdserv
$ chmod a+rx /tmp/inetdserv

前面的两个步骤将服务器拷贝到一个已知的地方,并且保证他是可执行的。

创建服务

为了这个测试,在/etc/inetd.conf文件中添加一行(将其添加到最后一行)。我们可以使用vi或是其他的编辑器来完成这个操作。其内容如下:

$ tail -1 /etc/inetd.conf
9099 stream tcp nowait root /tmp/inetdserv inetdserv

下面让我们来回顾一下,这一行对于inetd意味着什么:

因为我们的新服务并不拥有一个在/etc/services文件中存在的名字,第一个域只是简单的包含我们希望使用的端口号。在这里我们选择9099。
在第二个域包含stream,所以会使用流式套接口。
在第三个域中包含tcp,来表明我们希望一个TCP流,而不是其他的协议流。
第四个域指定为nowait,这是TCP流所必需的。
第五个域指定为root。我们也可以使用通常的用户ID(但是必须保证有合适的权限来执行/tmp/inetdserv)。
路径名/tmp/inetdserv指定为第六个域。这是连接到达套接口时将会执行的可执行文件的路径。
第七个域指定为inetdserv。在这个例子中,我们的服务器程序并不会关注argv[0]的值,所以这里可以是任何值。

现在,在我们实际连接到这个服务之前,执行一步额外的测试来确认一切正常:
$ /tmp/inetdserv
Tuesday Nov 02 16:52:33 1999
$

如果我们没有得到输出结果,检查来确认我们是否使用了正确的文件名来拷贝文件。另外,确认程序有正确的可执行权限。在完成这些演示的功能之后,我们已经准备好让inetd知道我们已经对其配置文件做了更改。

通知inetd配置更改

要通知inetd发生了更改,我们必须切换到root并且执行下面的操作:

# ps -ax | grep inetd
314 ? S 0:00 inetd
# kill -HUP 314
#

我们的进程ID也许不是314,事实上,很有可能不同。所需要用到的步骤如下:

1 使用ps命令并使用grep命令过滤列出inetd守护进程的进程ID。
2 向inetd发送一个SIGHUP信号来通知他重新读取/etc/inetd.conf配置文件。
这并没有结束进程。

在通知inetd之后,我们也许希望检测我们的配置更改是否被接受。检测的一个办法就是执行下面的命令:

# lsof -i
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
portmap 238 root 3u inet 369 UDP *:sunrpc
portmap 238 root 4u inet 370 TCP *:sunrpc (LISTEN)
inetd 314 root 4u inet 474 TCP *:ftp (LISTEN)
inetd 314 root 5u inet 475 TCP *:telnet (LISTEN)
inetd 314 root 6u inet 476 TCP *:login (LISTEN)
inetd 314 root 8u inet 477 TCP *:exec (LISTEN)
inetd 314 root 10u inet 478 TCP *:auth (LISTEN)
inetd 314 root 11u inet 1124 TCP *:9099 (LISTEN)
inetd 314 root 12u inet 1163 TCP *:daytime (LISTEN)
named 342 root 4u inet 531 UDP *:1024
. . .
在输入显示行中的TCP *:9099表明新的服务已经添加到新的服务器中。注意,左边显示inetd是在端口9099上监听的进程。TCP *:9099告诉我们TCP端口9099可以接受来自任何端的连接。

测试新服务

我们可以通过localhost地址来测试我们新的inetd服务:

$ telnet localhost 9099
Trying 127.0.0.1 . . .
Connected to localhost.
Escape character is '^]'.
Tuesday Nov 02 17:10:37 1999
Connection closed by foreign host.
$

我们会回忆起通常我们将127.0.0.1配置为我们的本地回环地址。如查我们有一个以太网卡,我们可以使用这个接口地址。我们的输出也许如下所示:

$ telnet 192.168.0.1 9099
Trying 192.168.0.1 . . .
Connected to 192.168.0.1.
Escape character is '^]'.
Tuesday Nov 02 17:13:28 1999
Connection closed by foreign host.
$

这个输出确认了允许由任何接口进行连接的事实。现在可以与我们已经存在的daytime服务相比。不要忘记在命令行添加端口13作为参数:

$ telnet 192.168.0.1 13
Trying 192.168.0.1 . . .
Connected to 192.168.0.1.
Escape character is '^]'.
Tue Nov 2 17:16:57 1999
Connection closed by foreign host.
$

我们可以注意到,与我们新服务器相比,这里忽略了星期名字。

禁止新服务器

现在切换到root用户,从/etc/inetd.conf文件中移除我们自定义的服务器实体。然后,重新通知inetd守护进程:

# ps -ax | grep inetd
314 ? S 0:00 inetd
# kill -HUP 314
#

使用inetd的数据报服务器

到目前为止,这一章一直在关注使用inetd的TCP 流式套接口的使用。当通过inetd创建数据报服务器端口时,就会添加额外的考虑。这是由我们在前面所谈到的wait与nowait标记值来提示的。

让我们来回顾一下应用到UDP服务器上所使用的inetd步骤:

1 inetd服务器在我们的UDP服务器器将会接受请求的UDP端口上进行监听。
2 inetd使用select调用来表明一个数据报已经到达套接口(注意,inetd并不读取数据报)
3 inetd服务器调用fork与exec来启动我们的UDP服务器。
4 我们的UDP服务器使用文件单元0(标准输入)来读取一个UDP数据包。

步骤1到4与我们的TCP流式是相同的。然而,在处理完步骤4所接收到的UDP数据包之后,UDP服务器有两个基本的选择:
退出(结束)
等待更多的UDP数据包(在超时时退出)

一 个小心的提示,如果UDP数据包频繁到达,为每一个UDP数据包启动一个新的进程对于系统来说是一沉重的开销。由于这个原因,一些UDP服务器在接收第一 个数据包这后会回环,并试着读取接下来的UDP数据包,而不是立即退出。使用超时,从而在没有更多数据包到达时进程会退出。当出现这种情况下,inetd 守护进程会为新的UDP数据包持续监听。

理解wait与nowait

只是简单的处理一个数据报然后退出的数据报服务器应使用nowait标记值。这会通知inetd守护进程当有额外的数据报到时会启动另外的服务器进程。这是必须的,因为启动的第一个进程将会只处理一个数据报。

对 于其他的试着读取更多数据报的数据报服务器,我们应使用wait标记值。这是必须的,inetd所启动的服务器进程将会处理接下来的数据报直到退出。 wait标记值会通知inetd不要为这个端口启动额外的服务器,直到wait系统调用通知inetd(使用SIGCHLD信号)我们的数据报服务器已经 结束。否则,inetd就会启动不必要的额外服务器进程。现在我们从系统有角度来重新描述这个过程:

1 inetd为将要到达的UDP数据包启动回环UDP服务器进程
2 inetd等待依赖于其配置文件的其他的不相关事件:他将会忽略当前的UDP端口,因为我们的数据报服务器已经启动来处理这些。注意,这个将动作将会使用,因为服务是使用wait标记值进行配置的(inetd不能确定可执行代表的是哪种类型的服务器)。
3 我们的数据报服务器完成第一个UDP数据报的处理。
4 我们的数据报服务器试着由标准输入读取额外的UDP数据报(数据报套接口)。
5 在我们的数据报服务器上发生超时,因为不再有数据报到达--我们的数据报服务器进程通过调用exit退出。
6 在inetd中发出SIGCHLD信号(记住,inetd是我们服务器的父进程)。
7 inetd服务器调用wait来确定进程ID以及我们服务器进程的结束状态。
8 inetd守护进程注意到由wait返回的进程ID属于我们的数据报服务器。他就会注意到这样的事实:他必须监视新的数据报,因为当前并没有进程在等待服务他们。

当定义数据报服务时我们要记住关于inetd的几下几点:

inetd守护进程并不能确定配置的数据报服务器中否需要wait或是nowait参数。我们必须清楚这些,并且为服务器提供正确的标记值。
wait标记值意味着不会启动另外的服务器进程,除非前面启动的服务器进程已结束。
为一个wait数据报服务器指定nowait会引起不必要的重复的服务器进程。
为一个nowait数据报服务器指定wait将会降低特定服务的性能。发生这样的情况是因为不会启动额外的进程直到父进程结束。

同时要记住,stream服务应总是使用nowait标记值。这会允许多个客户可以同时得到服务(每个连接的客户一个服务器进程)。如果为流式服务指定了wait标记值,则一次只可以服务一个客户连接。
分享到:
评论

相关推荐

    Linux Socket教程.zip

    Linux Socket学习(五).txt可能进一步深入,讲解了如何绑定Socket到特定的IP地址和端口号,这是通过bind()函数实现的。此外,可能还讨论了listen()函数,它是服务器端用来监听连接请求的关键步骤。 Linux Socket...

    实战Linux socket编程Linux Socket Programming By Example

    《实战Linux Socket编程》是...总之,《实战Linux Socket编程》的配套源代码是一个宝贵的资源,它使学习者能够动手实践,从而更好地掌握网络编程的核心概念和技术,对于提升Linux系统下的网络编程能力具有重要意义。

    LinuxSocket示例代码

    在IT行业中,Linux Socket是进行网络通信的重要工具,尤其对于系统和网络程序员来说,理解和掌握Linux Socket编程至关重要。本示例代码提供了客户端(client)和服务器端(server)的实现,帮助初学者深入理解如何在...

    实战Linux Socket编程

    在IT领域,Linux Socket编程是网络通信的核心技术之一,它为开发者提供了在Linux操作系统上实现进程间通信(IPC)和网络通信的接口。本实战指南将深入探讨这一主题,帮助你掌握如何在Linux环境中构建高效的网络应用...

    socket_test.zip_Linux下的socket_linux socket_linux socket server_l

    在Linux操作系统中,Socket是一种进程间通信机制,它允许不同进程或者不同计算机之间的通信。本教程将深入探讨Linux下的socket编程,包括服务器和客户端的实现。我们主要关注以下几个知识点: 1. **Socket基本概念*...

    linuxsocket.zip

    本压缩包“linuxsocket.zip”包含了基于TCP/IP协议的socket通信测试代码,是学习Linux应用编程的一个实用资源。这里我们将深入探讨Linux TCP/IP socket编程的相关知识点。 1. **TCP/IP协议栈**:TCP/IP协议栈是...

    linux socket学习.pdf

    本篇文章将根据给定文件“linux socket学习.pdf”的内容进行展开,重点讨论 socket 的定义、如何创建 socket 以及 socket 与其他常见 I/O 操作的区别。 #### 二、理解 Socket ##### 2.1 定义 Socket Socket 可以...

    Linux Socket

    Linux Socket是Linux操作系统中用于进程间通信(IPC)的一种接口,它允许程序通过网络协议进行数据传输。在本文中,我们将深入探讨Linux ...通过IBM技术论坛的文章,你可以更深入地了解和学习Linux Socket的各种细节。

    《实战 Linux Socket编程》练习代码

    通过本书的学习,读者可以掌握如何在Linux环境中使用Socket进行网络通信,构建高性能、稳定的网络应用。下面将根据提供的压缩包文件名“实战 Linux Socket编程代码”来解析其中可能包含的知识点。 1. **Socket基础...

    实战Linux Socket编程.rar

    Linux Socket编程是网络编程的重要组成部分,它为开发者提供...通过这个实战教程,开发者将学习如何在Linux环境中创建和管理Socket,实现网络通信功能,这对于开发服务器应用、网络工具或分布式系统是至关重要的技能。

    linux socket网络驱动深度分析

    在Linux操作系统中,Socket接口是应用程序与网络协议交互的主要接口,它允许程序通过网络发送和接收数据。在本文中,我们将深入探讨“Linux Socket网络驱动深度分析”这一主题,特别是当应用尝试创建一个用于捕获...

    Linux Socket Programming (Linux 套接字编程)

    ### Linux Socket Programming (Linux 套接字编程) #### 知识点概览: 1. **Socket编程基础** ...通过以上知识点的学习,读者可以深入了解Linux环境下的Socket编程,并能够掌握如何设计和实现网络通信程序。

    linux socket 实战编程pdf及源码

    Linux Socket实战编程是深入理解网络通信机制的...总的来说,这份资源对于想要深入理解和精通Linux Socket编程的IT从业者来说是一份宝贵的资料,通过学习和实践,可以显著提升网络编程能力,为职业发展打下坚实基础。

    linux socket programming

    Gay撰写,为读者提供了丰富的实例和深入的理论知识,是学习Linux Socket编程的绝佳资源。 **二、基本Socket概念** ### 1. Socket简介 在Linux环境下,Socket是一种进程间通信(IPC)的方式,它提供了一种在两个...

    linux下的socket聊天室程序

    10. **学习资源**:学习Socket编程,可以参考《UNIX Network Programming》等经典书籍,同时网上有许多关于Linux Socket编程的教程和示例代码可供学习。 总之,"Linux下的socket聊天室程序"是一个很好的实践项目,...

    Linux Socket两则示例

    在IT行业中,网络通信是至关重要的部分,而Linux Socket编程是实现这一目标的关键技术。Socket是一种接口,允许应用程序通过网络发送和接收数据。本篇将深入探讨标题为"Linux Socket两则示例"的资源,其中包括`echo_...

    Linux Socket Programming By Example

    《Linux Socket编程示例》...以上仅为《Linux Socket编程示例》部分章节的知识点总结,实际书中还包含了更多深入的技术细节和实战案例,对于希望掌握Linux环境下网络编程技术的学习者而言,是一本不可多得的参考书籍。

    实战Linux Socket 编程.rar

    本资源“实战Linux Socket编程”旨在帮助你深入理解并掌握这一关键技能,尤其对于那些希望在嵌入式领域有所建树的开发者来说,它是不可或缺的学习资料。 在Linux系统中,Socket编程主要涉及以下几大知识点: 1. **...

    实战Linux Socket编程源码

    Linux Socket编程是网络编程的重要组成部分,它允许...总之,"实战Linux Socket编程源码"提供了一个实践Socket编程和多客户端处理的平台,通过对源代码的学习和分析,可以加深对Socket编程的理解,提升网络编程技能。

Global site tag (gtag.js) - Google Analytics