Добавить в сетевой чат запись локальной истории в текстовый файл на клиенте java

Обновлено: 01.07.2024

Применяя здесь множество беспрецедентных концепций, это простой чат-сервер, способный обрабатывать несколько клиентов, которые запускаются через JavaFX, и создавать свои отдельные потоки и обрабатывать их в потоке приложения (было более сложным, чем ожидалось).

Сервер чата :

Клиент чата :

Я хотел бы сосредоточиться на:

  1. Общая критика по обработке потоков.
  2. Способ обработки клиентского и клиентского ввода.
  3. Я также задаюсь вопросом об использовании flush для потоков. Кажется, это не имеет значения, но повысит ли производительность, особенно когда это произойдет через сеть? Пока что я тестировал только локально.
  4. Протокол сервера и сервера.
  5. Общая производительность и эффективность.
  6. Общая расширяемость моих вариантов дизайна.

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

Для любого заинтересованного этого является репозиторий Github.

3 ответа

Сервер

Вместо extends Thread для ClientHandler , возможно, вы также можете рассмотреть implements Runnable ). Конечно, вам придется замените new ClientHandler(socket).start() на new Thread(new ClientHandler(socket)).start() тоже.

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

Другим преимуществом здесь является то, что ваш код isEmpty() также становится интернализованным и, следовательно, избыточным для вас, чтобы сделать это явно.

Client

Не так много комментариев, за исключением того, что, возможно, вы можете рассмотреть возможность замены сравнения String в методе call() с помощью enum -driven, например (пожалуйста, подумайте, как можно легко получить доступ к таким переменным, как out и messageArea ), Я не учитывал это в ):

Обработка выполняется полностью внутри метода handle(String) , который равномерно проходит через представление toString() каждого enum ) , в то же время заботясь о предварительном форматировании фактического ввода, чтобы «потреблять», выполнив input.substring(v.toString().length()) . Только одно замечание для обработки "INFO" : похоже, что вы жестко кодируете число клиентов, чтобы быть однозначными, поэтому я позволил вам проиллюстрировать, как вы может обойти это с помощью стратегически размещенного разделителя, например "," .

Запуск start(Stage stage) делает слишком много для того, что мне нравится. Вы создаете Анонимный класс, который охватывает логику чтения сокета и представления клиенту. Я бы создал отдельный класс, который бы извлекал эту логику. Это должно было бы передать сокет и текстовое поле, но я уверен, что это элегантно, либо с помощью конструктора, либо с классом, который будет инкапсулировать эту часть логики.

В настоящий момент вы используете непосредственно код String вашей команды в своем коде. Это прекрасно, потому что у вас есть только два класса, но вероятность ошибок есть. Я бы создал Enum , который будет выглядеть так:

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

Как и @rolfl, в своем комментариях , хорошая причина не наследовать Thread заключается в том, что вы не изменяете /расширяете функциональность класса Thread . Вы выполняете код, который должен выполняться в Thread , который отвечает за интерфейс Runnable . (Оба его комментария находятся на месте, поэтому я рекомендую вам прочитать их оба.)

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

Вы используете HashSet<> непосредственно вместо использования Set<> . Это то же самое, что и с List и почти любым интерфейсом. Если вам действительно не нужна эта конкретная реализация по определенной причине, используйте интерфейс. Тогда ваш код будет свободен от блокировки с конкретной реализацией.

Сервер

Я думаю, что вы используете ключевое слово static . Не ставьте каждую статическую функцию, создавайте объект класса и вместо этого используйте объект.

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

Используйте композицию над наследованием, не распространяйте Thread. Вместо этого выполните Runnable , как это было предложено другими.

Это один большой метод start в ClientHandler . Вы должны разрезать его на отдельные части, например: requestName , connected , messageLoop .

Если вы используете ConcurrentHashSet для names , вам не нужно использовать synchronized (names) .

В целом кажется, что вы слишком мало синхронизировались, что может привести к ошибкам параллелизма. Вы ничего не синхронизируете при добавлении /удалении в userNames , names и writers .

Я не понимаю, почему у вас есть переменная usersConnected (которая также не является частью любой синхронизации btw). Почему бы не использовать размер одного из наборов вместо этой переменной int ?

userNames , names и writers очень связаны друг с другом. Я бы реорганизовал это на Set<Client> , где у клиента есть userName , name и writer . Этот набор может предпочтительно быть ConcurrentHashSet .

Client

Пожалуйста, по крайней мере, сделайте поля класса private .

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

И клиент, и сервер полагаются на кодировку по умолчанию , например InputStreamReader . Вместо этого используйте UTF-8. Я считаю, что если вы будете использовать инструмент findbugs, это также предупредит вас об этом.

FutureTask

Эта часть дублируется:

Вы можете легко реорганизовать это на createNamePromptTask("Choose a screen name:")

Кроме того, ваш FutureTask вызывает showAndWait в диалоговом окне dialog , что делает его синхронным диалогом. Я бы предпочел, чтобы диалог был создан, показан, и код продолжался. Затем, когда диалог был закрыт, вы используете event listener или обратный вызов , который выполняет out.println(/* get value of text field in dialog */); Возможно, вы сможете выполнить это, используя один из onXYZ свойства в диалоге .

Сервер в автономном режиме

Вы уверены, что единственная причина, по которой это может произойти, заключается в том, что сервер отключен? Что делать, если вы отсоединяете сетевой кабель?

Вы можете реорганизовать этот код, чтобы использовать реальный Map<String, Consumer<String>>

Угадайте, что такое 8 ? Магическое число, да!

Ваши конкретные вопросы

Обработка потоков. Было бы неплохо проверить, был ли поток прерван, выполнив что-то вроде if (Thread.interrupted()) break; , когда вы находитесь внутри петля. В противном случае это выглядит нормально.

Клиент и клиентский вход: я не поклонник showAndWait , который приводит к синхронному диалогу. Ваши три тесно связанные переменные в Server userNames , names , writers также являются запахом для меня.

Промывка потока: вам не нужно вызывать его явно, потому что вы создаете PrintWriter с параметром true .

Если вы начали читать эту статью, то, скорее всего, имеете какое-то отношение к IT-и понимаете что такое IP-адрес – уникальный адрес, который определяет компьютер в сети.

Но достаточно ли такой адресации для полноценной работы? Предположим, что на некотором компьютере запущены одновременно две программы, которые взаимодействуют с интернетом – получают и/или отсылают какие-то данные. Программы никак между собой не связаны и общаются с разными интернет-сервисами. Но они расположены на одном компьютере, следовательно, имеют один IP-адрес. Если они одновременно должны получить данные от двух разных серверов, как же они определят, кому какие данные предназначались?

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

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

Что же собой представляет этот загадочный порт? Вы можете взять отвёртку и перебрать весь компьютер, но портов так и не найдёте. Это просто число, которое передаётся вместе с данными. Теоретически, оно может находиться в диапазоне от 1 до 65535, но порты 1..1024 используются системными программами и занимать их не стоит. Поэтому порт следует выбирать из диапазона 1025..65535.

Планируем

Даже в таких простых программах, как чат, не нужно сразу лезть в IDEи писать что-то невнятное. Для начала стоит осмыслить теорию, с которой мы ознакомились (вы же её не пропустили, правда?) и понять, что она значит в контексте нашей программы.

  • Необходимо два режима работы программы – серверный и клиентский

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

Написание программы

Теперь, когда все подготовительные момент ясны, можно преступить к самому интересному – написанию программы.

Файлы и структура пакетов

Вы, конечно, знаете, что любая программа на Javaначинается с метода main(String[] args). Для большей наглядности не будем добавлять его к другим классам, а создадим отдельный класс Mainи пакет для него – main. В любой программе наверняка будут какие-то константы. Я предпочитаю выносить их в отдельный файл в виде publicstaticполей, поэтому создам класс Constи также добавлю его в пакет main.

Как мы помним, программа должна работать в режиме клиента или сервера. Создадим два соответствующих класса Clientи Server.

В итоге дерево пакетов выглядит так:

Структура каталогов

Выбор режима работы

Для начала нужно выбрать, в каком режиме запускать программу – сервер или клиент. Это нам и нужно первым делом узнать у пользователя, поэтому в метод main(…) пишем следующее:

Здесь всё достаточно просто – спрашиваем, как запускать программу, считываем букву ответа и запускаем соответствующий класс. Стоит пояснить только по поводу класса Scanner – это класс стандартной библиотеки, который облегчает работу с вводом данных из консоли. Он инициализируется стандартным потоком ввода.

Режим клиента

Пойдём от простого к сложному и сначала реализует клиентский режим работы.

Если сервер просто запускается и ждём пользователей, то клиентам приходится проявлять некоторую активность, а именно – подключиться к серверу. Для этого нужно знать его IPи порт подключения. Порт является константой, поэтому зададим его в Const.java:

Constобъявлен как abstract, т.к. содержит только статические данные и создавать его экземпляр ни к чему.

IPдолжен ввести пользователь, поэтому в конструкторе Client пишем:

Теперь у нас есть все необходимые данные – ip, порт, режим работы программы. Можно подключаться к серверу. Сначала создадим сокет:

При этом сразу же производится подключение и можно передавать и считывать данные. Но как это сделать, если данные передаются только через потоки? Каждый Socketсодержит входной и выходной потоки класса InputStreamи OutputStream. Можно работать прямо с ними, но лучше для удобства «завернуть» их во что-то более функциональное:

Любые операции с потоками и сокетами должны выполняться внутри блока try..catch, для того, чтобы обрабатывать ошибки.

Thread– класс Java, который реализует такую незаменимую вещь, как многопоточность. Это возможность программы одновременно выполнять разные наборы действий. Как раз то, что нам сейчас нужно.

В конструкторе создадим объект этого класса и запустим поток:

Итоговый файл Client.java вместе с остальными приведён в конце статьи.

Режим сервера

Сервер, в отличие от клиента, работает не с классом Socket, а с ServerSocket. При создании его объекта программа никуда не подключается, а просто создаётся сервер на порту, переданном в конструктор.

Теперь осталось только создать сервер, который будет принимать подключения, создавать объекты Connectionи добавлять их в массив. В конструкторе класса Server пишем:

Метод server.accept() указывает серверу ожидать подключения. Как только какой-то клиент подключится к серверу, метод вернёт объект Socket, связанный с этим подключением. Дальше создаётся объект Connection, инициализированный этим сокетом и добавляется в массив. Не забываем про try..catchи в конце закрываем все сокеты вместе с потоками методом closeAll();

Исходники

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

Основы Java_19 | Небольшой многопоточный сетевой чат с комплексным практическим опытом работы на Java (с исходным кодом)

Базовые знания Java, использованные в этом проекте:

  • Сетевое взаимодействие Java
  • Многопоточность Java
  • Потоки ввода и вывода Java
  • Рисунок графического интерфейса пользователя Java AWT

Справочное руководство по каждому пункту знаний:

Если вам интересно, вы также можете добавить функцию передачи файлов по сети, обратитесь к руководству:

Эффект от бега следующий:



Эффект от бега следующий:

Начать мониторинг сервера


Клиент подключается к серверу


После подключения клиента вы также можете увидеть на сервере:




Чтобы получать больше интересных статей и информационных ресурсов, подпишитесь на мою общедоступную учетную запись WeChat: "mculover666" 。


Интеллектуальная рекомендация


Краткое описание общих функций MPI

содержание 1, основная функция MPI 2, точка-точка функция связи 3, коллективная функция связи 1, основная функция MPI MPI_Init(&argc, &argv) Информировать системы MPI для выполнения всех необх.

Примечание 9: EL выражение


JVM память

концепция Виртуальная машина JVM управляет собственной памятью, которая разделяет память во многие блоки, наиболее распространенной для памяти стека и памяти кучи. 1 структура виртуальной машины JVM H.

Проблема сетевого запроса на Android 9.0

вЗапустите Android 9 (API Уровень 28) или вышеНа устройстве операционной системы Android, чтобы обеспечить безопасность пользовательских данных и устройств, использование по умолчанию для зашифрованно.


Учебная запись по Webpack (3) В статье рассказывается о создании webpack4.0.

предисловие Для изучения веб-пакета автор также предпринял много обходных путей. Есть много вещей, которые я хочу знать, но я не могу их найти. Автор поможет вам быстро начать работу. Цель этой статьи.

В архитектуре используется архитектура Server - Client (s).
Большая часть кода находится в Java, JavaFX для графического интерфейса и PostgreSQL в качестве базы данных.

Поскольку это приложение чата (рабочий стол), я хотел бы знать, какой из них лучше всего хранить в истории чатов:

  1. Локально в текстовом файле, который клиент должен читать каждый раз
  2. В базе данных по типу String (VarChar)
  3. На сервере в виде списков

Некоторые вопросы основаны на трех способах:

  1. Если клиент подключается с другого компьютера, текстового файла там не будет
  2. Можно ли хранить каждую текстовую запись в базе данных с помощью chatroomID?
  3. Сколько объектов может храниться на сервере до тех пор, пока оно выполняется?

Из ваших трех вариантов я рекомендую вам выбрать вариант № 2 для хранения истории чата: база данных, и вот почему:

  1. Если вы храните историю чата локально в текстовом файле, вы сталкиваетесь с такими проблемами, как синхронизация с другими. Кроме того, вы можете изменять содержимое текстового файла, не просматривая свою программу Java (например, с помощью редактора). Если этот файл содержит чаты с конфиденциальной информацией, и у кого-то есть доступ к вашему компьютеру, они могут его прочитать. Это вызывает проблемы.
  2. Хранение в базе данных - отличная идея, поскольку она обеспечивает центральное расположение для всей вашей Java-программы. Это особенно удобно, если несколько человек используют ваш Java-клиент, таким образом, они могут получать историю чата, а также легко передавать чаты другим пользователям! Я бы не только использовал тип String (VarChar), но и попытался подумать о некоторых полезных полезных полях или столбцах, которые могут быть полезны (например, timeSent, chatUserID, timeRead и т.д.). Это также доказывает, что, используя базу данных, вы можете настроить некоторые права доступа пользователей (имя пользователя и пароль), чтобы конкретные люди могли читать определенные чаты.
  3. Если вы храните чаты на сервере в виде списка на самом сервере Java, и если ваш сервер перезагружается, вы теряете всю историю чатов. Облом.

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

Разработка клиент-серверного чата на Java. Часть 1. Немного теории и сервер

2012-06-23 в 20:53, admin , рубрики: java, клиент-серверные приложения, ооп, Софт, чат, метки: java, клиент-серверные приложения, чат

Краткое описание

Ну и, разумеется, учту все ошибки и недочеты, которые будут озвучены.

Немного теории

Итак, для начала. Проект будет состоять из двух частей: клиента и сервера. Клиент будет иметь GUI, написанный с помощью библиотеки Swing. Сервер GUI иметь не будет, только log-файл и небольшой вывод в консоль.

Для написания чата, нам понадобятся некоторые знания.

Сокеты

Поскольку взаимосвязь клиента и сервера у нас будет реализована с помощью сокетов, познакомимся с ними поближе.

Со́кеты (англ. socket — углубление, гнездо, разъём) — название программного интерфейса для обеспечения обмена данными между процессами. Сокет — абстрактный объект, представляющий конечную точку соединения.

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

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

Передача объектов по сети

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

Итак, что такое сериализация объектов.

Сериализация — процесс перевода какой-либо структуры данных в последовательность битов. Обратной к операции сериализации является операция десериализации — восстановление начального состояния структуры данных из битовой последовательности.

Сериализация используется для передачи объектов по сети и для сохранения их в файлы.

В Java единственное, что нужно для сериализации объекта — имплементировать интерфейс Serializable, который является интерфейсом-маркером, т.е не содержит методов.

Пишем Сервер

Итак, краткое описание работы сервера.

Но помимо этого, надо не забыть о том, что клиенты могут и отключаться. То есть мы периодически должны обмениваться с клиентами сигналами (ping'овать друг друга), чтобы в случае отключения клиента (или сервера) все об этом узнали.

Итак, приступим. Создадим наш первый класс

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

Ах да, мы же хотели получать номер порта из конфигурационного файла. Создадим для конфига отдельный класс, в котором будем хранить статические поля — параметры нашего чата.

Удобнее всего хранить параметры в properties-файле, благо Java предоставляет удобный интерфейс для работы с ними.

Итак, наш properties-файл будет состоять всего из одной строки (пока что)

PORT=1234

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

Осталось только заменить в Server.java
ServerSocket socketListener = new ServerSocket("1234"); на ServerSocket socketListener = new ServerSocket(Config.PORT); и добавить нужные import'ы

В дальнейшем, import'ы в коде буду упускать, поскольку любая IDE их сама подставит.

Поскольку он работает в отдельном потоке, первое, что мы должны сделать — написать

Далее, периодически, мы должны его ping'овать его каким-нибудь запросом, чтобы убедиться, что не ведем общение с трупом.

Ну и, как только мы перестали получать от него запросы — клиента следует удалить из списка доступнх пользователей.

На время забудем о ClientThread, а задумаемся «Каким образом будет происходить общение?»
Мы уже решили, что будем передавать сериализованный объект. Итак, чтоже должно быть в этом объекте?

Для успешной сериализации/десериализации, класс в клиенте и сервере должен быть одинаковым. Поэтому позаботимся сразу и о том, и о другом.

По сути, этот класс нам не сильно-то нужен, просто потом код будет удобнее читать

Итак, приступим к написанию ClientThread

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

Ах да, мы еще забыли вписать некоторые поля класса ClientThread, которые активно использовали. Итак, класс ClientThread,java целиком

Осталось разобраться с функциями getUserList() и getChatHistory
Для начала, определим еще 3 класса

По сути, сервер готов к работе, осталось только немного модифицировать 2 класса.

Методы getChatHistory() и getUserList() сделаны синхронизированными, потому что с ними могут работать несколько потоков

И, доделаем наш конфиг, так как у нас добавились некоторые параметры

PORT=5000
HISTORY_LENGTH=50
HELLO_MESSAGE=User join to the chat(Auto-message)

Заключение

Теперь наш сервер готов к использованию. Мы познакомились с сериализацией объектов и работой с сокетами в Java.

В следующей статье (завтра-послезавтра) мы напишем клиент для нашего чата, а пока жду комментариев, особенно относительно устройства чата, в частности — «Здесь абсолютно неправильно реаизовано XXX» (разумеется, жду только аргументированных оценок)

В любом случае, данная статья не призвана стать эталоном написания чата на Java, это лишь инструмент для понимания как вообще создавать такие приложения на хорошем примере, а не на эхо-чате в 10 строк.

Рекомендованный контент

Все ясно, но проблема NetBeans ругается :( Можно мне архив с сорсами?)

Ты нашел решение проблемы?

Поддержу вышеотписавшегося, можете ли дать ссылку на проет?

то Игорь: Подключайте таймер из библиотеки awt.
Вопрос к автору: где клиент.

Ребят, автор выложил клиент или нет еще?

у кого-нидь была проблема с timer.stop()?
как ее решить?

getUserList() не видит этот метод, предлагает его объявить.Что делать?

Где продолжение? Ссылку, пожалуйста.

Не знаю кому там всё ясно, но код вообще трудно читабельный. Пояснений во второй части вообще нету. Некоторые методы вообще вызываются не пойми как , так как находятся вне зоны видимости.

Хотя если не пытаться вдаваться в код, а просто понять общий принцип работы, то более менее понятно

Ну и где продолжение

хорошая статья, а вот продолжение где?

Алекс надо смотреть тут import java.awt.event.*;

К слову я что-то не догоняю о почему не видно вызовы getUserList и getChatHistory ? в какой класс зырить ?

Ребята кто-нибудь написал клиент к этому серверу? Поделитесь, у меня постоянно ошибки вылетают.

Где можно найти клиент?

ThreadClient не очень написан. Запускать поток в конструкторе как-то не очень.

Что бы не было ошибки связанной с с timer.stop() надо подключить import javax.swing.Timer;

Проблема с классом СlientTread.
Искал ошибки, но не нашел

ServerSocket socketListener = new ServerSocket(Config.PORT);
Не пойму почему ругается, в классе Server проблема, почему то не видит в классе config, не могли бы вы помочь разобраться?

В классе ClientThread не видит методы getChatHistory и getUserList. Как решить проблему?

Эти методы находятся в классе Server, и являются статическими. Вызываются так: Server.getChatHistory(), Server.getUserList().

Может кому пригодится клиент :)
Написал его за пару часов, и опыта в работе с потоками нет, поэтому не придирайтесь.

Всего в проекте 4 файла:

// Создаем поток для чтения с клавиатуры
BufferedReader keyboard = new BufferedReader( new InputStreamReader( System.in ) );
try // Ждем пока пользователь введет свой ник и нажмет кнопку Enter
userName = keyboard.readLine();
System.out.println();
> catch ( IOException e )

try try InetAddress ipAddress = InetAddress.getByName( address ); // создаем объект который отображает вышеописанный IP-адрес
socket = new Socket( ipAddress, serverPort ); // создаем сокет используя IP-адрес и порт сервера

// Берем входной и выходной потоки сокета, теперь можем получать и отсылать данные клиентом
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();

new PingThread( objectOutputStream, objectInputStream );

while (true) < // Бесконечный цикл
message = keyboard.readLine(); // ждем пока пользователь введет что-то и нажмет кнопку Enter.
objectOutputStream.writeObject( new Message( userName, message ) ); // отсылаем введенную строку текста серверу.
>
> catch ( Exception e ) < e.printStackTrace(); >
>
finally try if ( socket != null ) < socket.close(); >
> catch ( IOException e ) < e.printStackTrace(); >
>
>
>

public class ServerListenerThread implements Runnable private Thread thread = null;
private ObjectOutputStream objectOutputStream = null;
private ObjectInputStream objectInputStream = null;

public ServerListenerThread( ObjectOutputStream objectOutputStream, ObjectInputStream objectInputStream ) this.objectOutputStream = objectOutputStream;
this.objectInputStream = objectInputStream;

thread = new Thread( this );
thread.start();
>

3, 4) Это файла протокола обмена с сервером: Message.java и Ping.java

Собственно вот и весь клиент. А дальше уже сами наращивайте функционал и придумывайте GUIню какую хотите)

Читайте также: