Как работать с клиент-серверным приложением для отправки сообщений и/или файлов

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

Я не очень разбираюсь в клиент-серверных приложениях, и это моя первая попытка, поэтому, пожалуйста, будьте терпеливы, если я допустил какую-то ошибку или задал глупые вопросы.

Теперь вернемся к моей проблеме..

Мне нужно создать многоклиентское/серверное приложение.

Серверная часть должна быть простым менеджером для клиентов.

Так, например:

  • сервер может изменять некоторые параметры клиентов
  • принимает файлы от клиентов
  • другие скучные вещи.. которые в основном могут быть переведены в отправку строки или отправку файлов или получение строки и файлов

Клиенты, с другой стороны, представляют собой сложное приложение (по крайней мере, для пользователя), которое должно отправлять на сервер:

  • некоторые регистрационные данные пользователя (здесь нет необходимости в безопасности, я уже довольно запутался с простым подключением клиент/сервер)
  • PSD-файл, который должен быть результатом работы пользователя, поэтому, когда пользователь нажимает «Я готов», приложение получает этот PSD-файл и отправляет его на сервер для хранения.
  • другая информация о клиенте, такая как данные конфигурации по умолчанию и т. д.

Итак, мой вопрос в основном таков: как я могу обрабатывать связь с сервера с одним конкретным клиентом? Я имею в виду, что у меня есть сервер, и я хочу изменить конфигурацию только для одного клиента.

Я предполагаю, что мне нужно каким-то образом хранить клиентов ... например, в массиве (List), но я не знаю, правильный ли это способ сделать это. (В принципе, я не знаю, как работают классы Socket и ServerSocket.. если это поможет вам лучше понять)

Кроме того, когда сервер работает и слушает... графический интерфейс должен быть обновлен, чтобы отображать новых подключенных клиентов, поэтому мне нужен какой-то прослушиватель для сервера, который запускает действие обратно в интерфейс, когда появляется новый клиент? (Многие люди используют метод while(true) { socket = server.accept(); }, но мне это не кажется очень умным..)

Это основные файлы Client.java и Server.java, содержащие базовые функции клиента и сервера, которые я написал на основе большого количества поисковых запросов Google.

Но весь приведенный ниже код не соответствует всем моим потребностям.

Клиент.java

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class Client extends Socket {

    private static Client    instance    = null;

    /**
     * The main init() function for this class, to create a Singleton instance for the Client
     * 
     * @param host
     *            The host of the Server
     * @param port
     *            The port of the Server
     * @return The Client instance that is a new instance if no one exists previusly,
     *         otherwise an older instance is returned
     * @throws UnknownHostException
     * @throws IOException
     */
    public static Client init( String host, Integer port ) throws UnknownHostException, IOException
    {
        if ( Client.instance == null )
            Client.instance = new Client( host, port );
        return Client.instance;
    }

    /**
     * Default Constructor made private so this class can only be instantiated by the
     * singleton init() function.
     * 
     * @param host
     *            The host of the server
     * @param port
     *            The port of the server
     * @throws UnknownHostException
     * @throws IOException
     */
    private Client( String host, Integer port ) throws UnknownHostException, IOException
    {
        super( host, port );
    }

    /**
     * Function used to send a file to the server.
     * When this function fires, the Client class start sending a file to the server.
     * Internally this function handles the filesize, and some other file information
     * that the server needs to store the file in the correct location
     * 
     * @param filename
     *            The filename of the file that will be sended to the server
     */
    public void sendFile( String filename ) throws FileNotFoundException, IOException
    {
        // The file object from the filename
        File file = new File( filename );

        // A string object to build an half of the message that will be sent to the exceptions
        StringBuilder exception_message = new StringBuilder();
        exception_message.append( "The File [" ).append( filename ).append( "] " );

        // Check if the file exists
        if ( !file.exists() )
            throw new FileNotFoundException( exception_message + "does not exists." );

        // Check if the file size is not empty
        if ( file.length() <= 0 )
            throw new IOException( exception_message + "has zero size." );

        // Save the filesize
        Long file_size = file.length();

        // Check if the filesize is something reasonable
        if ( file_size > Integer.MAX_VALUE )
            throw new IOException( exception_message + "is too big to be sent." );

        byte[] bytes = new byte[file_size.intValue()];

        FileInputStream fis = new FileInputStream( file );
        BufferedInputStream bis = new BufferedInputStream( fis );
        BufferedOutputStream bos = new BufferedOutputStream( this.getOutputStream() );

        int count;

        // Loop used to send the file in bytes group
        while ( ( count = bis.read( bytes ) ) > 0 )
        {
            bos.write( bytes, 0, count );
        }

        bos.flush();
        bos.close();
        fis.close();
        bis.close();
    }

    /**
     * Function used to send string message from client to the server
     * 
     * @param message
     *            The string message the server should get
     * @throws IOException
     */
    public void sendMessage( String message ) throws IOException
    {
        OutputStream os = this.getOutputStream();
        OutputStreamWriter osw = new OutputStreamWriter( os );
        BufferedWriter bw = new BufferedWriter( osw );

        bw.write( message );
        bw.flush();
    }

    /**
     * Function used to get a message from the Server
     * 
     * @return The message the server sent back
     * @throws IOException
     */
    public String getMessage() throws IOException
    {
        InputStream is = this.getInputStream();
        InputStreamReader isr = new InputStreamReader( is );
        BufferedReader br = new BufferedReader( isr );

        String message = br.readLine();

        return message;
    }
}

Сервер.java

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class Server extends ServerSocket {

    private static Server    instance    = null;
    private Socket            socket        = null;

    /**
     * 
     * @param port
     * @return
     * @throws IOException
     */
    public static Server init( Integer port ) throws IOException
    {
        if ( Server.instance == null )
            Server.instance = new Server( port );
        return Server.instance;
    }

    /**
     * 
     * @param port
     * @throws IOException
     */
    private Server( Integer port ) throws IOException
    {
        super( port );

        // Maybe this is something that needs to be improved
        while ( true )
            this.socket = this.accept();
    }

    /**
     * 
     * @param message
     * @throws IOException
     */
    public void sendMessage( String message ) throws IOException
    {
        OutputStream os = this.socket.getOutputStream();
        OutputStreamWriter osw = new OutputStreamWriter( os );
        BufferedWriter bw = new BufferedWriter( osw );

        bw.write( message );

        bw.flush();
    }

    /**
     * 
     * @return
     * @throws IOException
     */
    public String getMessage() throws IOException
    {
        InputStream is = this.socket.getInputStream();
        InputStreamReader isr = new InputStreamReader( is );
        BufferedReader br = new BufferedReader( isr );

        String message = br.readLine();

        return message;
    }
}

эмм.. извините за мой английский.. пожалуйста.


person Andrea Rastelli    schedule 05.09.2013    source источник
comment
Самый простой способ обработки нескольких клиентов на вашем сервере — использовать поток для каждого соединения.   -  person Cruncher    schedule 05.09.2013
comment
@Cruncher Я где-то видел что-то подобное, и в основном мне нужно сделать новый класс Runnable Socket для серверной стороны, в котором хранится сокет server.accept(), верно? Но даже если я сделаю что-то подобное, как мне обработать связь между сервером и конкретным клиентом?   -  person Andrea Rastelli    schedule 05.09.2013
comment
@Crunchier: Не обязательно самый простой. Один из самых простых. Циклы событий в одном потоке также могут работать нормально. Multi-Process также может работать нормально. Все они в чем-то проще, в чем-то сложнее.   -  person haylem    schedule 05.09.2013
comment
@haylem в любом случае, по моему опыту, многопоточность была самой простой, но определенно не самой масштабируемой, в зависимости от долговечности соединений.   -  person Cruncher    schedule 05.09.2013
comment
@AndreaRastelli Метод run будет зацикливаться для каждого клиента, который будет другим экземпляром этого класса. В этом классе вы храните информацию о том, кто этот клиент. Всякий раз, когда вы получаете сообщение в этом случае, у вас есть вся необходимая информация.   -  person Cruncher    schedule 05.09.2013
comment
@Cruncher Итак, как я и думал, мне нужен какой-то List, в котором хранятся все эти объекты Thread, ведь у одного из них есть метод run(), который постоянно запрашивает что-то у соответствующего клиента .. верно?   -  person Andrea Rastelli    schedule 05.09.2013
comment
@haylem как насчет цикла событий, о котором вы говорили? Можете ли вы объяснить это более подробно или дать ссылку на несколько полезных веб-страниц здесь, пожалуйста?   -  person Andrea Rastelli    schedule 05.09.2013
comment
@AndreaRastelli, хранящий список соединений, необходим только в том случае, если клиентам нужно как-то общаться друг с другом, например, в чат-клиенте. Если их отношения связаны только с сервером (например, с веб-сервером), то в этом нет необходимости, экземпляр может содержать информацию о сокете.   -  person Cruncher    schedule 05.09.2013
comment
@AndreaRastelli: вот пример концепции сервера Tornado Python: tornadoweb.org /en/stable/ioloop.html . Это довольно простая вещь. На самом деле, если вы никогда раньше не работали с клиент-сервером, я думаю, вам стоит начать с этого подхода, а затем переходить к другим. Это довольно просто, сервер прослушивает входящие соединения, а затем отправляет их функциям-обработчикам. Как уже упоминалось, в некоторых областях он менее интуитивно понятен, чем подход, основанный на машинном переводе (например, для управления клиентскими подключениями к данным), но он прост.   -  person haylem    schedule 05.09.2013
comment
@AndreaRastelli: Однако эта концепция не ограничивается клиент-серверным сетевым кодом. Это обычная концепция и для фреймворков с графическим интерфейсом, где обычно используется то, что часто называют циклом большого события. Иногда этот BEL может быть в отдельном потоке (например, EventDispatchThread в Java Swing). Это в значительной степени продолжение упомянутого вами метода while (true) { accept(); /*...*/ }. И в последнее время он снова стал более привлекательным с растущим использованием серверного JavaScript, в частности из-за Node.JS. Хорошая ли это идея... Ну, это зависит от того, что вы хотите сделать.   -  person haylem    schedule 05.09.2013
comment
@Cruncher ммч .. Итак, если по какой-то причине соединение с одним клиентом прерывается, и клиенту необходимо повторно подключиться к серверу .. У меня два раза создается экземпляр одного и того же клиента .. или я что-то пропустил? То, о чем я думал, было чем-то вроде Map<String, ClientThread>. Итак, если мне нужно получить какую-то информацию о клиенте, я всегда могу получить к ней доступ по ключу карты, а если соединение прервется... Мне просто нужно перезаписать тот же ключ в карте, верно?   -  person Andrea Rastelli    schedule 05.09.2013
comment
@AndreaRastelli Что ж, после того, как соединение прервется, поток должен остановиться. Если вы хотите сохранить какую-либо информацию о последнем подключении при следующем подключении, то карта будет идеальной. Вам действительно нужно убедиться, что любая строка, которую вы используете для сопоставления, уникальна. Кроме того, если это приложение использует конфиденциальную информацию, это создает угрозу безопасности. Если бы я знал чью-то уникальную строку, я мог бы обманом заставить вас дать мне их сеанс.   -  person Cruncher    schedule 05.09.2013
comment
@AndreaRastelli В продолжение, если вам нужно постоянно хранить эту информацию, вам также может понадобиться база данных.   -  person Cruncher    schedule 05.09.2013
comment
@AndreaRastelli: хорошей программной идеей для начала работы с клиент-серверным программированием является простая программа клиент / сервер IRC или клиент / сервер FTP. Это может быть полезно, поскольку у вас уже есть существующие реализации для тестирования и сравнения, и вы можете сосредоточиться на одной стороне проблемы за раз, если хотите.   -  person haylem    schedule 05.09.2013
comment
@Cruncher хорошо... база данных - хорошая идея, но время, которое у меня есть, чтобы завершить это приложение, ограничено (и я должен закончить все к 16-му числу этого месяца, а клиент/сервер - это всего лишь один того, что мне нужно сделать). Что касается безопасности... как я уже упоминал, на самом деле я довольно запутался со всеми этими вещами клиент/сервер.   -  person Andrea Rastelli    schedule 05.09.2013
comment
@haylem хорошо .. IRC - хорошая идея для начала, но время, которое у меня есть, чтобы завершить этот проект, заканчивается 16 числа этого месяца.   -  person Andrea Rastelli    schedule 05.09.2013
comment
@AndreaRastelli Похоже, тебе нужна команда   -  person Cruncher    schedule 05.09.2013
comment
@Cruncher звучит так, будто мне нужна новая работа. Поверьте мне.   -  person Andrea Rastelli    schedule 05.09.2013
comment
@AndreaRastelli: 11 дней звучит выполнимо: вы кажетесь активными в своих исследованиях! Не уверен, о чем весь проект, но это кажется приемлемым, если интерфейс не должен быть слишком причудливым, и вы можете отложить дополнительные функции до более поздних сервисных выпусков. Тем не менее, вы можете держать своих руководителей / заинтересованных лиц в курсе развития дела, если крайний срок приближается.   -  person haylem    schedule 05.09.2013
comment
@AndreaRastelli: Кроме того, сначала я отнесся к вашему вопросу скорее как к личному стремлению, поэтому я не рекомендовал этого, но вы, возможно, захотите взглянуть на какую-то структуру, чтобы облегчить большую часть боли и не слишком беспокоиться об идеальном случае ( за исключением случаев, когда у вас есть требования, о которых вы нам не сообщили, например потребности в масштабируемости и т. д.). Могу я предложить вам взглянуть на что-то вроде netty.io (чтобы оставаться на стороне событийного программирования модели)?   -  person haylem    schedule 05.09.2013
comment
Если вы просто хотите, чтобы клиенты отправляли строки или файлы на сервер, возможно, вам стоит взглянуть на спокойные веб-сервисы. Эталонная реализация — jersey jersey.java.net.   -  person mike    schedule 05.09.2013
comment
@haylem в Италии (вообще) и в компании, в которой я работаю (в частности), программисты — профессиональная фигура, которая выглядит чем-то раздражающим и нужна просто для того, чтобы быть конкурентоспособными на рынке. Если завтра способ быть конкурентоспособным станет носить розовую ленточку, то уровень безработицы может подняться до невиданного ранее уровня.   -  person Andrea Rastelli    schedule 05.09.2013
comment
@mike может быть прав в том, что реализация этого поверх веб-сервера с использованием служб REST также избавит вас от большинства проблем. Но я предполагаю, что могут быть требования, о которых мы не знаем, или что вы изначально хотели более простой подход (и, возможно, у вас нет опыта веб-разработки? Если у вас есть, это может быть быстрее для вас).   -  person haylem    schedule 05.09.2013
comment
@haylem Ну .. У меня нет абсолютно никаких знаний в Java, связанных с Интернетом (или сетью, или как там это называется). В java я знаю только, как делать графические вещи (я математик со степенью в области прикладной математики для графики, и я много использовал java, а также обработку, чтобы делать визуальные вещи с помощью OpenGL), но для такого рода задача я совсем новичок. Это моя (вынужденная) первая попытка построить что-то вроде этого клиент/сервер.   -  person Andrea Rastelli    schedule 05.09.2013
comment
Отдых не так уж и сложен. Если никто другой не сделает, я создам для вас пример отдыха. Но я сейчас тороплюсь. ... если вы используете трикотаж вместе с веб-сервером гризли, у вас есть 100% решение Java.   -  person mike    schedule 05.09.2013
comment
@mike спасибо, но я думаю, что я попробую фреймворк netty.io, который предложил Хайлем ... если я потрачу больше пары часов на настройку и другие вещи, я попробую REST, который вы предложил .. в любом случае спасибо, вы все мне очень помогли!   -  person Andrea Rastelli    schedule 05.09.2013
comment
@AndreaRastelli: просмотрите примеры на этой странице, особенно первые в разделе «Основные» и текстовые протоколы для основ. Затем выберите те, которые соответствуют вашим потребностям.   -  person haylem    schedule 05.09.2013


Ответы (2)


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

Сервер обрабатывает каждого клиента в своем собственном «потоке», можно сказать, что это базовая архитектура клиент/сервер. Но я использовал новый Callable<V> вместо потоков.

Я не продлевал ни Socket, ни ServerSocket. Я нервно видел это раньше. Я думаю, что в этом случае лучше предпочесть композицию наследованию. Это дает вам больше контроля, поскольку вы можете делегировать то, что и как вы предпочитаете.

Для получения дополнительной информации я рекомендую вам ознакомиться с руководствами по Oracle.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ClientServerExample
{
  private final static int PORT = 1337;
  private final static String LOOPBACK = "127.0.0.1";

  public static void main(String[] args) throws IOException
  {
    ExecutorService se = Executors.newSingleThreadExecutor();
    se.submit(new Server(PORT, 5));

    ExecutorService ce = Executors.newFixedThreadPool(3);
    for (String name : Arrays.asList("Anton", "John", "Lisa", "Ben", "Sam", "Anne"))
      ce.submit(new Client(name, LOOPBACK, PORT));

    ce.shutdown(); while (!ce.isTerminated()) {/* wait */}
    se.shutdown();
  }
}

class Client implements Callable<Void>
{
  private final String name;
  private final String ip;
  private final int port;

  public Client(String name, String ip, int port)
  {
    this.name = name;
    this.ip = ip;
    this.port = port;
  }

  @Override
  public Void call() throws Exception
  {
    Socket s = new Socket(ip, port);
    PrintWriter out = new PrintWriter(s.getOutputStream(), true);
    out.println("Hi, I'm " + name + "!");
    out.close();
    s.close();
    return null;
  }
}

class Server implements Callable<Void>
{
  private final int port;
  private final int clients;
  private final ExecutorService e;

  public Server(int port, int clients)
  {
    this.port = port;
    this.clients = clients;
    this.e = Executors.newFixedThreadPool(clients);
  }

  @Override
  public Void call() throws Exception
  {
    ServerSocket ss = new ServerSocket(port);
    int client = 0;
    while (client < clients)
    {
      e.submit(new ClientHandler(client++, ss.accept()));
    }
    ss.close();
    e.shutdown(); while (!e.isTerminated()) {/* wait */}
    return null;
  }
}

class ClientHandler implements Callable<Void>
{

  private int client;
  private Socket s;

  public ClientHandler(int client, Socket s)
  {
    this.client = client;
    this.s = s;
  }

  @Override
  public Void call() throws Exception
  {
    BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
    String fromClient;
    while ((fromClient = in.readLine()) != null)
    {
      System.out.println("FROM CLIENT#" + client + ": " + fromClient);
    }
    in.close();
    s.close();
    return null;
  }
}

ВЫВОД

ОТ КЛИЕНТА №0: Привет, я Джон!
ОТ КЛИЕНТА №2: Привет, меня зовут Сэм!
ОТ КЛИЕНТА №1: Привет, меня зовут Бен!
ОТ КЛИЕНТА №3: Привет, меня зовут Анна!
ОТ КЛИЕНТА №4: Привет, меня зовут Антон!

person mike    schedule 05.09.2013

Это пока работа в процессе, потерпите меня... Изначально я просто обращался к тому, что я уже разместил в комментариях относительно возможных моделей. обновления будут позже сегодня вечером.

Различные модели

Обычно на ум приходят эти 3 подхода:

  • Многопроцессная модель
  • Многопоточная модель
  • Модель цикла событий

Однако они не ограничиваются моделью клиент-сервер для сетевого программирования, и довольно часто их можно увидеть в других сценариях, например, в программировании с графическим интерфейсом.

Многопроцессная модель

// входящий

Многопоточная модель

// входящий

Модель цикла событий

цикл событий — это конструкция, в которой программа ожидает поступления инструкций для отправки сообщений в другие части система. Его главное преимущество заключается в простоте реализации и легкости.

Это особенно полезно, если ваша основная программа не должна выполнять никаких ресурсоемких и длительных действий, чтобы не блокировать следующее входящее соединение слишком долго. Идея состоит в том, чтобы просто подождать, получить соединение, отправить в отдельную подсистему какое-то действие и дождаться ответа, пока вы не сможете ответить.

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


Обновлять

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

Итак... основываясь на том, что мы обсудили, я бы порекомендовал вам обратиться к netty.io< /strong> платформа сетевого программирования, управляемая событиями (примеры см. в вики).

person haylem    schedule 05.09.2013
comment
Пожалуйста, продолжайте обновлять этот ответ, это то, что мне нужно изучить для будущих обновлений и обновлений для этого приложения, поэтому, чем больше я узнаю, тем лучше я буду совершенствоваться в будущем. - person Andrea Rastelli; 05.09.2013