我有一台 CentOS 5.5 服务器,在端口 80 上运行 Apache 以及一些其他应用程序。一切正常,直到我出于某种原因需要重新启动 httpd 进程。这样做会返回:
sudo /etc/init.d/httpd restart
Stopping httpd: [ OK ]
Starting httpd: (98)Address already in use: make_sock: could not bind to address [::]:80
(98)Address already in use: make_sock: could not bind to address 0.0.0.0:80
no listening sockets available, shutting down
Unable to open logs
一开始我以为 httpd 可能已经冻结并且仍在运行,但事实并非如此。所以我运行 netstat 来找出正在使用端口 80 的程序:
sudo netstat -tlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 *:7203 *:* LISTEN 24012/java
tcp 0 0 localhost.localdomain:smux *:* LISTEN 3547/snmpd
tcp 0 0 *:mysql *:* LISTEN 21966/mysqld
tcp 0 0 *:ssh *:* LISTEN 3562/sshd
tcp 0 0 *:http *:* LISTEN 3780/python26
事实证明,在 httpd 重新启动的瞬间,我的 python 进程接管了对 http 的监听。因此,我终止了 python 并尝试再次启动 httpd - 但遇到了同样的错误。再次运行 Netstat:
sudo netstat -tlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 *:7203 *:* LISTEN 24012/java
tcp 0 0 localhost.localdomain:smux *:* LISTEN 3547/snmpd
tcp 0 0 *:mysql *:* LISTEN 21966/mysqld
tcp 0 0 *:ssh *:* LISTEN 3562/sshd
tcp 0 0 *:http *:* LISTEN 24012/java
现在我的 java 进程已接管监听 http。我也终止了它,然后可以成功重新启动 httpd。
但这是一个糟糕的解决方法。为什么这些 python 和 java 进程会在 httpd 重新启动后立即开始监听端口 80?如何解决?
另外两条评论。1) Java 和 Python 进程都是由 apache 通过 php 脚本启动的。但是当 apache 重新启动时,它们不应该受到影响。2) 我在另外两台运行 Ubuntu 的机器上进行了相同的设置,没有任何问题。
有任何想法吗?
编辑:
Java 进程监听端口 7203,而 python 进程据说不监听任何端口。由于某种原因,当 apache 重新启动时,它们开始监听端口 80。以前没有发生过这种情况。在 Ubuntu 上运行良好。由于某种原因,在我当前的 CentOS 5.5 机器上出现了这个问题。
答案1
问题可能在于 Apache 启动子进程的方式。它们可能通过分叉生成,并让克隆的 Apache 进程成为其他进程。每个克隆都会继承父进程的所有打开的文件句柄,包括 TCP 端口 80 的打开句柄。
Netstat 仅显示与打开文件句柄关联的一个应用程序,而三个进程保持该句柄保持打开。
您的问题的可能解决方案:
如果在 Apache 重启期间保持子进程运行很重要,最简单的解决方案是将 3 个进程作为单独的系统服务启动。
如果它们依赖于正在运行的 Apache,则“apache stop”命令也应终止它们。这可以通过编辑 /etc/init.d/apache 脚本来实现。
如果您被迫从 Apache 启动它们,则需要将它们作为真正的“守护进程”启动!
我为一个 PyQt 应用程序编写了一个守护进程创建器,灵感来自http://code.activestate.com/recipes/278731-creating-a-daemon-the-python-way/。您可以从 Apache 启动该脚本,为每个子进程调用 exec_as_daemon 并关闭它。
# Copyright: (c) 2011 phobie <[email protected]>
# License: CC-by-sa 3.0
import os
def exec_as_daemon(s_path_cmd, l_args=[]):
i_pid = os.fork()
if i_pid != 0:
# Always remember to gobble your zombie children
os.wait()
# Back to parent
return
# Detach from parent
os.setsid()
# Do not block any mounts
os.chdir('/')
# Reset file creation rights
os.umask(0)
i_pid = os.fork()
if i_pid != 0:
# Close the direct child process
os._exit(0)
# Get the maximum count of open file handles
try:
import resource
i_fd_max = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
if i_fd_max == resource.RLIM_INFINITY:
i_fd_max = 1024
except ImportError:
i_fd_max = 1024
# Try to close all possible file handles
for i_cur_fd in range(0, i_fd_max):
try:
os.close(i_cur_fd)
except OSError:
pass
# Assosiate STDIN with /dev/null
os.open(os.devnull if hasattr(os, "devnull") else '/dev/null', os.O_RDWR)
# STDOUT to /dev/null
os.dup2(0, 1)
# STDERR to /dev/null
os.dup2(0, 2)
l_args.insert(0, s_path_cmd)
# Let the clone become a other process
os.execv(s_path_cmd, l_args)
if __name__ == '__main__':
exec_as_daemon('/usr/bin/java', ['-jar', '/usr/local/bin/apache_helper_daemon.jar'])
exec_as_daemon('/usr/local/bin/apache_helper_daemon.py')
答案2
理论上,只有一个进程应该监听给定的 IP/端口。如果您需要多个应用程序监听同一个端口,则需要一种反向代理类型的设置,该设置将根据收到的内容确定哪个进程获取消息。
了解您的 Python 和 Java 应用程序正在做什么会很有帮助。如果它们也是侦听端口 80 的服务器,那么它们会在 Apache 运行时打开端口 80 进行侦听时卡住,并且一旦您终止 Apache,进程队列中的下一个进程就会通过并打开端口。您需要更改 Python 和 Java 代码以侦听不同的端口。
答案3
您未能彻底关闭 Apache 主进程,并且其子进程仍挂在套接字上。不要运行重新启动,而是运行服务 httpd stop。如果此操作无法彻底退出,您需要进一步调查您通过 Apache 启用的 Java 和 Python 应用程序。
如果服务 httpd stop 无法正常工作,请卸载 Java 和 Python 应用程序,直到可以正常重新启动。然后,调查这些进程无法正常终止的原因。