Linux下解决母女进程端口冲突的两种方式案例背景
在项目实践过程中常常会碰到须要A服务(进程)拉起B服务(进程)的场景linux 程序 端口,此时B进程作为A进程的子进程会承继A进程的一些资源、比如socket句柄、文件描述符等。若果在这以后,A进程退出,B进程依旧会占有之前从A进程承继到的资源。此时若果A进程退出以后再启动,可能还会由于资源被占用而形成问题。
问题实例为了演示上述问题,首先,我们作为子进程的脚本,命名为son,shell脚本如下:
#! /bin/bash
while true
do
echo "i am here"
sleep 1
done
之后我们写一个程序,作为父进程。在父进程中窃听端口并拉改锥进程。C++代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main() {
int socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
serv_addr.sin_port = htons(1234);
if(-1==(bind(socket_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr))))
{
cout << "bind failed!" << endl;
}
listen(socket_fd, 20);
system("./son >/dev/null &"); //以后台方式启动子进程
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size = sizeof(clnt_addr);
int clnt_sock = accept(socket_fd, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
char str[] = "aaaa";
write(clnt_sock, str, sizeof(str));
close(clnt_sock);
close(socket_fd);
return 0;
}
编译授权以后,把两个进程置于同一目录下
之后启动父进程。此时通过ps可以看见兄妹进程都在运行。
之后通过lsof-i:1234命令发觉,1234即被父进程占用,又被单进程占用。
之后杀掉父进程,再lsof-i:1234命令发觉,1234一直毛毯进程占用。
此时再度启动父进程,由于1234端口早已毛毯进程占用,所以父进程启动失败。
至此,就出现了父女进程端口占用问题,该问题归根揭底是兄妹进程端口承继问题。
解决方案
解决方案有两种:
方案一
通过fcntl设置socket句柄,可以促使父进程fork子进程时,子进程不承继父进程的端口。
原理就是:对于socket句柄设置文件描述符标志,致使其在执行exec时(即创建子进程时)关掉(然后再打开),由于创建子进程时该描述符关掉了,所以子进程就难以承继相应的文件描述符了。
只须要添加以下代码即可:
/* close server socket on exec so cgi's can't write to it*/
if (fcntl(server_s, F_SETFD, 1) == -1)
{
msprintf("can't set close-on-exec on server socket!");
}
更改后的代码如下所示:
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main() {
int socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
serv_addr.sin_port = htons(1234);
if(-1==(bind(socket_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr))))
{
cout << "bind failed!" << endl;
return -1;
}
listen(socket_fd, 20);
/* close server socket on exec so cgi's can't write to it*/
if (fcntl(socket_fd, F_SETFD, 1) == -1)
{
cout << "can't set close-on-exec on server socket!" << endl;
return -1;
}
system("./son >/dev/null &"); //以后台方式启动子进程
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size = sizeof(clnt_addr);
int clnt_sock = accept(socket_fd, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
char str[] = "aaaa";
write(clnt_sock, str, sizeof(str));
close(clnt_sock);
close(socket_fd);
return 0;
}
方案二
方案一便捷快捷,适用于直接调用低层插口的程序。并且如今好多程序都是使用的现有的程序库linux系统下载官网,而现有程序库中可能不会直接曝露socket文件描述符,也就没办法使用fcntl来设置标志。
不过不要着急,这个时侯有第二套解决方案:将子进程封装成服务,通过systemctl命令来控制子进程的启停。systemctl是linux下管理进程的新标准linux认证,功能庞大,这儿暂不做赘言
原理就是,通过systemctl形式启动son服务,父进程不再是通过fork的方法来启动一个子进程,而是通知内核,由内核来启动son服务。所以,就不存在端口承继问题了。
编撰服务描述文件son.service
[Unit]
Description=son server
[Service]
Type=simple
ExecStart=/test/son >/dev/null &
ExecStop=/test/son stop
[Install]
将son.service拷贝到/usr/lib/systemd/system
执行命令
cp -f ./son.service /usr/lib/systemd/system
重新加载下systemctl
执行命令
systemctl daemon-reload
此时就可以通过systemctlstartson命令来启动son程序了,同时也可以通过systemctlstatusson来查看son服务的状态。
5.更改父进程代码,在父进程中通过systemctlstartson命令来启动son服务。更改后的代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main() {
int socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
serv_addr.sin_port = htons(1234);
if(-1==(bind(socket_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr))))
{
cout << "bind failed!" << endl;
return -1;
}
listen(socket_fd, 20);
/* close server socket on exec so cgi's can't write to it*/
if (fcntl(socket_fd, F_SETFD, 1) == -1)
{
cout << "can't set close-on-exec on server socket!" << endl;
return -1;
}
system("systemctl start son"); //以服务方式启动子进程
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size = sizeof(clnt_addr);
int clnt_sock = accept(socket_fd, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
char str[] = "aaaa";
write(clnt_sock, str, sizeof(str));
close(clnt_sock);
close(socket_fd);
return 0;
}
至此,启动父进程,可以发觉子进程似乎被拉上去了,并且子进程不会集成父进程的端口等资源,问题也就解决了。
总结
本文首先介绍了因为母子进程资源承继可能会引起的问题,这些问题容易忽视,无法察觉linux 程序 端口,且出现问题后无法排查。
此后,本文介绍了两种解决问题的方案。这两种方案除了可适用于端口冲突问题上,也适用于文件冲突等其他场景下。
文章评论