TopMetaFizick
@TopMetaFizick
Все двоит и троит...

Как правильно реализовать клиент-серверную структуру в данном случае?

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

Начнем с третьего модуля, а именно сетевого:

Интерфейс TCPConnectionListener
public interface TCPConnectionListener {

            void onConnectionReady(TCPConnection tcpConnection);
            void onReceiveString(TCPConnection tcpConnection, String value);
            void onDisconnect(TCPConnection tcpConnection);
            void onExeption(TCPConnection tcpConnection, Exception e);


}

Класс TCPConnection
import java.io.*;
import java.net.Socket;
import java.nio.charset.Charset;

public class TCPConnection {

    private final Socket socket;
    private final Thread rxThread;
    private final TCPConnectionListener eventListener;
    private final BufferedReader in;
    private final BufferedWriter out;

        public TCPConnection(TCPConnectionListener eventListener, String ip, int port) throws IOException {
            this(eventListener, new Socket(ip,port));
        }
        public TCPConnection(TCPConnectionListener eventListener, Socket socket)throws IOException {


            this.socket =socket;
            this.eventListener=eventListener;

            in =new BufferedReader(new InputStreamReader(socket.getInputStream(), Charset.forName("UTF-8")));

            out =new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), Charset.forName("UTF-8")));

            rxThread=new Thread(new Runnable() {
                @Override
                public void run() {

                    try {

                        eventListener.onConnectionReady(TCPConnection.this);
                        while (!rxThread.isInterrupted()){
                            eventListener.onReceiveString(TCPConnection.this, in.readLine());
                        }

                    } catch (IOException e) {

                                eventListener.onExeption(TCPConnection.this,e);

                    }finally {
                                eventListener.onDisconnect(TCPConnection.this);

                    }


                }
            });
            rxThread.start();

        }

        public synchronized  void sendMessage(String value){
            try {
                out.write(value+"\r\n");
                out.flush();
            } catch (IOException e) {
                eventListener.onExeption(TCPConnection.this, e);
                setDissconnect();
            }
        }

        public synchronized  void setDissconnect(){
            rxThread.interrupt();
            try {
                socket.close();
            } catch (IOException e) {
                eventListener.onExeption(TCPConnection.this, e);
            }

        }

    @Override
    public String toString() {
        return "TCPConnection: "+socket.getInetAddress()+" port: "+socket.getPort();
    }
}

Далее серверный модуль:

Класс ChatServer
import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;

public class ChatServer implements TCPConnectionListener{

    public static void main(String[]args){
        new ChatServer();
    }
    private  final ArrayList<TCPConnection> connections =new ArrayList<>();
    private ChatServer(){
        System.out.println("Server running...");
        try(ServerSocket serverSocket=new ServerSocket(8189)){
            while (true){
                try {
                    new TCPConnection(this, serverSocket.accept());

                }catch (IOException e){

                    System.out.println("TCPConnection exeption...");
                }}
        }catch (IOException e){
            throw new RuntimeException(e);

        }
    }

    @Override
    public synchronized void onConnectionReady(TCPConnection tcpConnection) {
        connections.add(tcpConnection);
        sendToAllConnections("Client connect: "+tcpConnection);
    }

    @Override
    public synchronized  void onReceiveString(TCPConnection tcpConnection, String value) {
        System.out.println("ttt");
    sendToAllConnections(value);
    }

    @Override
    public synchronized void onDisconnect(TCPConnection tcpConnection) {
        connections.remove(tcpConnection);
        sendToAllConnections("Client disconnect: "+tcpConnection);
    }

    @Override
    public synchronized void onExeption(TCPConnection tcpConnection, Exception e) {
        System.out.println("TCPConnections exeption...");
    }

    private void sendToAllConnections(String message){

        System.out.println(message);
        final int lh=connections.size();
        for (int i=0;i<lh;i++){
            connections.get(i).sendMessage(message);
        }
    }
}

И последний модуль - клиентский. Он состоит из главного класса, контроллера, и fxml файла:

Главный класс
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class ClientWindow extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        FXMLLoader loader =new FXMLLoader(getClass().getResource("sample.fxml"));
        Parent root = loader.load();

        primaryStage.setTitle("Hello World");
        primaryStage.setResizable(false);
        primaryStage.setScene(new Scene(root, 300, 500));
        primaryStage.show();

    }


    public static void main(String[] args) {
        launch(args);
    }
}

Класс контроллера
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;

import java.io.IOException;


public class Controller implements TCPConnectionListener{

    @FXML
    public TextArea dialogWn;

    @FXML
    public TextField nickname;

    TCPConnection connection;
    public void onToSendClick(MouseEvent mouseEvent) {
            connection.sendMessage(nickname.getText());
    }

    public void onExitClicked(MouseEvent mouseEvent) {
        try {
            connection =new TCPConnection(this, "127.0.0.1", 8189);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void printMessage(String msg){
        dialogWn.appendText(msg);

    }


    @Override
    public void onConnectionReady(TCPConnection tcpConnection) {

    }

    @Override
    public void onReceiveString(TCPConnection tcpConnection, String value) {
        printMessage(value+"\n\r");
    }

    @Override
    public void onDisconnect(TCPConnection tcpConnection) {

    }

    @Override
    public void onExeption(TCPConnection tcpConnection, Exception e) {

    }
}

Шаблон
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>

<AnchorPane prefHeight="500.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="Controller">
   <children>
      <Label prefHeight="40.0" prefWidth="80.0" text="Ваш ник:" textFill="#3c39a3" underline="true" AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="0.0">
         <font>
            <Font name="System Bold" size="17.0" />
         </font>
      </Label>
      <TextField fx:id="nickname" prefHeight="25.0" prefWidth="174.0" AnchorPane.rightAnchor="20.0" AnchorPane.topAnchor="8.0" />
      <ScrollPane prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="50.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="40.0">
         <content>
            <TextArea fx:id="dialogWn" editable="false" prefHeight="408.0" prefWidth="298.0" />
         </content>
      </ScrollPane>
      <Button fx:id="send" mnemonicParsing="false" onMouseClicked="#onToSendClick" prefHeight="25.0" prefWidth="100.0" text="Отправить" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="40.0" />

      <Button fx:id="exit" mnemonicParsing="false" onMouseClicked="#onExitClicked" prefHeight="25.0" prefWidth="100.0" text="Выход" AnchorPane.bottomAnchor="10.0" AnchorPane.rightAnchor="40.0" />
   </children>
</AnchorPane>


Все вроде работает, но проблема в том , что я в контроллере создаю соединение (я потом спец кнопку добавлю, сейчас оно создается по кнопке выход, а текст диалога вводится в поле имени), т.е. у меня все легло на контроллер. Я понимаю, что это дикие костыли. Как это поправить? Даже если я создам отдельный класс, создам в нем соединение, унаследую его от интерфейса все хорошо, я даже смогу передать туда объект контроллер, дабы выводить смс на форму полученные с сервера, но я не смогу сделать обратного, из контроллера передать сообщение в этот класс. Как это решить, и как вообще правильно строятся данные приложения??
  • Вопрос задан
  • 2446 просмотров
Пригласить эксперта
Ответы на вопрос 1
@AlexHell
Помоему более-менее еще написано, не совсем говнокод, можно улучшить
если 2 контроллера - и это у вас разные Окна / Формы, то никакий из нихз не надо насоледовать от TCPConnection и TCPConnectionListener, надо сделать гдето Singleton
public static MyConnectionToServer
{
  private static readonly TCPConnection Connection
    = new .. (OnMessageReceived);
  private static readonly Dictionary<string, List<Action<MessageBase>>> Listeners = new ..;

  public static void SendToServer<T>(RequestBase request) { /* send */ }

  private static void OnMessageReceived(string message)
  {
  // все сообщения наследовать например от MessageBase
  // но в подклассах еще новые данные
  // в MessageBase только string MessageType

  // это десериализует только тип по сути, остальное игнорит
  MessageBase messageBase = Deserialize<MessageBase>(message); 

  MessageBase concreteMessage = DeserializeConcrete(messageBase.MessageType, message);
  Dispatch(messageBase.MessageType, concreteMessage);
}

  private static T DeserializeConcrete<T>(string messageType, string message)
  {
    switch (messageType)
    {
      case "type1": return Deserialize<MessageType1>(message);
      case "type2": return Deserialize<MessageType2>(message);
    }
  }

  private static void Dispatch(string messageType, T concreteMessage) where T: MessageBase
  {
  // юзать Listeners по messageType и в них передать concreteMessage
  }

  public static void AddListener<T>(string messageType, Action<T> onReceived)
  {
    // добавить в Dictionary по messageType
  }

  public static void RemoveListener<T>(string messageType, Action<T> onReceived)
  {
    // удалить из Dictionary по messageType
  }
}

а из каждого Контроллера Окна вызывать
AddListener("type1", (message) => ProcessType1(message));

и в конце RemoveListener("type1") чтобы не утекло
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы