Краткое описание
На данный момент заканчиваю 2-й курс универститета, одной из лабораторных работ по курсу Java было написание чата. После того, как разобрался в теме сокетов, сериализации объектов и MVC, хотелось бы поделиться с читателим, тем более, что оно мне несказанно помогло при написании проекта.
Ну и, разумеется, учту все ошибки и недочеты, которые будут озвучены.
Немного теории
Итак, для начала. Проект будет состоять из двух частей: клиента и сервера. Клиент будет иметь GUI, написанный с помощью библиотеки Swing. Сервер GUI иметь не будет, только log-файл и небольшой вывод в консоль.
Для написания чата, нам понадобятся некоторые знания.
Сокеты
Поскольку взаимосвязь клиента и сервера у нас будет реализована с помощью сокетов, познакомимся с ними поближе.
Со́кеты (англ. socket — углубление, гнездо, разъём) — название программного интерфейса для обеспечения обмена данными между процессами. Сокет — абстрактный объект, представляющий конечную точку соединения.
Каждый процесс может создать слушающий сокет (серверный сокет) и привязать его к какому-нибудь порту операционной системы. Слушающий процесс обычно находится в цикле ожидания, то есть просыпается при появлении нового соединения.
Для нас это означает, что сервер будет слушать какой-либо порт (для большей универсальности, мы будем задавать порт в конфигурационном файле) и после подключения клиента, будем производить с ним какие-то действия.
ServerSocket socketListener = new ServerSocket("1234"); //Слушаем порт 1234
while (true) {
Socket client = null;
while (client == null) {
client = socketListener.accept(); //Пытаемся соединиться с клиентом
}
//Как только подключились, можем как-то с ним взаимодействовать
}
Передача объектов по сети
Но просто получать сообщение от клиента нам мало. Нам хочется знать его имя, IP-адрес, а также передавать ему в ответ список подключенных пользователей. Таким образом, просто передача текстовых данных нас не устроит. У нас есть 2 выхода:
1. Передавать xml-строку с описанием всей информации
2. Передавать сериализованный объект, в котором будут храниться все необходимые нам данные в виде полей.
Не будем говорить о плюсах и минусах каждого подхода. Воспользуемся вторым вариантом. (А если понадобится, когда-нибудь потом, я напишу второй)
Итак, что такое сериализация объектов.
Сериализация — процесс перевода какой-либо структуры данных в последовательность битов. Обратной к операции сериализации является операция десериализации — восстановление начального состояния структуры данных из битовой последовательности.
Сериализация используется для передачи объектов по сети и для сохранения их в файлы.
В Java единственное, что нужно для сериализации объекта — имплементировать интерфейс Serializable, который является интерфейсом-маркером, т.е не содержит методов.
Пишем Сервер
Итак, краткое описание работы сервера.
Сервер работает в вечном цикле. Как только подключается новый клиент, он создает для работы с ним новый поток, оповещает уже подключенных клиентов о новом пользователей, а новичку отсылает какое-то количество последних сообщений в чате. Клиент же, при подключении сообщает о себе некоторую информациию, а также какое-то сообщение, идентифицирующее то, что он только что подключился.
Но помимо этого, надо не забыть о том, что клиенты могут и отключаться. То есть мы периодически должны обмениваться с клиентами сигналами (ping'овать друг друга), чтобы в случае отключения клиента (или сервера) все об этом узнали.
Итак, приступим. Создадим наш первый класс
package anexroid.server;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
public class Server {
public static void main(String[] args) {
try {
//Создаем слушатель
ServerSocket socketListener = new ServerSocket("1234");
while (true) {
Socket client = null;
while (client == null) {
client = socketListener.accept();
}
new ClientThread(client); //Создаем новый поток, которому передаем сокет
}
} catch (SocketException e) {
System.err.println("Socket exception");
e.printStackTrace();
} catch (IOException e) {
System.err.println("I/O exception");
e.printStackTrace();
}
}
}
Думаю, данный код, пока что, не нуждается в комментариях. Я не стал особо заморачиваться на обработке исключений, но нам ведь не это важно, верно?
Ах да, мы же хотели получать номер порта из конфигурационного файла. Создадим для конфига отдельный класс, в котором будем хранить статические поля — параметры нашего чата.
Удобнее всего хранить параметры в properties-файле, благо Java предоставляет удобный интерфейс для работы с ними.
Итак, наш properties-файл будет состоять всего из одной строки (пока что)
PORT=1234
package anexroid.server
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;
public class Config {
private static final String PROPERTIES_FILE = "./server.properties";
public static int PORT;
static {
Properties properties = new Properties();
FileInputStream propertiesFile = null;
try {
propertiesFile = new FileInputStream(PROPERTIES_FILE);
properties.load(propertiesFile);
PORT = Integer.parseInt(properties.getProperty("PORT"));
} catch (FileNotFoundException ex) {
System.err.println("Properties config file not found");
} catch (IOException ex) {
System.err.println("Error while reading file");
} finally {
try {
propertiesFile.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
Вся загрузка параметров происходит в блоке статической инициализации, поскольку полноценный конструктор в нашем случае — непозволительная роскошь.
Осталось только заменить в Server.java
ServerSocket socketListener = new ServerSocket("1234");
на ServerSocket socketListener = new ServerSocket(Config.PORT);
и добавить нужные import'ы
В дальнейшем, import'ы в коде буду упускать, поскольку любая IDE их сама подставит.
Итак, мы написали new ClientThread();
. Но что это такое, пока не решили. Пора исправить это.
Этот класс у нас будет отвечать за прием и передачу сообщений между клиентом и сервером, а значит, самый главный класс в чате — именно ClientThread.
Поскольку он работает в отдельном потоке, первое, что мы должны сделать — написать
public class ClientThread extends Thread {
private Socket socket;
public ClientThread(Socket socket) {
this.socket = socket;
this.start();
}
public void run() {
}
А теперь подумаем, что же написать в методе run().
Итак, по порядку. Для начала, мы должны получить от клиента информацию «Ты кто такой?» в противном случае — «Давай, до свидания!». Как только мы узнали кто он такой, мы должны отправить ему последние сообщения в нашем чате.
Далее, периодически, мы должны его ping'овать его каким-нибудь запросом, чтобы убедиться, что не ведем общение с трупом.
Затем, мы должны получать от него сообщения, о которых должны уведомлять всех подключенных клиентов.
Ну и, как только мы перестали получать от него запросы — клиента следует удалить из списка доступнх пользователей.
На время забудем о ClientThread, а задумаемся «Каким образом будет происходить общение?»
Мы уже решили, что будем передавать сериализованный объект. Итак, чтоже должно быть в этом объекте?
Я остановился на следующем варианте:
1. Логин пользователя, отправившего это сообщение (или Server-Bot)
2. Собственно, само сообщение
3. Время отправки
4. Список доступных серверу клиентов (для отображения у пользователя)
Для успешной сериализации/десериализации, класс в клиенте и сервере должен быть одинаковым. Поэтому позаботимся сразу и о том, и о другом.
public class Message implements Serializable {
private String login;
private String message;
private String[] users;
private Date time;
//Конструктор, которым будет пользоваться клиент
public Message(String login, String message){
this.login = login;
this.message = message;
this.time = java.util.Calendar.getInstance().getTime();
}
//Конструктор, которым будет пользоваться сервер
public Message(String login, String message, String[] users){
this.login = login;
this.message = message;
this.time = java.util.Calendar.getInstance().getTime();
this.users = users;
}
public void setOnlineUsers(String[] users) {
this.users = users;
}
public String getLogin() {
return this.login;
}
public String getMessage() {
return this.message;
}
public String[] getUsers() {
return this.users;
}
public String getDate(){
Time tm = new Time(this.time.getTime());
return tm.toString();
}
}
Думаю, всё очевидно. Также, помимо сообщений мы хотим передавать нечто вроде ping'ов.
public class Ping extends Message {
public Ping() {
super("ping", "ping");
}
}
По сути, этот класс нам не сильно-то нужен, просто потом код будет удобнее читать
Итак, приступим к написанию ClientThread
public void run() {
try {
//Создаем потоки ввода-вывода для работы с сокетом
final ObjectInputStream inputStream = new ObjectInputStream(this.socket.getInputStream());
final ObjectOutputStream outputStream = new ObjectOutputStream(this.socket.getOutputStream());
//Читаем Message из потока
this.c = (Message) inputStream.readObject();
//Читаем логин отправителя
this.login = this.c.getLogin();
//Что же нам прислали?
if (! this.c.getMessage().equals(Config.HELLO_MESSAGE)) { //Если это не регистрационное сообщение
System.out.println("[" + this.c.getLogin() + "]: " + this.c.getMessage());
getChatHistory().addMessage(this.c); //То добавляем его к истории чата
} else {
outputStream.writeObject(getChatHistory()); //Иначе, отправляем новичку историю чата
this.broadcast(getUserList().getClientsList(), new Message("Server-Bot", "The user " + login + " has been connect")); //И сообщаем всем клиентам, что подключился новый пользователь
}
//Добавляем к списку пользователей - нового
getUserList().addUser(login, socket, outputStream, inputStream);
//Для ответа, указываем список доступных пользователей
this.c.setOnlineUsers(getUserList().getUsers());
//Передаем всем сообщение пользователя
this.broadcast(getUserList().getClientsList(), this.c);
//Запускаем таймер
this.timer = new Timer(DELAY, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try { //Если количество входящих пакетов от клиента рано исходящему, значит клиент еще не в ауте
if (inPacks == outPacks) {
outputStream.writeObject(new Ping());
outPacks++;
System.out.println(outPacks + " out");
} else { //Иначе, в ауте
throw new SocketException();
}
} catch (SocketException ex1) {
System.out.println("packages not clash");
System.out.println(login + " disconnected!");
//Удаляем клиента из списка доступных и информируем всех
getUserList().deleteUser(login);
broadcast(getUserList().getClientsList(), new Message("Server-Bot", "The user " + login + " has been disconnect", getUserList().getUsers()));
flag = true;
timer.stop();
} catch (IOException ex2) {
ex2.printStackTrace();
}
}
});
this.timer.start();
//Начинаем пинговать клиента
outputStream.writeObject(new Ping());
this.outPacks++;
System.out.println(outPacks + " out");
//А теперь нам остается только ждать от него сообщений
while (true) {
//Как только пинг пропал - заканчиваем
if(this.flag) {
this.flag = false;
break;
}
//Принимаем сообщение
this.c = (Message) inputStream.readObject();
//Если это ping
if (this.c instanceof Ping) {
this.inPacks++;
System.out.println(this.inPacks + " in");
} else if (! c.getMessage().equals(Config.HELLO_MESSAGE)) {
System.out.println("[" + login + "]: " + c.getMessage());
getChatHistory().addMessage(this.c);
} else {
outputStream.writeObject(getChatHistory());
this.broadcast(getUserList().getClientsList(), new Message("Server-Bot", "The user " + login + " has been connect"));
}
this.c.setOnlineUsers(getUserList().getUsers());
if (! (c instanceof Ping) && ! c.getMessage().equals(Config.HELLO_MESSAGE)) {
System.out.println("Send broadcast Message: "" + c.getMessage() + """);
this.broadcast(getUserList().getClientsList(), this.c);
}
}
} catch (SocketException e) {
System.out.println(login + " disconnected!");
this.broadcast(getUserList().getClientsList(), new Message("Server-Bot", "The user " + login + " has been disconnect", getUserList().getUsers()));
this.timer.stop();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
Код немного комментирован, поэтому все должны разобраться. Осталось дописать только несколько функций.
Итак, начнем по порядку.
Данная функция рассылает какое-то сообщение всем клиентам
private void broadcast(ArrayList<Client> clientsArrayList, Message message) {
try {
for (Client client : clientsArrayList) {
client.getThisObjectOutputStream().writeObject(message);
}
} catch (SocketException e) {
System.out.println("in broadcast: " + login + " disconnected!");
getUserList().deleteUser(login);
this.broadcast(getUserList().getClientsList(), new Message("System", "The user " + login + " has been disconnected", getUserList().getUsers()));
timer.stop();
} catch (IOException e) {
e.printStackTrace();
}
}
Ах да, мы еще забыли вписать некоторые поля класса ClientThread, которые активно использовали. Итак, класс ClientThread,java целиком
public class ClientThread extends Thread {
private final static int DELAY = 30000;
private Socket socket;
private Message c;
private String login;
private int inPacks = 0;
private int outPacks = 0;
private boolean flag = false;
private Timer timer;
public ClientThread(Socket socket) {
this.socket = socket;
this.start();
}
public void run() {
try {
final ObjectInputStream inputStream = new ObjectInputStream(this.socket.getInputStream());
final ObjectOutputStream outputStream = new ObjectOutputStream(this.socket.getOutputStream());
this.c = (Message) inputStream.readObject();
this.login = this.c.getLogin();
if (! this.c.getMessage().equals(Config.HELLO_MESSAGE)) {
System.out.println("[" + this.c.getLogin() + "]: " + this.c.getMessage());
getChatHistory().addMessage(this.c);
} else {
outputStream.writeObject(getChatHistory());
this.broadcast(getUserList().getClientsList(), new Message("Server-Bot", "The user " + login + " has been connect"));
}
getUserList().addUser(login, socket, outputStream, inputStream);
this.c.setOnlineUsers(getUserList().getUsers());
this.broadcast(getUserList().getClientsList(), this.c);
this.timer = new Timer(DELAY, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
if (inPacks == outPacks) {
outputStream.writeObject(new Ping());
outPacks++;
System.out.println(outPacks + " out");
} else {
throw new SocketException();
}
} catch (SocketException ex1) {
System.out.println("packages not clash");
System.out.println(login + " disconnected!");
getUserList().deleteUser(login);
broadcast(getUserList().getClientsList(), new Message("Server-Bot", "The user " + login + " has been disconnect", getUserList().getUsers()));
flag = true;
timer.stop();
} catch (IOException ex2) {
ex2.printStackTrace();
}
}
});
this.timer.start();
outputStream.writeObject(new Ping());
this.outPacks++;
System.out.println(outPacks + " out");
while (true) {
if(this.flag) {
this.flag = false;
break;
}
this.c = (Message) inputStream.readObject();
if (this.c instanceof Ping) {
this.inPacks++;
System.out.println(this.inPacks + " in");
} else if (! c.getMessage().equals(Config.HELLO_MESSAGE)) {
System.out.println("[" + login + "]: " + c.getMessage());
getChatHistory().addMessage(this.c);
} else {
outputStream.writeObject(getChatHistory());
this.broadcast(getUserList().getClientsList(), new Message("Server-Bot", "The user " + login + " has been connect"));
}
this.c.setOnlineUsers(getUserList().getUsers());
if (! (c instanceof Ping) && ! c.getMessage().equals(Config.HELLO_MESSAGE)) {
System.out.println("Send broadcast Message: "" + c.getMessage() + """);
this.broadcast(getUserList().getClientsList(), this.c);
}
}
} catch (SocketException e) {
System.out.println(login + " disconnected!");
getUserList().deleteUser(login);
broadcast(getUserList().getClientsList(), new Message("Server-Bot", "The user " + login + " has been disconnect", getUserList().getUsers()));
this.timer.stop();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private void broadcast(ArrayList<Client> clientsArrayList, Message message) {
try {
for (Client client : clientsArrayList) {
client.getThisObjectOutputStream().writeObject(message);
}
} catch (SocketException e) {
System.out.println("in broadcast: " + login + " disconnected!");
getUserList().deleteUser(login);
this.broadcast(getUserList().getClientsList(), new Message("Server-Bot", "The user " + login + " has been disconnected", getUserList().getUsers()));
timer.stop();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Осталось разобраться с функциями getUserList()
и getChatHistory
Для начала, определим еще 3 класса
public class Client {
private Socket socket;
private ObjectOutputStream oos;
private ObjectInputStream ois;
public Client(Socket socket){
this.socket = socket;
}
public Client(Socket socket , ObjectOutputStream oos , ObjectInputStream ois ){
this.socket = socket;
this.oos = oos;
this.ois = ois;
}
public Socket getSocket() {
return this.socket;
}
public ObjectOutputStream getThisObjectOutputStream() {
return this.oos;
}
public ObjectInputStream getThisObjectInputStream() {
return this.ois;
}
public void setThisObjectOutputStream(ObjectOutputStream oos) {
this.oos = oos;
}
public void setThisObjectInputStream(ObjectInputStream ois) {
this.ois = ois;
}
}
public class UsersList {
private Map<String, Client> onlineUsers = new HashMap<String, Client>();
public void addUser(String login, Socket socket, ObjectOutputStream oos, ObjectInputStream ois) {
System.out.println( login +" connected" );
if (!this.onlineUsers.containsKey(login)) {
this.onlineUsers.put(login , new Client(socket, oos, ois));
} else {
int i = 1;
while(this.onlineUsers.containsKey(login)) {
login = login + i;
i++;
}
this.onlineUsers.put(login , new Client(socket, oos, ois));
}
}
public void deleteUser(String login) {
this.onlineUsers.remove(login);
}
public String[] getUsers() {
return this.onlineUsers.keySet().toArray(new String[0]);
}
public ArrayList<Client> getClientsList() {
ArrayList<Client> clientsList = new ArrayList<Client>(this.onlineUsers.entrySet().size());
String s = "";
for(Map.Entry<String, Client> m : this.onlineUsers.entrySet()){
clientsList.add(m.getValue());
System.out.println(m.getKey());
s = s + m.getKey();
}
return clientsList;
}
}
public class ChatHistory implements Serializable {
private List<Message> history;
public ChatHistory() {
this.history = new ArrayList<Message>(Config.HISTORY_LENGTH);
}
public void addMessage(Message message){
if (this.history.size() > Config.HISTORY_LENGTH){
this.history.remove(0);
}
this.history.add(message);
}
public List<Message> getHistory(){
return this.history;
}
}
По сути, сервер готов к работе, осталось только немного модифицировать 2 класса.
public class Server {
private static UsersList list = new UsersList();
private static ChatHistory chatHistory = new ChatHistory();
public static void main(String[] args) {
try {
//Создаем слушатель
ServerSocket socketListener = new ServerSocket("1234");
while (true) {
Socket client = null;
while (client == null) {
client = socketListener.accept();
}
new ClientThread(client); //Создаем новый поток, которому передаем сокет
}
} catch (SocketException e) {
System.err.println("Socket exception");
e.printStackTrace();
} catch (IOException e) {
System.err.println("I/O exception");
e.printStackTrace();
}
}
public synchronized static UsersList getUserList() {
return list;
}
public synchronized static ChatHistory getChatHistory() {
return chatHistory;
}
}
Методы getChatHistory()
и getUserList()
сделаны синхронизированными, потому что с ними могут работать несколько потоков
И, доделаем наш конфиг, так как у нас добавились некоторые параметры
HISTORY_LENGTH=50
HELLO_MESSAGE=User join to the chat(Auto-message)
public class Config {
private static final String PROPERTIES_FILE = "./server.properties";
public static int PORT;
public static int HISTORY_LENGTH;
public static String HELLO_MESSAGE;
static {
Properties properties = new Properties();
FileInputStream propertiesFile = null;
try {
propertiesFile = new FileInputStream(PROPERTIES_FILE);
properties.load(propertiesFile);
PORT = Integer.parseInt(properties.getProperty("PORT"));
HISTORY_LENGTH = Integer.parseInt(properties.getProperty("HISTORY_LENGTH"));
HELLO_MESSAGE = properties.getProperty("HELLO_MESSAGE");
} catch (FileNotFoundException ex) {
System.err.println("Properties config file not found");
} catch (IOException ex) {
System.err.println("Error while reading file");
} finally {
try {
propertiesFile.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
Заключение
Теперь наш сервер готов к использованию. Мы познакомились с сериализацией объектов и работой с сокетами в Java.
В следующей статье (завтра-послезавтра) мы напишем клиент для нашего чата, а пока жду комментариев, особенно относительно устройства чата, в частности — «Здесь абсолютно неправильно реаизовано XXX» (разумеется, жду только аргументированных оценок)
В любом случае, данная статья не призвана стать эталоном написания чата на Java, это лишь инструмент для понимания как вообще создавать такие приложения на хорошем примере, а не на эхо-чате в 10 строк.
Автор: Anexroid
Все ясно, но проблема NetBeans ругается :( Можно мне архив с сорсами?)
Ты нашел решение проблемы?
Поддержу вышеотписавшегося, можете ли дать ссылку на проет?
у меня осталась одна ошибка вот в этом месте this.timer = new Timer(DELAY, new ActionListener() { , как её исправить?
то Игорь: Подключайте таймер из библиотеки awt.
Вопрос к автору: где клиент!!!
Ребят, автор выложил клиент или нет еще?
У socketListener в конструкторе указан порт в виде String.Надо бы исправить.
ServerSocket socketListener = new ServerSocket(“1234”);
у кого-нидь была проблема с timer.stop()?
как ее решить?
getUserList() не видит этот метод, предлагает его объявить.Что делать?
Где продолжение? Ссылку, пожалуйста.
Перелыл весь сайт так и не нашел продолжения этой хорошей статьи! Может кто нибудь подскажет??? дайте ссылку плизззз…..
Не знаю кому там всё ясно, но код вообще трудно читабельный. Пояснений во второй части вообще нету. Некоторые методы вообще вызываются не пойми как , так как находятся вне зоны видимости.
Хотя если не пытаться вдаваться в код, а просто понять общий принцип работы, то более менее понятно
Ну и где продолжение
Где клиент?
хорошая статья, а вот продолжение где?
В пакете java.awt я не нашел ни одного таймера…
Алекс надо смотреть тут import java.awt.event.*;
К слову я что-то не догоняю о почему не видно вызовы getUserList и getChatHistory ? в какой класс зырить ?
В Server, там они определены. Просто из ClientThread они не видны…
Мне одному кажеться или код
“if (! (c instanceof Ping) && ! c.getMessage().equals(Config.HELLO_MESSAGE)) {
System.out.println(“Send broadcast Message: “” + c.getMessage() + “””);
this.broadcast(getUserList().getClientsList(), this.c); ”
не имеет смысла…?
Ребята кто-нибудь написал клиент к этому серверу? Поделитесь, у меня постоянно ошибки вылетают.
Ребят, вот я честно дуб-дубом в этом всем….. но мне нужна помощ как раз тех кто шарит. Хотим с кентами просто сделать свой чатик что бы там трещать на досуге, но вот проблема никто в этом не шарит, денег платить как то не охота,
кто может помочь, киньте инструкцию полную на почту tarasovdaniil22@gmail.com
Вопрос автору: а как поведёт себя сервер, если будет 10 000 000+ клиентов онлайн? сервак от количества потоков не упадёт? и сколько надо оперативной памяти под это? и ещё один вопрос не лучше ли сообщения передавать в хотя бы xml формате, на серваке брать парсером его статус(ping | text | login |…) и дальше зависимости от статуса его обрабатывать или всё таки лучше сериализованный объект передавать и на сервере не парсить а десериализовывать?
Где можно найти клиент?
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 файла:
1) SocketClient.java
public class SocketClient {
private final static String address = “127.0.0.1”; // это IP-адрес компьютера, где исполняется наша серверная программа
private final static int serverPort = 6666; // здесь обязательно нужно указать порт к которому привязывается сервер
private static String userName = “”;
static Socket socket = null;
public static void main( String[] args ) {
System.out.println(“Вас приветствует клиент чата!\n”);
System.out.println(“Введите свой ник и нажмите \”Enter\””);
// Создаем поток для чтения с клавиатуры
BufferedReader keyboard = new BufferedReader( new InputStreamReader( System.in ) );
try {
// Ждем пока пользователь введет свой ник и нажмет кнопку Enter
userName = keyboard.readLine();
System.out.println();
} catch ( IOException e ) { e.printStackTrace(); }
try {
try {
InetAddress ipAddress = InetAddress.getByName( address ); // создаем объект который отображает вышеописанный IP-адрес
socket = new Socket( ipAddress, serverPort ); // создаем сокет используя IP-адрес и порт сервера
// Берем входной и выходной потоки сокета, теперь можем получать и отсылать данные клиентом
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
// Конвертируем потоки в другой тип, чтоб легче обрабатывать текстовые сообщения
ObjectOutputStream objectOutputStream = new ObjectOutputStream( outputStream );
ObjectInputStream objectInputStream = new ObjectInputStream( inputStream );
new PingThread( objectOutputStream, objectInputStream );
// Создаем поток для чтения с клавиатуры
String message = null;
System.out.println(“Наберите сообщение и нажмите \”Enter\”\n”);
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(); }
}
}
}
2) ServerListenerThread.java
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();
}
@Override
public void run() {
try {
while (true) {
Message messageIn = (Message) objectInputStream.readObject();
if ( messageIn instanceof Ping ) {
Ping ping = (Ping) messageIn;
objectOutputStream.writeObject( new Ping() );
} else {
System.out.println(“[ ” + messageIn.getDate().toString() + ” ] ” + messageIn.getLogin() + ” : ” + messageIn.getMessage() );
}
}
}
catch ( SocketException e ) { e.getMessage(); }
catch ( ClassNotFoundException e ) { e.getMessage(); }
catch ( IOException e ) { e.getMessage(); }
}
}
3, 4) Это файла протокола обмена с сервером: Message.java и Ping.java
Собственно вот и весь клиент. А дальше уже сами наращивайте функционал и придумывайте GUIню какую хотите)