3

我在 Windows XP 上的 Eclipse 中编程 java。我有一个多进程模拟,它使用 ProcessBuilder 来运行一个服务器和两个客户端。服务器启动一个线程来监听两个不同的套接字 - 每个客户端一个。我可以注释掉每个客户的代码,让其他的工作完美无缺。当我尝试同时运行它们时,一个客户端总是会出现 ConnectException 错误:连接被拒绝:连接。它似乎是哪个客户端运行速度较慢,尽管很难说。我可以在启动服务器之后但在客户端之前暂停,并且 netstat 验证两个套接字都处于活动状态。这可能是什么原因造成的?我在下面有一些简化的代码。

更新:根据评论,我编辑了代码以在单个套接字上对服务器进行多线程处理,但我仍然遇到同样的问题。下面的代码反映了这些变化。似乎套接字正在由一个客户端打开和关闭,然后另一个客户端有机会打开它。我可以在每个客户端结束时抛出暂停语句,让另一个客户端完成,但这是一个修复,而不是解决方案。所以现在真正的问题是,我如何让 ServerSocket 一直监听直到我指示它关闭?

服务器

try{
    server = new ServerSocket(sockNum); 
} catch (IOException e) {
        System.out.printf("Could not listen on port %d\n",sockNum);
        System.exit(-1);
}
while(true){
    ClientWorker w;
    try{
        Socket connection = server.accept();
        w = new ClientWorker(connection);
        Thread t = new Thread(w);
        t.start();
    } catch (IOException e) {
        System.out.printf("Accept failed: %d\n",sockNum);
        System.exit(-1);
    }
}

class ClientWorker implements Runnable {
    private Socket client;

          ClientWorker(Socket client) {
           this.client = client;
          }

          public void run(){
            Object line;
            ObjectInputStream in = null;
            PrintWriter out = null;
            try{
              in = new ObjectInputStream(client.getInputStream());
              out = new PrintWriter(client.getOutputStream(), true);
            } catch (IOException e) {
              System.out.println("in or out failed");
              System.exit(-1);
            }

            while(true){
                try{
                    line = in.readObject();
                    //Send data back to client
                    out.println("Sent from broker");
                    if(line instanceof String)
                        System.out.println(line);
                    else
                        System.out.println(line.getClass());                        
                } catch (IOException e) {
                    System.out.println("Read failed");
                    System.exit(-1);
                } catch (ClassNotFoundException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

客户

 try{
    socket = new Socket("localhost", socketNum);
    out = new ObjectOutputStream(socket.getOutputStream());
    in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

    out.writeObject(message);
    String line = in.readLine();
    System.out.println(line);
    } catch (UnknownHostException e) {
        System.out.println("Unknown host: localhost.eng");
        System.exit(1);
    } catch  (IOException e) {
        System.out.println("No I/O");
        System.exit(1);
    }

控制器

ProcessBuilder server = new ProcessBuilder("java.exe","-Xss64m","-cp","bin;jscheme.jar","ServerProcess");
server.redirectErrorStream(true);
Process runServer = server.start();

ProcessBuilder clientA = new ProcessBuilder("java.exe","-Xss64m","-cp","bin;jscheme.jar","ClientAProcess");
clientA.redirectErrorStream(true);
Process runClientA = clientA.start();

ProcessBuilder clientB = new ProcessBuilder("java.exe","-Xss64m","-cp","bin;jscheme.jar","ClientBProcess");
clientB.redirectErrorStream(true);
Process runClientB = clientB.start();
4

4 回答 4

1

“服务器启动一个线程来监听两个不同的套接字——每个客户端一个。”

不,请不要这样做!为每个客户端分配一个单独的监听套接字是不正常的设计。只需启动一个两个客户端都连接的侦听线程。当 accept() 返回时,为该客户端启动一个读取线程,传递由 accept() 调用返回的 ServerClient Socket。

于 2012-04-16T21:33:47.467 回答
1

编写服务器的常用方法是在一个循环中侦听单个套接字。

对于每个传入的连接,产生一个线程来处理客户端,然后立即listen在同一个服务器套接字上再次执行。

在您的情况下,如果两个客户端尝试连接到同一个服务器端口,第二个客户端将始终得到“连接被拒绝”。第一个客户端获取连接,服务器不会listen在套接字上重新发出 a 。您还没有展示客户端如何获取他们的端口号,但我猜他们都试图连接到同一个端口。

于 2012-04-16T21:35:54.593 回答
0

当我写这篇文章时,您的代码对我来说看起来不错。您有一个侦听新连接的ServerSocket 。每个连接发生时,都会设置一个新线程来处理它为其提供自己的Socket。您的Socket和它们的线程应该是完全独立的。每个人的命运都应该完全独立于另一个人。他们不应该知道彼此的存在。

弄清楚它们如何相互作用并将它们分开。

可以尝试的一件事是设置 100 个客户端而不是 2 个。这可能会使问题更加明显;一吨TNT会留下比鞭炮更大的火山口。

另一个可能的问题:确保定期重置 ObjectOutputStream。这种 I/O 非常方便,但它可以做一些非常奇怪的事情。如果没有重置,如果你发送同一个对象两次,第二次它只会发送一个对第一个版本的引用,如果最新版本发生了变化,这是没有用的。此外,即使使用重置,如果您发送一个引用另一个对象的对象,对象也将被发送到该行。(这可能非常好。您可以发送一个引用包含数百万个对象的庞大结构的单个对象,整个事物将被发送保留其组织。如果您知道它在做什么,它非常巧妙且非常有用。)

于 2012-04-17T18:05:32.157 回答
0

来自 OP

因此,在解决我认为不相关的问题的过程中,我似乎已经解决了最初的问题。我添加了两个主要内容。首先,我将客户端设置为在完成时发送“结束”命令,以便在尝试 readObject() 时套接字不会阻塞。其次,(我认为这是修复它的原因),我为 ServerSocket 定义了一个超时值,而不是让它未定义(又名无限)。当我在让事情正常工作后回去清理我的代码时,我尝试删除客户端进程中的暂停,但事情仍然有效!我的猜测是,在没有定义超时的情况下,当一个套接字在另一个套接字打开之前关闭时,ServerSocket 也会关闭。定义超时后,ServerSocket 将等到超时后再关闭。(这只是一个猜测。)

这是当前的工作代码。如果您认为修复的原因不同,请随时发表评论。

服务器

try{
    server = new ServerSocket(sockNum);
    server.setSoTimeout(timeOut);
} catch (IOException e) {
    System.out.printf("Could not listen on port %d\n",sockNum);
    System.exit(-1);
}

while(isActive){
    ClientWorker w;
    try{
        Socket connection = server.accept();
        w = new ClientWorker(connection);
        Thread t = new Thread(w);
        t.start();
    } catch (IOException e) {
        if(e instanceof SocketTimeoutException)
            isActive = false;
        else{
            System.out.printf("Accept failed: %d\n",sockNum);
            System.exit(-1);
        }
    }
}

 class ClientWorker implements Runnable {
        private Socket client;
        private Source source = Source.NONE;
        private String sourceName = "?";
        private Boolean threadActive = true;

        ClientWorker(Socket client) {
            this.client = client;
        }

        public void run(){
            Object line;
            ObjectInputStream in = null;
            PrintWriter out = null;
            try{
                in = new ObjectInputStream(client.getInputStream());
                out = new PrintWriter(client.getOutputStream(), true);
            } catch (IOException e) {
                System.out.println("in or out failed");
                System.exit(-1);
            }

            while(threadActive){
                try{
                    line = in.readObject();
                    if(line instanceof String){
                        if(line.equals("end")){
                            threadActive = false;
                        }else
                            sourceName = line;
                        }
                    } else 
                        handleData;
                } catch (IOException e) {
                    System.out.println("Read failed");
                    threadActive = false;
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                    threadActive = false;
                }
            }
            try {
                this.client.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
于 2012-05-01T13:50:48.373 回答