Обслуживание нескольких клиентов
JabberServer работает, но он может обслуживать только одного клиента одновременно. В типичном сервере, Вы захотите иметь возможность общаться со несколькими клиентами одновременно. Решение - это много поточность, а в языках, которые напрямую не поддерживают многопоточность это означает все виды сложностей. В Главе 14 Вы увидели, что многопоточность в Java настолько просто, насколько это возможно, в то время, как многопоточность вообще является сложной темой. Т.к. поддерка нитей в Java является прямой и открытой, то создание сервера, поддерживающего множество клиентов оказывается относительно простой задачей.
Основная схема это создание единичного объекта ServerSocket в серверной части и вызвать метод accept( ) для ожидания нового соединения. Когда accept( ) возвращает управления, Вы берете возвращенный Socket и используете его для создания новой нити(потока), чьей работой является обслуживание этого клиента. Затем Вы вызываете метод accept( ) снова, для ожидания нового клиента.
В следующем коде серверной части Вы увидите, что это выглядит также как и в примере JabberServer.java за исключением того, что все операции по обслуживанию конкретного клиента перемещены внутрь класса нити (потока):
//: c15:MultiJabberServer.java
// Сервер, использующий многопоточность
// для обслуживания любого числа клиентов.
import java.io.*; import java.net.*;
class ServeOneJabber extends Thread { private Socket socket; private BufferedReader in; private PrintWriter out; public ServeOneJabber(Socket s) throws IOException { socket = s; in = new BufferedReader( new InputStreamReader( socket.getInputStream())); // Включение автосброса буферов:
out = new PrintWriter( new BufferedWriter( new OutputStreamWriter( socket.getOutputStream())), true); // Если какой либо, указанный выше класс выбросит исключение
// вызывающая процедура ответственна за закрытие сокета
// В противном случае нить(поток) закроет его.
start(); // Вызывает run()
} public void run() { try { while (true) { String str = in.readLine(); if (str.equals("END")) break; System.out.println("Echoing: " + str); out.println(str); } System.out.println("closing..."); } catch(IOException e) { System.err.println("IO Exception"); } finally { try { socket.close(); } catch(IOException e) { System.err.println("Socket not closed"); } } } }
public class MultiJabberServer { static final int PORT = 8080; public static void main(String[] args) throws IOException { ServerSocket s = new ServerSocket(PORT); System.out.println("Server Started"); try { while(true) { // Останавливает выполнение, до нового соединения:
Socket socket = s.accept(); try { new ServeOneJabber(socket); } catch(IOException e) { // Если неудача - закрываем сокет,
// в противном случае нить закроет его:
socket.close(); } } } finally { s.close(); } } } ///:~
Нить ServeOneJabber берет объект Socket, который создается методом accept( ) в main( ) каждый раз, когда новый клиент создает соединение. Затем, как раньше, он создает объект BufferedReader и объект PrintWriter с авто-сбросом используя Socket. Наконец, он вызывает специальный метод объекта Thread - start( ), который выполняет инициализации в нити и, затем вызывает метод run( ). Здесь выполняются те же действия, что и в предыдущем примере: чтение данных из сокета, а затем возврат этих данных обратно, пока не придет специальный сигнал - строка “END”.
Ответственность за очистку сокета должна быть снова тщательно обработана. В этом случае, сокет создается за пределами ServeOneJabber так что ответственность может быть разделена. Если конструктор ServeOneJabber завершится неудачно, он просто вызовет исключение вызывающему методу, который затем очистит нить. Но если конструктор завершится успешно, то объект ServeOneJabber возьмет ответственность за очистку нить на себя, в его методе run( ).
Посмотрите, насколько протая реализация у MultiJabberServer. Как раньше, ServerSocket создается и вызывается метод accept( ) для ожидания нового соединения. Но в этот момент, возвращаемое значение accept( ) (объекты Socket) передается в конструктор ServeOneJabber, который создает новую нить для обраболтки этого соединения. Когда соединение закрывается, нить завершает свою работу.
Если создание ServerSocket прерывается, снова выбрасывается в main( ). Но если создание успешное, внешний блок try-finally гарантирует его очистку. Внутренний блок try-catch защищает только от ошибок в конструкторе ServeOneJabber; если конструктор выполняется без ошибок, то нить ServeOneJabber закроет связанный с ней сокет.
Для проверки того, что сервер поддерживает несколько клиентов, следующая программа создает множество клиентов (используя нити) которые подключаются к одному и тому же серверу. Максимальное число нитей определяется переменной final int MAX_THREADS.
//: c15:MultiJabberClient.java
// Клиент для проверки MultiJabberServer
// посредством запуска множества клиентов.
import java.net.*; import java.io.*;
class JabberClientThread extends Thread { private Socket socket; private BufferedReader in; private PrintWriter out; private static int counter = 0; private int id = counter++; private static int threadcount = 0; public static int threadCount() { return threadcount; } public JabberClientThread(InetAddress addr) { System.out.println("Making client " + id); threadcount++; try { socket = new Socket(addr, MultiJabberServer.PORT); } catch(IOException e) { System.err.println("Socket failed"); // Если сокет не создался,
// ничего не надо чистить.
} try { in = new BufferedReader( new InputStreamReader( socket.getInputStream())); // Включение авто-очистки буфера:
out = new PrintWriter( new BufferedWriter( new OutputStreamWriter( socket.getOutputStream())), true); start(); } catch(IOException e) { // Сокет должен быть закрыт при появлении любой ошибки
try { socket.close(); } catch(IOException e2) { System.err.println("Socket not closed"); } } // Иначе сокет будет закрыт
// методом run() у нити.
} public void run() { try { for(int i = 0; i < 25; i++) { out.println("Client " + id + ": " + i); String str = in.readLine(); System.out.println(str); } out.println("END"); } catch(IOException e) { System.err.println("IO Exception"); } finally { // Всегда закрывает его:
try { socket.close(); } catch(IOException e) { System.err.println("Socket not closed"); } threadcount--; // Завершение нити
} } }
public class MultiJabberClient { static final int MAX_THREADS = 40; public static void main(String[] args) throws IOException, InterruptedException { InetAddress addr = InetAddress.getByName(null); while(true) { if(JabberClientThread.threadCount() < MAX_THREADS) new JabberClientThread(addr); Thread.currentThread().sleep(100); } } } ///:~
Конструктор JabberClientThread берет InetAddress и использует его для открытия Socket. Вы возпожно уже начинаете видешь шаблон: Socket используется всегда для создания некоторых типов Reader и/или Writer (или InputStream и/или OutputStream) объект, что является единственным способом, в котором Socket может быть использован. (Вы можете, конечно, написать один-два класса для автоматизации этого процесса, вместо того , чтобы заново все это набирать, если для Вас это сложно.) Итак, start( ) выполняет инициализации нити и вызывает метод run( ). Здесь, сообщения отсылаются серверу и информация с сервера печатается на экране. Однако, нить имеет ограниченное время жизни и в один прекрасный момент завершается. Обратите внимание, что сокет очищается если конструктор завершается неуспешно, после того как сокет создается, но перед тем, как конструктор завершится. В противном случае ответственность за вызов метода close( ) для сокета ложится на метод run( ).
Переменная threadcount хранит число - сколько объектов JabberClientThread в данный момент существует. Она увеличивается в конструкторе и уменьшается при выходе из метода run( ) (и это значит, что нить завершается). В методе MultiJabberClient.main( ), Вы видите, что число нитей проверяется, и если нитей слишком много - новые не создаются. Затем метод засыпает. При этом, некоторые нити будут завершаться и новые смогут быть созданы. Вы можете поэкспериментировать с MAX_THREADS, чтобы посмотреть когда у Вашей системы появятся проблемы с обслуживанием большого количества соединений.