【问题标题】:How to create a java Server that accepts client connections and then build a relay connection for a client pair如何创建一个接受客户端连接的 Java 服务器,然后为客户端对建立中继连接
【发布时间】:2014-10-10 12:25:24
【问题描述】:

我想创建一个可以接受多个连接的服务器,然后将 2 个客户端绑定为一对,并在这 2 个客户端之间转发数据。但它是关于多对客户的。我已经有多线程服务器,可以为每个新连接的客户端创建一个新线程。对我来说问题是这些线程彼此不知道,我必须以某种方式将 2 个客户端连接到一个连接对。

现在我只是这样创建这些对连接:我等待第一个客户端,然后等待第二个客户端,然后为客户端 1 的输入打开一个线程,该输入被转发到客户端 2,反之亦然.这不适用于多个客户端。

我怎样才能做到体面?

【问题讨论】:

  • 我猜你必须以某种方式识别每个客户,不是吗?
  • 是的,我知道要连接哪个客户端对,因为它们在连接到服务器后必须发送一个唯一 ID。我的“问题”是如果我为每个客户端打开一个线程并等待输入流,我如何连接在不同线程中处理的这两个客户端?

标签: java multithreading sockets


【解决方案1】:

在我看来,客户需要

  1. 与您的服务器建立 TCP(?) 连接,
  2. 标识自己
  3. 提供它希望与之交谈的其他客户的 ID

第一个连接的客户端必须保持暂停(在您服务器的某个全局表中),直到第二个客户端连接。 一旦一对客户端被识别为对话者,您将创建一对线程来将每个客户端发送的数据转发给另一个客户端。

更新:示例

ClientSocket.java

package matchmaker;

import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class ClientSocket implements Closeable {
    private final Socket socket;
    private final InputStream in;
    private final OutputStream out;
    private final String ownId;
    private final String peerId;

    public ClientSocket(Socket socket) throws IOException {
        this.socket = socket;
        this.in = socket.getInputStream();
        this.out = socket.getOutputStream();
        DataInputStream din = new DataInputStream(in);
        this.ownId = din.readUTF();
        this.peerId = din.readUTF();
    }

    public ClientSocket(String server, int port, String ownId, String peerId)
            throws IOException {
        this.socket = new Socket(server, port);
        this.socket.setTcpNoDelay(true);
        this.in = socket.getInputStream();
        this.out = socket.getOutputStream();
        this.ownId = ownId;
        this.peerId = peerId;
        DataOutputStream dout = new DataOutputStream(out);
        dout.writeUTF(ownId);
        dout.writeUTF(peerId);
    }

    public String getOwnId() {
        return ownId;
    }

    public String getPeerId() {
        return peerId;
    }

    public InputStream getInputStream() {
        return in;
    }

    public OutputStream getOutputStream() {
        return out;
    }

    @Override
    public void close() throws IOException {
        socket.close();
    }
}

Matchmaker.java:服务器

package matchmaker;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Matchmaker extends Thread {
    private static final Logger LOG
            = Logger.getLogger(Matchmaker.class.getName());

    private final int port;
    private final Map<ClientPair,ClientSocket> waiting = new HashMap<>();

    public static void main(String[] args) {
        try {
            int port = 1234;
            int st = 0;
            for (String arg: args) {
                switch (st) {
                    case 0:
                        switch (arg) {
                            case "-p":
                                st = 1;
                                break;
                            default:
                                System.out.println("Unknown option: " + arg);
                                return;
                        }
                        break;
                    case 1:
                        port = Integer.parseInt(arg);
                        st = 0;
                        break;
                }
            }
            Matchmaker server = new Matchmaker(port);
            server.start();
            server.join();
        } catch (InterruptedException ex) {
            LOG.log(Level.SEVERE, null, ex);
        }
    }

    private Matchmaker(int port) {
        this.port = port;
        setDaemon(true);
    }

    @Override
    public void run() {
        try {
            ServerSocket server = new ServerSocket(port);
            while (true) {
                ClientSocket socket = new ClientSocket(server.accept());
                ClientPair pair = new ClientPair(
                        socket.getOwnId(), socket.getPeerId());
                ClientSocket other;
                synchronized(this) {
                    other = waiting.remove(pair.opposite());
                    if (other == null) {
                        waiting.put(pair, socket);
                    }
                }
                if (other != null) {
                    LOG.log(Level.INFO, "Establishing connection for {0}",
                            pair);
                    establishConnection(socket, other);
                } else {
                    LOG.log(Level.INFO, "Waiting for counterpart {0}", pair);
                }
            }
        } catch (IOException ex) {
            LOG.log(Level.SEVERE, null, ex);
        }
    }

    private void establishConnection(ClientSocket socket, ClientSocket other)
            throws IOException {
        Thread thread = new StreamCopier(
                socket.getInputStream(), other.getOutputStream());
        thread.start();
        thread = new StreamCopier(
                other.getInputStream(), socket.getOutputStream());
        thread.start();
    }
}

StreamCopier.java:从 InputStream 读取并写入 OutputStream 的线程

package matchmaker;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;

public class StreamCopier extends Thread {
    private static final Logger LOG
            = Logger.getLogger(StreamCopier.class.getName());

    private final InputStream in;
    private final OutputStream out;

    public StreamCopier(InputStream in, OutputStream out) {
        this.in = in;
        this.out = out;
        setDaemon(true);
    }

    @Override
    public void run() {
        LOG.info("Start stream copier");
        try {
            for (int b = in.read(); b != -1; b = in.read()) {
                out.write(b);
            }
        } catch (IOException ex) {
            LOG.log(Level.SEVERE, null, ex);
        } finally {
            LOG.info("End stream copier");
            try {
                out.close();
            } catch (IOException ex) {
                LOG.log(Level.SEVERE, null, ex);
            }
        }
    }
}

ClientPair.java:一对客户端 ID

package matchmaker;

public class ClientPair {
    private final String client1;
    private final String client2;

    public ClientPair(String client1, String client2) {
        this.client1 = client1;
        this.client2 = client2;
    }

    public String getClient1() {
        return client1;
    }

    public String getClient2() {
        return client2;
    }

    public ClientPair opposite() {
        return new ClientPair(client2, client1);
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 73 * hash + client1.hashCode();
        hash = 73 * hash + client2.hashCode();
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final ClientPair other = (ClientPair) obj;
        return client1.equals(other.client1) && client2.equals(other.client2);
    }

    @Override
    public String toString() {
        return "[" + client1 + "," + client2 + "]";
    }
}

ReaderClient.java:一个从套接字读取并写入标准输出的示例客户端

package matchmaker;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ReaderClient {
    private static final Logger LOG = Logger.getLogger(ReaderClient.class.getName());

    public static void main(String[] args) {
        try (ClientSocket client
                = new ClientSocket("localhost", 1234, "reader", "writer")) {
            Reader reader
                    = new InputStreamReader(client.getInputStream(), "UTF-8");
            BufferedReader in = new BufferedReader(reader);
            for (String s = in.readLine(); s != null; s = in.readLine()) {
                System.out.println(s);
            }
        } catch (IOException ex) {
            LOG.log(Level.SEVERE, null, ex);
        }
    }
}

WriterClient.java:写入套接字的示例客户端

package matchmaker;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.logging.Level;
import java.util.logging.Logger;

public class WriterClient {
    private static final Logger LOG = Logger.getLogger(ReaderClient.class.getName());

    public static void main(String[] args) {
        try (ClientSocket client
                = new ClientSocket("localhost", 1234, "writer", "reader")) {
            Writer writer
                    = new OutputStreamWriter(client.getOutputStream(), "UTF-8");
            PrintWriter out = new PrintWriter(writer);
            for (int i = 0; i < 30; ++i) {
                out.println("Message line " + i);
            }
            out.flush();
        } catch (IOException ex) {
            LOG.log(Level.SEVERE, null, ex);
        }
    }    
}

【讨论】:

  • 我的问题是。如果第一个连接的客户端在一个新线程中被识别,而另一个客户端稍后也在另一个线程中被识别。我怎样才能连接这两个?也许我创建了另一个线程来查找一个数组,其中每个套接字及其 id 在它们连接后被保存,然后将两个客户端连接为一个单独的线程?
  • 在双方都在线之前,您不需要创建任何线程。服务器线程(池)除外。在任何情况下,您都需要一个受到充分保护的全局表来进行匹配。
猜你喜欢
  • 2021-11-06
  • 2019-02-28
  • 1970-01-01
  • 2013-05-07
  • 2015-09-30
  • 1970-01-01
  • 1970-01-01
  • 2013-04-10
  • 2021-11-01
相关资源
最近更新 更多