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

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

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

На данный момент заканчиваю 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'овать друг друга), чтобы в случае отключения клиента (или сервера) все об этом узнали.

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

Server.java

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

Config.java

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. Список доступных серверу клиентов (для отображения у пользователя)

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

Message.java

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'ов.

Ping.java

public class Ping extends Message {
	public Ping() {
		super("ping", "ping");
	}
}

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

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

ClientThread.run()

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();
      }
}

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

Данная функция рассылает какое-то сообщение всем клиентам

ClientThread.broadcast()

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 целиком

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 класса

Client.java

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;
    }
}

UserList.java

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;
    }

}

ChatHistory.java

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 класса.

Server.java

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() сделаны синхронизированными, потому что с ними могут работать несколько потоков

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

server.properties

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

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


  1. Кристел:

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

  2. Фишман:

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

  3. Игорь:

    у меня осталась одна ошибка вот в этом месте this.timer = new Timer(DELAY, new ActionListener() { , как её исправить?

  4. Ed:

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

  5. Ursus:

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

  6. Vikking:

    У socketListener в конструкторе указан порт в виде String.Надо бы исправить.
    ServerSocket socketListener = new ServerSocket(“1234”);

  7. Andrey:

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

  8. Illia:

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

  9. Алексей:

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

  10. Валерий:

    Перелыл весь сайт так и не нашел продолжения этой хорошей статьи! Может кто нибудь подскажет??? дайте ссылку плизззз…..

  11. Друк:

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

  12. Друк:

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

  13. Андрей:

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

  14. Евгений:

    Где клиент?

  15. никита:

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

  16. Алекс:

    В пакете java.awt я не нашел ни одного таймера…

    • Игорь:

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

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

  17. Алекс:

    Мне одному кажеться или код
    “if (! (c instanceof Ping) && ! c.getMessage().equals(Config.HELLO_MESSAGE)) {
    System.out.println(“Send broadcast Message: “” + c.getMessage() + “””);
    this.broadcast(getUserList().getClientsList(), this.c); ”
    не имеет смысла…?

  18. Джордж:

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

  19. Даниил:

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

    кто может помочь, киньте инструкцию полную на почту tarasovdaniil22@gmail.com

  20. angeldust:

    Вопрос автору: а как поведёт себя сервер, если будет 10 000 000+ клиентов онлайн? сервак от количества потоков не упадёт? и сколько надо оперативной памяти под это? и ещё один вопрос не лучше ли сообщения передавать в хотя бы xml формате, на серваке брать парсером его статус(ping | text | login |…) и дальше зависимости от статуса его обрабатывать или всё таки лучше сериализованный объект передавать и на сервере не парсить а десериализовывать?

  21. Dmitry:

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

  22. Max:

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

  23. Вова:

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

  24. Владимир:

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

  25. Владимир:

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

  26. Rustem:

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

    • _Кибер_Кукумбер_:

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

  27. йцв:

    порт в кавычках??? мдаааааа….бб ламер

  28. йцв:

    в общем иди нахер…твой код безнадежно устарел…в топку его и тебя

  29. _Кибер_Кукумбер_:

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

    Всего в проекте 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ню какую хотите)

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js