简体   繁体   English

双向多线程套接字连接

[英]Bi-direction multithreaded socket connection

The situation is as following: There is a server and a client, which both can initiate a command/message to each other. 情况如下:有一个服务器和一个客户端,它们可以相互发起命令/消息。

Because the server can send a message at any time, the listening on the socket is done in a separate thread (a ListenerThread ). 因为服务器可以随时发送消息,所以套接字上的侦听是在单独的线程( ListenerThread )中完成的。 This is all fine. 一切都很好。 The client can send messages and receive at the same time, however, how would you know if a certain response belongs to the command you sent when the server can also initiate a new command/message to notify that something happened? 客户端可以同时发送消息和接收消息,但是,当服务器还可以启动新命令/消息来通知发生了某些事情时,如何知道某个响应是否属于您发送的命令呢?

If I send a message to the server, and the server responds with "OK" in the listening thread. 如果我向服务器发送消息,并且服务器在侦听线程中以“确定”响应。 How would you know this is the actual response of the message/command you sent (keeping in mind this is another thread). 您怎么知道这是您发送的消息/命令的实际响应(请记住,这是另一个线程)。 What if the server received an update from another client and sends that update first. 如果服务器从另一个客户端收到更新并首先发送该更新怎么办。

This like a chat application, though with an actual response for every sent command. 就像聊天应用程序一样,尽管每个发送的命令都有实际的响应。

example case: 示例案例:

Let us say that the protocol only consists of a move <playernum> [<x>,<y>] command which indicates that a player has done a move (server notifies client) or that a player wants to do a move (client notifies server). 让我们说该协议仅由move <playernum> [<x>,<y>]命令组成,该命令指示玩家已完成移动(服务器通知客户端)或玩家想要进行移动(客户端通知)服务器)。 Also, the server responds with "OK" if the move was okay or with "ERR" if not. 另外,如果移动正常,服务器将以“确定”进行响应,否则将以“ ERR”进行响应。

Safe state: 安全状态:

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

        OK
client <--- server

Unsafe state: 不安全状态:

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

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

        OK
client <--- server

The client did not expect this response... should responded with OK. 客户端没有期望此响应...应该以OK响应。

You have a protocol where the client can read one of three possible messages: 您有一个协议,客户端可以读取以下三种可能的消息之一:

  1. OK (The move you made was accepted) OK (您的举动已被接受)
  2. ERR (The move you made was rejected) ERR (您的举动被拒绝)
  3. move PLAYERID <co-ord1,co-ord2>

It is a reasonable assumption that the messages OK and ERR will only be sent back to the socket which requested a move . 有一个合理的假设,即OKERR消息只会发送回请求move的套接字。 However a legal move is broadcast to all other players (perhaps excluding the player who moved). 但是,合法move会广播给所有其他玩家(也许不包括移动的玩家)。

Since you can receive unsolicited responses (the moves that other players make), you have correctly created a listener thread. 由于您可以收到未经请求的响应(其他玩家做出的举动),因此您已正确创建了侦听器线程。 You have not described the action your application takes when it receives a move message from another client, but I will assume that your listener thread handles that case. 您尚未描述应用程序从另一个客户端接收到move消息时所采取的操作,但是我将假定您的侦听器线程可以处理这种情况。 What remains is how to co-ordinate your move commands, and the response to that which will appear in the listener thread. 剩下的就是如何协调您的move命令,以及对将出现在侦听器线程中的响应。

To synchronize the submission of your move command, and the response, a BlockingQueue (called queue ) will be used , and shared between the client and listener. 为了同步move命令的提交和响应,将使用BlockingQueue(称为queue ),并在客户端和侦听器之间共享它们。 The form of this will be: 其形式为:

Client: 客户:

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

Listener Thread: 侦听器线程:

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

As you can see, the client simply submits a command, and blocks for the response of "OK" or "ERR". 如您所见,客户端仅提交命令,并阻止响应“ OK”或“ ERR”。 The requirement for processing other player moves has moved into the listener thread. 处理其他播放器移动的要求已移到侦听器线程中。

The listener processes all three conditions (Another player move, an "OK" or an "ERR"). 侦听器处理所有三个条件(另一个播放器移动,“确定”或“错误”)。 The messages responses "OK" and "ERR" are sent back to the client. 消息响应“ OK”和“ ERR”被发送回客户端。 A move command is processed separately, and as such is not the responsibility of the client making the moves. 移动命令是单独处理的,因此,执行移动的客户端不承担任何责任。

Below I have mocked working code which demonstrates these concepts. 下面我模拟了演示这些概念的工作代码。 The server will randomly (with equal probability) respond with: 服务器将随机(以相等的概率)响应:

  1. OK
  2. ERR
  3. A multiline response which includes OK and another player's move 多线回应,包括OK和其他玩家的举动

Code: 码:

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();
            }
        }

    }

}

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM