Связывание двух потоков в программе сокет клиент-сервер - Java - PullRequest
0 голосов
/ 08 октября 2011

Я создаю потоки класса A , и каждый из них отправляет сериализованный объект на Сервер , используя ObjectOutputStream.

Сервер создает новые потоки B для каждого сокетного соединения (всякий раз, когда подключается новый A клиент)

B будет вызывать синхронизированный метод для общего ресурса Мьютекс , который заставляет его (B) ждать () , пока не произойдет какое-то внутреннее состояние в Мьютексе.

В этом случае A может узнать, что B в данный момент ожидает?

Надеюсь, это описание понятно.

КлассРасположение:

A1--------->B1-------->|       |
A2--------->B2-------->| Mutex |
A3--------->B3-------->|       |

РЕДАКТИРОВАТЬ: необходимо иметь wait (), notify () или notifyAll (), так как это для академического проекта, где тестируется параллелизм.

1 Ответ

4 голосов
/ 08 октября 2011

Обычно A читает на сокете, который будет «блокировать» (то есть не возвращать, не зависать), пока некоторые данные не будут отправлены обратно B. Это не нужно записывать, чтобы иметь дело со статусом ожидания B. Он просто читает, и это по своей сути предполагает ожидание чего-то, что можно прочитать.

Обновление Таким образом, вы хотите, чтобы пользовательский интерфейс А оставался отзывчивым. Безусловно, лучший способ сделать это - воспользоваться системой очередей событий библиотеки пользовательского интерфейса. Все структуры графического интерфейса пользователя имеют центральный цикл обработки событий, который отправляет события обработчикам (нажатие кнопки, перемещение мыши, таймер и т. Д.). Обычно фоновый поток может отправить что-либо в эту очередь событий, чтобы он выполнялся на главном сервере. Пользовательский интерфейс Детали будут зависеть от используемой вами платформы.

Например, в Swing фоновый поток может сделать это:

SwingUtilities.invokeAndWait(someRunnableObject);

Предположим, вы определили этот интерфейс:

public interface ServerReplyHandler {
    void handleReply(Object reply);
}

Затем создайте хороший API для вашего кода GUI, который будет использоваться, когда он хочет отправить запрос на сервер:

public class Communications {

    public static void callServer(Object inputs, ServerReplyHandler handler);

}

Итак, ваш клиентский код может вызывать сервер так:

showWaitMessage();

Communications.callServer(myInputs, new ServerReplyHandler() {
    public void handleReply(Object myOutputs) {

        hideWaitMessage();
        // do something with myOutputs...

    }
});

Чтобы реализовать вышеупомянутый API, у вас будет потокобезопасная очередь объектов запроса, в которой хранится объект inputs и обработчик для каждого запроса. И фоновый поток, который просто ничего не делает, кроме как извлекает запросы из очереди, отправляет сериализованные входные данные на сервер, читает ответ и десериализует его, а затем делает это:

final ServerReplyHandler currentHandler = ...
final Object currentReply = ...

SwingUtilities.invokeAndWait(new Runnable() {
    public void run() {

        currentHandler.handleReply(currentReply);

    }
});

Поэтому, как только фоновый поток считывает ответ, он передает его обратно в основной поток пользовательского интерфейса посредством обратного вызова.

Именно так браузеры выполняют асинхронную связь из кода JS. Если вы знакомы с jQuery, приведенный выше метод Communications.callServer имеет тот же шаблон, что и:

showWaitMessage();

$.get('http://...', function(reply) {

    hideWaitMessage();

    // do something with 'reply' 
});

Единственное отличие в этом случае заключается в том, что вы пишете весь стек связи вручную.

Обновление 2

Вы спросили:

Вы имеете в виду, что я могу передать "new ObjectOutputStream (). WriteObject (obj)" как "myInputs" в Communications.callServer?

Если вся информация передается как сериализованные объекты, вы можете встроить сериализацию в callServer. Вызывающий код просто передает некоторый объект, который поддерживает сериализацию. Реализация callServer сериализует этот объект в byte[] и отправляет его в рабочую очередь. Фоновый поток извлекает его из очереди и отправляет байты на сервер.

Обратите внимание, что это позволяет избежать сериализации объекта в фоновом потоке. Преимущество этого состоит в том, что вся фоновая активность потока отделена от кода пользовательского интерфейса. Код пользовательского интерфейса может не знать, что вы используете потоки для связи.

Re: wait и notify и т. Д. Вам не нужно писать свой собственный код, чтобы использовать их. Используйте одну из стандартных реализаций интерфейса BlockingQueue . В этом случае вы можете использовать LinkedBlockingQueue с конструктором по умолчанию, чтобы он мог принимать неограниченное количество элементов. Это означает, что отправка в очередь всегда будет происходить без блокировки. Итак:

private static class Request {
    public byte[] send;
    public ServerReplyHandler handler;
};

private BlockingQueue<Request> requestQueue;

public static callServer(Object inputs, ServerReplyHandler handler) {

    ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
    new ObjectOutputStream(byteStream).writeObject(inputs);

    Request r = new Request();
    r.send = byteStream.toByteArray();
    r.handler = handler;
    requestQueue.put(r);
}

Тем временем фоновый рабочий поток делает это:

for (;;) {
    Request r = requestQueue.take();

    if (r == shutdown) {
        break;
    }

    // connect to server, send r.send bytes to it
    // read back the response as a byte array:

    byte[] response = ...

    SwingUtilities.invokeAndWait(new Runnable() {
        public void run() {
            currentHandler.handleReply(
                new ObjectInputStream(
                    new ByteArrayInputStream(response)
                ).readObject()
            );
        }
    });
}

Переменная shutdown просто:

private static Request shutdown = new Request();

т.е. это фиктивный запрос, используемый как специальный сигнал. Это позволяет использовать другой общедоступный статический метод, позволяющий пользовательскому интерфейсу запрашивать выход фонового потока (возможно, очистит очередь перед добавлением shutdown).

Обратите внимание на основы шаблона: объекты интерфейса никогда не доступны в фоновом потоке. Они управляются только из потока пользовательского интерфейса. Существует четкое разделение собственности. Данные передаются между потоками как байтовые массивы.

Вы можете запустить несколько рабочих, если хотите поддерживать более одного запроса одновременно.

...