【问题标题】:Bi-direction multithreaded socket connection双向多线程套接字连接
【发布时间】:2018-04-03 19:29:09
【问题描述】:

情况如下: 有一个服务器和一个客户端,它们都可以向对方发起命令/消息。

因为服务器可以随时发送消息,所以对套接字的监听是在一个单独的线程中完成的(ListenerThread)。这一切都很好。客户端可以同时发送和接收消息,但是,当服务器也可以发起一个新的命令/消息来通知发生了某事时,你怎么知道某个响应是否属于你发送的命令?

如果我向服务器发送消息,并且服务器在侦听线程中以“OK”响应。你怎么知道这是你发送的消息/命令的实际响应(记住这是另一个线程)。如果服务器从另一个客户端收到更新并首先发送该更新会怎样。

这就像一个聊天应用程序,但对每个发送的命令都有实际响应。

例子:

假设该协议仅包含一个move <playernum> [<x>,<y>] 命令,该命令指示玩家已经移动(服务器通知客户端)或玩家想要移动(客户端通知服务器)。此外,如果移动正常,服务器会响应“OK”,否则会响应“ERR”。

安全状态:

     move 1 [3,4]
client ---> server

        OK
client <--- server

不安全状态:

    move 1 [3,4]
client ---> server

    move 2 [1,2]
client <--- server

        OK
client <--- server

客户端没有预料到这个响应...应该以 OK 响应。

【问题讨论】:

  • 在每个命令中传递一个消息 ID,并将其与响应一起传回。
  • 服务器只回复发送命令的客户端。服务器知道哪个Socket 正在发送数据,它不应该仅仅因为一个客户端发送数据就向所有客户端发送“OK”。这不是问题。这就像在问“当我在一群人中时,我怎么知道我在和谁说话”。
  • @teppic 这有点用,但我不负责服务器部分;(
  • 您的协议(因此服务器)已损坏。变通方案将“发生作用”,直到它们没有。真正的解决办法是替换协议。
  • 你知道服务器是否发送一个带有命令响应的换行符(或来自另一个玩家的移动)?换句话说,在 readLine() 上阻塞是否安全?

标签: java multithreading sockets bidirectional


【解决方案1】:

您有一个协议,客户端可以读取三种可能的消息之一:

  1. OK(你的举动被接受了)
  2. ERR(你的举动被拒绝了)
  3. move PLAYERID &lt;co-ord1,co-ord2&gt;

合理的假设是消息OKERR 只会被发送回请求move 的套接字。然而,合法的move 会广播给所有其他玩家(可能不包括移动的玩家)。

由于您可以收到不请自来的响应(其他玩家做出的动作),因此您已经正确地创建了一个监听线程。您还没有描述您的应用程序在收到来自另一个客户端的move 消息时所采取的操作,但我会假设您的侦听器线程会处理这种情况。剩下的就是如何协调您的 move 命令,以及对将出现在侦听器线程中的响应的响应。

为了同步您的move 命令的提交和响应,将使用一个阻塞队列(称为queue),并在客户端和侦听器之间共享。它的形式是:

客户:

out.println(command);   // Where out is the socket PrintWriter stream
String response = queue.take();   // Where queue is the BlockingQueue
// Process either `OK` or `ERR`

监听线程:

while ((command = in.readLine()) != null) {
    if (command.equalsIgnoreCase("OK") || command.equalsIgnoreCase("ERR"))
        queue.put(command);
    else if (command.startsWith("move")) {
        // Process a move
    }
    else 
        System.out.println("Unrecognized command="+command);
}

如您所见,客户端只需提交一个命令,并阻止响应“OK”或“ERR”。处理其他玩家移动的需求已移至侦听器线程。

监听器处理所有三个条件(另一个玩家移动,“OK”或“ERR”)。消息响应“OK”和“ERR”被发送回客户端。移动命令是单独处理的,因此移动不是客户端的责任。

下面我模拟了演示这些概念的工作代码。服务器将随机(以相同的概率)响应:

  1. 好的
  2. 错误
  3. 包含OK 和另一个玩家的移动的多行响应

代码:

public class MoveGame {

public static void main(String[] args) {

    Scanner scanner = new Scanner(System.in);
    String command = "";

    new Thread(new MoveServer()).start();

    Socket socket = null;
    PrintWriter out = null;
    BlockingQueue<String> queue = new ArrayBlockingQueue<String>(10);
    try {
        socket = new Socket("localhost", 5001);
        out = new PrintWriter(socket.getOutputStream(), true);
        new Thread(new ClientReader(socket, queue)).start();

        while (!command.equals("quit")) {

            command = scanner.nextLine();
            if (command.startsWith("move")) {
                out.println(command);
                String response = queue.take();
                System.out.println("Client got response="+response);
            }
        }

    } catch (IOException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        scanner.close();
        out.close();
        try {
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

static class ClientReader implements Runnable {

    private final Socket socket;
    private final BlockingQueue<String> queue;
    public ClientReader(Socket socket, BlockingQueue<String> queue) {
        super();
        this.socket = socket;
        this.queue = queue;
    }

    @Override
    public void run() {
        BufferedReader in = null;
        try {
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String command;
            while ((command = in.readLine()) != null) {
                if (command.equalsIgnoreCase("OK") || command.equalsIgnoreCase("ERR"))
                    queue.put(command);
                else if (command.startsWith("move")) {
                    System.out.println("A player made a move: command="+command);
                }
                else 
                    System.out.println("Unrecognized command="+command);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

}

static class MoveServer implements Runnable {

    @Override
    public void run() {

        Random random = new Random();
        Socket socket = null;
        try {
            ServerSocket ss = new ServerSocket(5001);
            while (true) {

                System.out.println("Listening for new connections");
                socket = ss.accept();
                System.out.println("New session has started");
                PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
                BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

                String command;
                while ((command = in.readLine()) != null) {
                    System.out.println("Got command="+command);
                    int responseType = random.nextInt(3);
                    if (responseType == 0) 
                        out.println("OK");
                    else if (responseType == 1)
                        out.println("ERR");
                    else {
                        out.println("move 1 [3,4]");
                        out.println("OK");
                    }
                }
                in.close();
                out.close();
                socket.close();
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

}

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多