用Mina开发Socket服务器总有一些不可预知的奇怪现象?程序结束不了?!

最近在用 Java的Socket框架mina开发一个服务器端程序,服务器写完后,用mina的客户端API写了程序去连接服务器端,也连接成功了,但是发现每次客户端连接完成运行后 过1分钟客户端程序才结束! 经过调试发现代码已经执行了客户端代码的最后一行! 真是奇怪,客户端代码如下:

public class Client
{
private static final String HOSTNAME = "localhost";
private static final int PORT = 20000;
public static void main( String[] args ) throws Throwable
{
    SocketConnector connector = new SocketConnector();       
    // Configure the service.
    SocketConnectorConfig cfg = new SocketConnectorConfig();
             cfg.getFilterChain().addLast(
                "codec",
                new ProtocolCodecFilter( new ObjectSerializationCodecFactory() ) );

    cfg.getFilterChain().addLast( "logger", new LoggingFilter() );

    IoSession session;
                try {
            ConnectFuture future = connector.connect(new InetSocketAddress(
                    HOSTNAME, PORT), new ClientSessionHandler("Hi hi :("), cfg);

            future.join();
            session = future.getSession();
                    } catch (RuntimeIOException e) {
            System.err.println("Failed to connect.");
            e.printStackTrace();
                       }

    // wait until the summation is done
    session.getCloseFuture().join();
    System.out.println(session,"### all done!  !");
}





"### all done!  !" 明明都已经打印出来了! 但是发现进程中还是有一个 javaw.exe! 而且用进程管理工具无法结束!,过了1分钟后,这个javaw.exe就没有了,发现eclipse的控制台红色结束按钮也变灰色了. 这些居然都是在最后一行代码执行完发生的? 感觉Java真是一个不稳定的东西~~
问题补充
多谢回复,我看2.0是M版本,好像下载上面写着 unstable,所以没有用,你用这个版本感觉稳定吗?  我刚才又试验了一下 quickServer,试验socket连接到达100个居然就Connnectin refused了? 好像根本没有使用 非阻塞队列. mina虽然有我刚才说的问题,但是他却能坚持到 1000个连接没有发生 Connnectin refused的错误,说明他还是利用了线程池重用了很多连接,我现在马上试试 mina2.0! 我用的是1.1.7,connector没有dispose方法,那个例子我也看到了
问题补充
还有你知道他的 httpserver怎么用吗? asyWeb合到mina里面了,我看了例子"httpserver",但是里面居然没有客户端调用示例.客户端怎么传递参数到http服务器也没有说明
问题补充
我试了一下2.0M果然可以. 但是服务器端好像不能自己设置连接池了? 下面的代码在2.0不能用,你们怎么用连接池的? 我的性能是要求一台服务器机器需要有1000个并发连接. 我现在服务器这样写不知道有没有问题:

ExecutorService executor = Executors.newFixedThreadPool(1000);

    IoAcceptor acceptor = new NioSocketAcceptor();


    acceptor.setHandler(  new TimeServerHandler() );

    acceptor.getSessionConfig().setReadBufferSize( 2048 );
    acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 );

    acceptor.getFilterChain().addLast("executor",
            new ExecutorFilter(executor));
    acceptor.getFilterChain().addLast( "logger", new LoggingFilter() );
    acceptor.getFilterChain().addLast(
            "codec",
            new ProtocolCodecFilter( new ObjectSerializationCodecFactory() ) ); 
    acceptor.bind( new InetSocketAddress(PORT) );</pre>




客户端很简单,就是模拟1000个并发连接:

 public static void main( String[] args ) throws Throwable

{

for(int i=0;i<1000;i++){
log.info("Enter run l..."+i);
new Thread(new ClientThread(i)).start();
}
     }    </pre>


每个客户端代码是:

public void run() {
// TODO Auto-generated method stub
log.info("#### thread :"+this.num+" Running...");
NioSocketConnector connector = new NioSocketConnector();
// Configure the service.
    connector.setConnectTimeoutMillis(CONNECT_TIMEOUT);


    connector.getFilterChain().addLast(
                "codec",
                new ProtocolCodecFilter( new ObjectSerializationCodecFactory() ) );

    connector.getFilterChain().addLast( "logger", new LoggingFilter() );
    connector.setHandler(new ClientSessionHandler(" hihi new!"));


    IoSession session;
    for (;;) {
        try {
            ConnectFuture future = connector.connect(new InetSocketAddress(
                    HOSTNAME, PORT));

            future.awaitUninterruptibly();
            session = future.getSession();
            break;
        } catch (Exception e) {
            System.err.println("Failed to connect.");
            e.printStackTrace();

        }
    }

    // wait until the summation is done
    session.getCloseFuture().awaitUninterruptibly();

    connector.dispose();
    log.info("#### thread :"+this.num+" over ...");

}</pre>


不知道这样写是不是已经用了NIO特性和数量是1000的连接池了. 而且我发现客户端如果不用mina的API,直接用Socket API连接,服务器撑不到1000个,200多个就Connection refused了.
问题补充
刚才试验了一下,模拟1000个客户端连接,成功了931个,异常是:

2008-12-28 02:18:06,968 ERROR (LoggingFilter.java:127) - EXCEPTION :
java.io.IOException: 您的主机中的软件放弃了一个已建立的连接。
at sun.nio.ch.SocketDispatcher.read0(Native Method)
at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:25)
at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:233)
at sun.nio.ch.IOUtil.read(IOUtil.java:206)
at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:207)
at org.apache.mina.transport.socket.nio.NioProcessor.read(NioProcessor.java:180)
at org.apache.mina.transport.socket.nio.NioProcessor.read(NioProcessor.java:42)
at org.apache.mina.core.polling.AbstractPollingIoProcessor.read(AbstractPollingIoProcessor.java:568)
at org.apache.mina.core.polling.AbstractPollingIoProcessor.process(AbstractPollingIoProcessor.java:547)
at org.apache.mina.core.polling.AbstractPollingIoProcessor.process(AbstractPollingIoProcessor.java:539)
at org.apache.mina.core.polling.AbstractPollingIoProcessor.access$400(AbstractPollingIoProcessor.java:57)
at org.apache.mina.core.polling.AbstractPollingIoProcessor$Processor.run(AbstractPollingIoProcessor.java:867)
at org.apache.mina.util.NamePreservingRunnable.run(NamePreservingRunnable.java:65)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:650)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:675)
at java.lang.Thread.run(Thread.java:595)


不知道调整哪些参数可以不发生这个错误? 就是说调整什么可以使1000个连接都能连上
问题补充
windows需要修改OS的一个设置,但是设置了也不起作用,我再试试吧! java的NIO的目的是使服务器端打开尽量少的Socket连接,能处理尽可能多的客户端请求(不知道主要是非阻塞队列起作用还是那个concurrent线程池在发挥作用,应该是前者),这样理解没错吧?

mina 1.1.x版本貌似是 session.close();

对应的方法定义是: CloseFuture close();

mina 2.0更简单好用。不过我们不是直接用。是参考2.0做了些裁减。

mina的性能没什么可担心的,成功案例有:openfire

可以google一下:openfire connection manager

如果你用的是mina 2.0.0-M4,那么你是漏了一行代码:
[code]
connector.dispose();
[/code]
其它版本的mina对应调用方法类似,可能叫close方法。

只要额外启动了线程而线程还没执行完,且程序没有调用System的exit,则程进程序会一直存在。

例如启动了swing的时候因为有了后台线程需要显式的调用System的exit才能退出,

或者就是使用了ThreadPoolExecutor之类的线程池而没有调用shutdown或shutdownNow,导致看上去代码执行到了最后一行而程序没有退出。

以及其它启动了类似run方法写成 while(true)之类的线程。

而楼主的一分钟退出,是因为socket被服务端超时断开了,客户端异步的线程很仔细完毕才让程序自动退出。

这个和“Java真是一个不稳定的东西”扯不上半点关系。

相关例子在 mina 的 example中:

https://svn.apache.org/repos/asf/mina/tags/2.0.0-M4/example/src/main/java/org/apache/mina/example/chat/client/SwingChatClient.java

[code]

closeButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
client.quit();
connector.dispose();
dispose();
}
});
setLoggedOut();
setDefaultCloseOperation(EXIT_ON_CLOSE);

[/code]

asyncWeb虽然被整合到mina,但是好像作者没时间更新asyncweb 的代码,asyncweb 好像没法用新版的mina的jar,我很久没关注这个了。

2.0的api变化较大,写法和1.0不一样,你参考官方代码吧。

服务器撑不到连接应该是服务器的限制,linux是有打开文件的最大个数限制的,貌似windows也有,我没了解windows的。

引用:
修改linux最大连接数
1、直接用ulimit命令
ulimit -n 8192

2、修改/proc/sys/net/ipv4/ip_conntrack_max为8192
或者是/etc/sysctl.conf中加入ip_conntrack_max=8192

3、请首先编辑/usr/include/bits/types.h 文件,改变__FD_SETSIZE 的值:
#define _ _FD_SETSIZE 8192

下一步,使用这个命令增加内核文件描述符的限制:

echo 8192 > /proc/sys/fs/file-max

最后,增加进程文件描述符的限制,在你即将编译squid 的同一个shell 里执行:
sh# ulimit -Hn 8192

该命令必须以root 运行,仅仅运行在bash shell。不必重启机器。

Socket连接是不可能少的,少了就是断开连接了。
nio是异步读取Socket流中和异步写入数据。
避免了线程浪费在读写的等待上面。

就像酒店的服务员,聪明的服务员会让你先看菜单,等你想好点什么了再叫他来点菜,

而不聪明的服务员,是从你一进店就守着你,直到你点好菜,像这种服务员一天服务不了多少顾客。

问下 做什么应用时要用到 mina 2.0呀~~

是啊,我也想知道什么应用需要MINA呢?难道是SE方面的应用?

[quote]最近在用 Java的Socket框架mina开发一个服务器端程序,服务器写完后,用mina的客户端API写了程序去连接服务器端,也连接成功了,[b]但是发现每次客户端连接完成运行后 过1分钟客户端程序才结束![/b] 经过调试发现代码已经执行了客户端代码的最后一行![/quote]

这个是因为SocketConnector会启动一个worker线程用来监听网络上的事件。每秒中轮询一次,默认的超时时间就是1分钟。可以通过setWorkerTimeout(int workerTimeout)来设置这个超时时间。

[code="java"] /**
* Set how many seconds the connection worker thread should remain alive once idle before terminating itself.
*
* @param workerTimeout the number of seconds to keep thread alive.
* Must be >=0. If 0 is specified, the connection
* worker thread will terminate immediately when
* there's no connection to make.
*/
public void setWorkerTimeout(int workerTimeout) {
if (workerTimeout < 0) {
throw new IllegalArgumentException("Must be >= 0");
}
this.workerTimeout = workerTimeout;
}[/code]

[code="java"] private class Worker implements Runnable {
private long lastActive = System.currentTimeMillis();

    public void run() {
        Selector selector = SocketConnector.this.selector;
        for (;;) {
            try {
                int nKeys = selector.select(1000);

                registerNew();

                if (nKeys > 0) {
                    processSessions(selector.selectedKeys());
                }

                processTimedOutSessions(selector.keys());

                if (selector.keys().isEmpty()) {
                    //检查是否超时
                    if (System.currentTimeMillis() - lastActive > workerTimeout * 1000L) {
                        synchronized (lock) {
                            if (selector.keys().isEmpty()
                                    && connectQueue.isEmpty()) {
                                worker = null;
                                try {
                                    selector.close();
                                } catch (IOException e) {
                                    ExceptionMonitor.getInstance()
                                            .exceptionCaught(e);
                                } finally {
                                    SocketConnector.this.selector = null;
                                }
                                break;
                            }
                        }
                    }
                } else {
                    lastActive = System.currentTimeMillis();
                }
            } catch (IOException e) {
                ExceptionMonitor.getInstance().exceptionCaught(e);

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e1) {
                    ExceptionMonitor.getInstance().exceptionCaught(e1);
                }
            }
        }
    }
}[/code]

[quote="lyo"]刚才试验了一下,模拟1000个客户端连接,成功了931个,异常是:
[code]2008-12-28 02:18:06,968 ERROR (LoggingFilter.java:127) - EXCEPTION :
java.io.IOException: 您的主机中的软件放弃了一个已建立的连接。
at sun.nio.ch.SocketDispatcher.read0(Native Method)
at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:25)
at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:233)
at sun.nio.ch.IOUtil.read(IOUtil.java:206)
at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:207)
at org.apache.mina.transport.socket.nio.NioProcessor.read(NioProcessor.java:180)
at org.apache.mina.transport.socket.nio.NioProcessor.read(NioProcessor.java:42)
at org.apache.mina.core.polling.AbstractPollingIoProcessor.read(AbstractPollingIoProcessor.java:568)
at org.apache.mina.core.polling.AbstractPollingIoProcessor.process(AbstractPollingIoProcessor.java:547)
at org.apache.mina.core.polling.AbstractPollingIoProcessor.process(AbstractPollingIoProcessor.java:539)
at org.apache.mina.core.polling.AbstractPollingIoProcessor.access$400(AbstractPollingIoProcessor.java:57)
at org.apache.mina.core.polling.AbstractPollingIoProcessor$Processor.run(AbstractPollingIoProcessor.java:867)
at org.apache.mina.util.NamePreservingRunnable.run(NamePreservingRunnable.java:65)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:650)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:675)
at java.lang.Thread.run(Thread.java:595)[/code]
不知道调整哪些参数可以不发生这个错误? 就是说调整什么可以使1000个连接都能连上[/quote]

这个异常是连接被断开了。你可以设置一下mina服务端的backlog,设置一个较大的值。
你是在什么操作系统上运行的,如果是64位solaris的话,单个进程打开文件最多是65536个,所以足够用,不需要修改。如果是32位或者是windows,默认值是多少我记不清了,你可以查一下,你要建立的连接的数量不能超过操作系统的限制。

[quote="codeutil"]mina 1.1.x版本貌似是 session.close();

对应的方法定义是: CloseFuture close();
[b]
mina 2.0更简单好用。不过我们不是直接用。是参考2.0做了些裁减。[/b]

mina的性能没什么可担心的,成功案例有:openfire

可以google一下:openfire connection manager

[/quote]

mina2.0 unstable,所以一直都没敢用,也没看有什么不同,听说是简化了接口。你们做了哪些裁剪?对mina的读写算法和bytebuffer的控制是否做了什么优化吗?

另外问一下,

比如像短信网关这种应用,即发送一个请求出去,然后得到一个响应,消息都不长,大概几十个字节。像这种应用,setReceiveBufferSize和setSendBufferSize是应该设置的较大一点好,还是较小一点好呢?

[quote]"问下 做什么应用时要用到 mina 2.0呀~~ "[/quote]

什么应用用短连接的socket,比如http server什么的,又想能支持c10k这个连接数的,长连接client数量有限传统io每连接每线程的模型应该够用,所以这种情况也没必要非用nio,不过自己有能力写还是自己写好

[quote]比如像短信网关这种应用,即发送一个请求出去,然后得到一个响应,消息都不长,大概几十个字节。像这种应用,setReceiveBufferSize和setSendBufferSize是应该设置的较大一点好,还是较小一点好呢? [/quote]

sendBuffer的作用是已发送数据在发送端的副本,主要用来出错/超时重传
receiveBuffer是已接受的数据存放的地方,然后依次从内核空间拷贝到应用

两个都应该设大点好吧,理论上最大是65535-TCP头-IP头,不过不同的网络不一样

mina我已经在生产环境中使用了,效果非常好,目前没有发现大的问题。

你们用SocketChannel获得socket的时候如果设置了timeout
难道都没碰到file descriptor leak的问题?

没有用那个方法 ,用了框架,你还用得到socket干什么呢?

[quote="wanghelong"]没有用那个方法 ,用了框架,你还用得到socket干什么呢?[/quote]
用的whalin 的MemCached的client
他使用了SocketChannel来获得socket
结果在jdk1.5,1.6,red hat下出现了pipe泄露