分类: Java
2015-04-08 10:31:44
Linux下端口占用问题
1、 环境
操作系统:centos 6.3 64位
JDK:OpenJDK1.7
2、 前提
1) C程序通过shell脚本来启动一个java程序。
2) C程序需要打开一个TCP端口等待客户端连接;(C程序占用的端口为15001)
3) JAVA程序实现了一个RMI服务。(为RMI指定了一个端口6000)
4) C进程重启时,不重启JAVA进程;
3、 问题
1) 启动java程序时,经常会占用C程序要监听的TCP端口;
问题分析:
a) 通过netstat查看java进程所占的端口会发现,其除了占用了6000端口外,还会多绑定一个额外端口;通过多次启动java进程发现,该额外端口不定,有时占用15001,有时又占用别的端口;
b) 通过上面的观察,就怀疑RMI机制会不会就要多分一个端口。通过了解RMI的原理得知:
RMI需要有2个端口,一个负责注册服务(默认1009),另一个提供服务。
分析代码中分配端口的地方:
以下代码是打开一个注册服务,并指定了端口为6000,该端口负责注册RMI服务;
LocateRegistry.createRegistry(6000);
提供RMI服务的类继承了UnicastRemoteObject类,并在构造方法中掉用了UnicastRemoteObject的无参的构造方法;而UnicastRemoteObject内部又会执行一个带有端口参数的构造方法,并默认传入了0;RMI的机制就是如果传入的参数是0,那么系统就会随机分配一个端口给RMI服务;
解决方法:
直接调用UnicastRemoteObject中带端口参数的构造方法,指定一个端口为6001;之后启动JAVA程序就不会再随机分配端口了。
2) 解决了以上问题后,又运行了一段时间,发现C进程重启时,15001又会别java进程占用。
问题分析:
a) 通过几次实验发现,只要C进程关闭,15001端口就被分配到java进程上。而且过一会该端口就会自动关闭,通过抓包发现该端口上吐出的数据也是C进程传输的内容。可以确定C进程关闭时,由于数据还没有发送完,端口没有立即关闭,而是发送完后,等一个TIMEWait时间后才关闭。
b) 初步怀疑是Linux系统进程管理机制的问题,当父进程被杀死后,子进程会继承父进程的端口资源。但是通过ps命令发现java进程此时的父进程为init进程。在C进程没有被杀死的情况下,其父进程也为init进程。这样似乎不是该问题,但是经过仔细分析还是能得出上述结果的,如下分析过程:
system()会调用fork()产生子进程,由子进程来调用/bin/sh-c
string来执行参数string字符串所代表的命令,此命令执行完后随
即返回原调用的进程。
2、 popen(建立管道I/O)
popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh -c
来执行参数command的指令。
3、 使用fork()新建子进程,然后调用exec函数族;
用fork函数创建子进程后,子进程往往要调用一种exe以执行另一个程序。当进程调用一种exe时,该进程完全由新程序代换,而新程序则从其 main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。有六种不同的exe可供使用,它们常常被统称为exec函数。
解决方法:
从分析可以得知,要想解决当前问题,就是阻止子进程继承父进程的资源;当前C进程是通过system函数来执行shell脚本,必然会继承父进程的资源;通过c语言调用shell的三个方法看,只有间接调用exec函数才会打乱继承关系,因为它会用新的程序来占用新fork的子进程,继承而来的资源也都会覆盖掉,即就不存在继承关系了。