2014-10-10 62 views
0

我想创建一个服务器,它可以接受多个连接,然后将两个客户端绑定为一对,并在这两个客户端之间转发数据。但它涉及多对客户。我已经有了可以为每个新连接的客户端创建一个新线程的多线程服务器。对我来说问题是这些线程不知道对方,不知何故我必须将2个客户端连接到一个连接对。如何创建一个接受客户端连接,然后为客户端对建立中继连接的java服务器

现在我只是创建这些对连接,如下所示:我等待第一个客户端,然后等待第二个客户端,然后打开一个线程以获取客户端1的输入,并将其转发到客户端2,周围。这不适用于多个客户端。

我该如何做到这一点呢?

+0

我想你将不得不以某种方式识别每个客户端,不是吗? – 2014-10-10 12:31:42

+0

是的,我知道要连接哪个客户端对,因为他们必须在连接到服务器后发送唯一ID。我的“问题”是,如果我为每个客户端打开一个线程并等待输入流,我如何连接那些在不同线程中处理的客户端? – Hydrophilia 2014-10-10 12:34:49

回答

0

我看到它的方式,客户端需要

  1. 建立与服务器的TCP(?)连接,
  2. 识别自身
  3. 给其他客户端的ID它希望与
  4. 对话

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

UPDATE:实施例

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); 
     } 
    }  
} 
+0

对我来说问题是。如果第一个连接的客户端在新线程中被识别,并且其他客户端稍后在另一个线程中也被识别出来。我怎样才能连接这两个?也许我创建另一个线程查找数组,每个套接字和它的id在连接后保存,然后将这两个客户端作为一个单独的线程连接? – Hydrophilia 2014-10-10 13:05:27

+0

直到双方联机为止,您不需要创建任何线程。除了服务器线程(池)。无论如何,你需要一个充分保护的全局表来进行匹配。 – 2014-10-10 13:17:32

+0

添加了一个示例 – 2014-10-13 10:09:03