我可以在不中断现有连接的情况下重新启动服务器进程吗?

时间:2019-03-05 15:50:13

标签: python linux

为了使基于终端的程序存活更长的时间,我被告知要研究从系统中分叉的进程。我找不到太多指定要生成新进程的PID。

在Linux中这可能吗?我主要是Windows员工。

我的程序将要处理套接字,如果我的应用程序崩溃了,那么我将丢失很多信息。我的印象是,如果它是从系统中分叉出来的,套接字将保持活动状态?

编辑:这是我正在尝试做的事情。我有多台计算机要与之通信。所以我正在构建一个程序,让我可以监听套接字(简单)。然后,我将从每台远程计算机(简单)连接到它。

一旦建立连接,我想打开一个新终端,并使用我的程序开始与远程计算机(简单)进行交互。

问题来自此部分。.客户端外壳程序会将所有流量发送到主外壳程序,然后再将其发送到远程计算机。收到响应后,它进入主外壳并将其转发给客户端外壳。

问题在于使每个客户端外壳都处于循环中。我希望所有客户端外壳都知道谁连接到每个客户端外壳上的谁。因此,客户端外壳程序1应该告诉我是否有客户端外壳程序2、3、4、5等,以及与谁连接的客户端外壳程序。这样就可以在不同进程之间共享资源。所以我在考虑使用本地套接字在所有这些客户端外壳之间发送数据。但是后来我遇到了一个问题,如果主壳要死了,一切都会丢失。所以我想尝试一种方法来保护它。

如果有道理。

2 个答案:

答案 0 :(得分:3)

那么,您希望能够在不丢失打开的套接字连接的情况下重新加载程序吗?

首先要了解的是,当进程退出时,所有打开的文件描述符都将关闭。这包括套接字连接。作为守护程序运行不会改变这一点。进程通过独立于终端会话而成为守护程序,以便在终端会话结束时它将继续运行。但是,就像任何其他进程一样,当守护程序由于某种原因(正常退出,崩溃,终止,机器重新启动等)终止时,与该守护程序的所有连接都将不复存在。顺便说一句,这不是特定于unix,Windows是相同的。

因此,对您的问题的简短回答是“否”,没有办法告诉unix / linux在进程停止时不关闭套接字,它将关闭它们。就是这样。

长答案是,有几种方法可以重新设计解决此问题的方法:

1)当您向程序exec()发送特殊消息或信号(例如SIGHUP)时,您可以拥有它自己的程序。在Unix中,exec(或其多种变体)不会结束或启动任何进程,它只是将代码加载到当前进程中并开始执行。在同一过程中,新代码将代替旧代码。由于过程保持不变,因此所有打开的文件均保持打开状态。但是,您将丢失存储在内存中的所有数据,因此套接字将打开,但是您的程序对此一无所知。在启动时,您必须使用各种系统调用来发现您的进程中打开了哪些描述符,以及它们是否是与客户端的套接字连接。解决此问题的一种方法是将关键信息作为命令行参数或环境变量进行传递,这些信息可以通过exec()调用传递,因此可以保留,以供新代码开始执行时使用。

请记住,这仅在进程仍在运行时调用exec ITSELF时有效。因此,您无法从崩溃或进程结束的任何其他原因中恢复。.您的连接将消失。但是这种方法确实解决了您希望在不丢失连接的情况下加载新代码的问题。

2)您可以通过将服务器(主服务器)分为两个进程来绕过此问题。第一个(称为“代理”)接受来自客户端的TCP连接,并使它们保持打开状态。代理永远不会退出,因此它应该保持简单,以至于您几乎不需要更改该代码。第二个过程运行“工作者”,它是实现您的应用程序逻辑的代码。您可能经常要更改的所有代码都应该放在工作程序中。现在,您所需要做的就是建立从代理服务器到工作服务器的进程间通信,并确保如果工作服务器退出,则在工作服务器再次启动时,代理服务器中有足够的信息来重新建立您的应用程序状态。在一个非常简单的低容量应用程序中,该机制可以像代理每次需要执行某项工作时fork() + exec()那样简单。我使用的效果更好的一种更好的方法是unix域数据报(SOCK_DGRAM)套接字。代理从客户端接收消息,通过数据报套接字将其转发给工作器,工作器完成工作,并将结果返回给代理,然后代理将其转发回客户端。这行之有效,因为只要代理正在运行并打开了UNIX域套接字,工作进程就可以随意重启。共享内存还可以用作代理与工作器之间通信的一种方式。

3)您可以将unix域套接字与sendmesg()recvmsg()函数以及SCM_RIGHTS标志一起使用,以不传递客户端数据本身,而是实际发送打开的数据。套接字文件描述符从旧实例到新实例。这是在不相关进程之间传递打开文件描述符的唯一方法。使用这种机制,您可以实施各种策略。例如,您可以启动主程序的新实例,然后使其(通过Unix域套接字)连接到旧实例,然后将所有套接字转移。然后,您的旧实例可以退出。或者,您可以使用代理/工作者模型,但是不必通过代理传递消息,只需让代理通过它们之间的unix域套接字将套接字描述符交给工作者,然后工作者可以直接与客户端使用该描述符。或者,您可以让您的主服务器将其所有套接字文件描述符发送到另一个“存储”进程,以在主服务器需要重新启动时保留这些进程。各种各样的架构都是可能的。请记住,操作系统只是提供了将描述符运送出去的功能,您还需要自己编写所有其他逻辑。

4)您可以接受的是,无论您多么小心,都会不可避免地失去连接。网络不可靠,程序有时会崩溃,计算机会重新启动。因此,与其花费大量的精力来确保连接不会关闭,不如将其设计为在不可避免的情况下恢复系统。

最简单的方法是:由于您的客户端知道他们希望连接的对象,因此您可以让您的客户端进程运行一个循环,在该循环中,如果由于任何原因与主服务器的连接丢失,他们会定期尝试重新连接(假设每10-30秒),直到成功为止。因此,主服务器要做的就是打开会合(侦听)套接字并等待,然后仍在运行的每个客户端都将重新建立连接。然后,客户端必须重新发送它具有的任何信息,以在主服务器中重新建立正确的状态。

已连接计算机的列表可以保存在主服务器的内存中,没有理由将其写入磁盘或其他任何位置,因为当主服务器退出时(由于某种原因),这些连接不再存在。然后,任何客户端都可以连接到您的服务器(主)进程,并要求其提供已连接的客户端列表。

就我个人而言,我将采用最后一种方法。由于在您的系统中,连接本身似乎比主服务器状态更有价值,因此在丢失时能够恢复连接将是首要任务。

在任何情况下,由于主机的作用似乎只是在客户端之间来回传递数据,因此这是使用select()或{ {1}}函数,这使您可以在一个进程中在多个套接字之间进行通信而不会阻塞。这是一个基于poll()的服务器的很好的示例,该服务器接受多个连接:

https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_71/rzab6/poll.htm

至于在Unix / Linux中“在系统外”运行进程,则被称为守护程序。在* ix中,这些进程是进程ID 1 poll()进程的子进程,它是系统启动时启动的第一个进程。您无法告诉您的流程成为init的子代,这会在现有父代退出时自动发生。 init采用所有“孤立”过程。由于有许多容易找到的编写unix守护程序的示例(此时,您需要编写的代码已变得相当标准化),因此我不会在此处粘贴任何代码,但这是我发现的一个很好的示例:{{ 3}}

如果您的linux发行版使用init(在某些发行版中是systemd的最新替代品),那么您可以将其作为systemd服务来使用,这是systemd的守护进程,但他们做一些为您服务(好坏之分。关于系统化的投诉很多。战争刚刚爆发)...

答案 1 :(得分:0)

从您自己的程序中派生是一种方法-然而,一种更简单易用的方法是创建服务。服务是程序周围的一个小包装,用于保持程序运行,在程序失败时重新启动并提供启动和停止程序的方法。

此链接显示了如何编写服务。尽管它专用于Web服务器应用程序,但是相同的逻辑可以应用于任何事物。

https://medium.com/@benmorel/creating-a-linux-service-with-systemd-611b5c8b91d6

然后启动您要编写的程序:

sudo systemctl start my_service_name

要停止它:

sudo systemctl stop my_service_name

要查看其输出,请执行以下操作:

sudo journalctl -u my_service_name