H
H
hHup2018-11-04 03:25:23
Java
hHup, 2018-11-04 03:25:23

How to correctly implement a client-server structure in this case?

The situation is this, there is a client-server application, a regular network chat. Sources: 3 modules Client, Server and the module responsible for the connection, the first and second modules depend on the third one. (Further shit code, and then the essence of the question):
Let's start with the third module, namely the network:
Interface 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 class
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();
    }
}

Next server module:
ChatServer class
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);
        }
    }
}

And the last module is the client one. It consists of a main class, a controller, and an fxml file:
Main class
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);
    }
}

Controller class
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) {

    }
}

Sample
<?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>

Everything seems to work, but the problem is that I create a connection in the controller (I will add a special button later, now it is created by the exit button, and the dialog text is entered in the name field), i.e. I have everything on the controller. I understand that these are wild crutches. How to fix it? Even if I create a separate class, create a connection in it, inherit it from the interface, everything is fine, I can even pass the controller object there in order to display the SMS received from the server on the form, but I can’t do the opposite, pass the message from the controller to this class . How to solve this, and how are these applications generally built correctly ??

Answer the question

In order to leave comments, you need to log in

1 answer(s)
A
AlexHell, 2019-01-05
@AlexHell

In my opinion, it’s more or less written, not quite shitty code, you can improve
if there are 2 controllers - and you have different Windows / Forms, then none of them need to be inherited from TCPConnection and TCPConnectionListener, you need to make a Singleton somewhere

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") чтобы не утекло

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question