Clients can’t communicate in private chat Server HELP PLEASE I AM CRYING

I’ve been trying to find the error for 3 days now, but I just can’t do it. I am so close to give up but i have come long way now… Quick overview: Multiple clients connect via a server. These all then end up in a public chat. You can access the private chat via a button. If both customers agree, a private scene opens (private chat). Everything works so far. But the message exchange in the private scene doesn’t work smoothly (a few messages are not output at all), and I don’t know why. Can someone help me please?


package javafiles;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

public class Server {
    private static final int PORT = 3141;
    private CopyOnWriteArrayList<ClientHandler> clients = new CopyOnWriteArrayList<>();
    private Map<ClientHandler, String> clientNames = new ConcurrentHashMap<>();
    private StringBuilder chatHistory = new StringBuilder(); // Chat history


    public void start() {
        try (ServerSocket serverSocket = new ServerSocket(PORT)) {
            System.out.println("Server läuft...");

            while (true) {
                Socket clientSocket = serverSocket.accept();
                new ClientHandler(this, clientSocket); // Start a new client handler for each client
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void addClient(ClientHandler clientHandler, String name) {
        clients.add(clientHandler);
        clientNames.put(clientHandler, name);
        // Send chat history to newly connected client
        clientHandler.sendMessage(chatHistory.toString());

    }

    public void removeClient(ClientHandler clientHandler) {
        clients.remove(clientHandler);
        clientNames.remove(clientHandler);
    }




    public String getClientName(ClientHandler clientHandler) {
        return clientNames.get(clientHandler);
    }


    public void requestPrivateChat(ClientHandler requester, String recipientName) {
        for (ClientHandler client : clients) {
            if (clientNames.get(client).equals(recipientName)) {
                client.sendMessage("Willst du mit " + clientNames.get(requester) + " einen Privatchat öffnen? (/privateResponse " + clientNames.get(requester) + " yes/no)");
                requester.sendMessage("Warten auf Antwort...");
                return;
            }
        }
        requester.sendMessage("Benutzer " + recipientName + " nicht gefunden.");
    }

   /*public void sendPrivateMessage(String recipientName, String message) {
        for (ClientHandler client : clients) {
            if (clientNames.get(client).equals(recipientName)) {
                client.sendMessage(message);
                return;
            }
        }
        // If recipient not found, send a message to the sender
        /*for (ClientHandler client : clients) {
            if (clientNames.get(client).equals(getClientName(sender))) {
                client.sendMessage("Benutzer " + recipientName + " nicht gefunden.");
                return;192.168.
            }
        }
    }*/

    public void sendPrivateMessage(ClientHandler sender, String recipientName, String message) {
        System.out.println("Attempting to send private message");
        System.out.println("Sender: " + clientNames.get(sender));
        System.out.println("Recipient Name: '" + recipientName + "'");
        System.out.println("Message: " + message);

        ClientHandler recipientHandler = null;
        for (ClientHandler client : clients) {
            System.out.println("Checking client: " + client.getName());
            if (client.getName().equalsIgnoreCase(recipientName)) {
                recipientHandler = client;
                break;
            }
        }

        if (recipientHandler != null) {
            System.out.println("Sending private message from " + clientNames.get(sender) + " to " + recipientName + ": " + message);
            recipientHandler.sendMessage("Privat von " + clientNames.get(sender) + ": " + message);
            sender.sendMessage("Privat von " + clientNames.get(sender) + ": " + message); // send to sender as well
        } else {
            sender.sendMessage("Benutzer " + recipientName + " nicht gefunden.");
            System.out.println("Recipient " + recipientName + " not found.");
        }
    }




package javafiles;

import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;

public class PrivateChatController {
    private String empfaengerin;
    private Socket socket;
    private BufferedReader fromServer;
    private PrintWriter toServer;
    @FXML
    private TextArea outputTextArea;

    private int port;

    @FXML
    private TextField inputTextField;


    @FXML
    private TextArea chatArea;

    @FXML
    private TextField messageField;

    public PrivateChatController() {
        port = 3141; // Default port
    }


    public void initialize(String empfaengerin, final Socket socket) throws IOException {
        this.empfaengerin = empfaengerin;
        this.socket = socket;

        fromServer = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        toServer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);

        new Thread(() -> {
            String message;
            try {
                while ((message = fromServer.readLine()) != null) {
                    System.out.println("NAchricht bekommen: " + message);
                    outputTextArea.appendText(message + "n");
                    System.out.println("WÜRDE ZU AREA HINZUGEFÜGT");
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }

    @FXML
    private void sendMessage() {
        String message = inputTextField.getText();
        if (!message.isEmpty()) {
            String prefix = "/privateMessage " + empfaengerin + " ";
            String modifiedMessage = prefix + message.trim(); // Trimming any extra spaces

            System.out.println("Sending message to server: " + modifiedMessage);

            // Send the modified message to the server
            toServer.println(modifiedMessage);

            // Append only the original message to the chat area
          //  outputTextArea.appendText("Ich: " + message + "n");

            // Clear the message field
            inputTextField.clear();
        }
    }




}










    public void handlePrivateChatResponse(ClientHandler responder, String requesterName, boolean accepted) {
        ClientHandler requester = null;
        for (ClientHandler client : clients) {
            if (clientNames.get(client).equals(requesterName)) {
                requester = client;
                break;
            }
        }

        if (requester != null) {
            if (accepted) {
                requester.sendMessage("Privatchat mit " + clientNames.get(responder) + " geöffnet.");
                responder.sendMessage("Privatchat mit " + clientNames.get(requester) + " geöffnet.");
            } else {
                requester.sendMessage(clientNames.get(responder) + " hat den Privatchat abgelehnt.");
            }
        } else {
            responder.sendMessage("Anfragender Benutzer " + requesterName + " nicht gefunden.");
        }
    }

    public void sendClientList(ClientHandler requester) {
        StringBuilder clientList = new StringBuilder("CLIENTLIST:");
        for (String name : clientNames.values()) {
            if (!name.equals(clientNames.get(requester))) {
                clientList.append(name).append(",");
            }
        }
        if (clientList.length() > 11) {
            clientList.setLength(clientList.length() - 1); // Remove the trailing comma
        }
        requester.sendMessage(clientList.toString());
    }

    public static void main(String[] args) {
        new Server().start();
    }


    public void broadcastMessage(String message) {
        // Add timestamp if the message is not a system message
        if (message.contains(":")) {
            SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
            String uhrzeit = dateFormat.format(new Date());
            message = message + "tt(" + uhrzeit + ")";
        }

        chatHistory.append(message).append("n"); // Append message to chat history

        for (ClientHandler client : clients) {
            client.sendMessage(message);
        }

        System.out.println(message); // Output message to server console
    }



}




package javafiles;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class ClientHandler implements Runnable {
    private Server server;
    private Socket clientSocket;
    private String name;

    public String getName() {
        return name;
    }

    private PrintWriter out;
    private BufferedReader in;

    public ClientHandler(Server server, Socket clientSocket) {
        this.server = server;
        this.clientSocket = clientSocket;
        new Thread(this).start();
    }

    public void sendPrivateMessage(String senderName, String message) {
        out.println("Privat von " + senderName + ": " + message);
    }
    @Override
    public void run() {
        try {
            out = new PrintWriter(clientSocket.getOutputStream(), true);
            in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

            // Handle initial message for username
            String initialMessage = in.readLine();
            if (initialMessage != null && initialMessage.startsWith("NAME:")) {
                name = initialMessage.substring(5).trim();
                System.out.println("Client name set to: " + name);
            }

            server.addClient(this, name);
            server.broadcastMessage(name + " connected.");

            String message;
            while ((message = in.readLine()) != null) {
                System.out.println("Nachricht bekommen von " + name + " Nachricht: " + message);

                if (message.startsWith("/privateMessage")) {
                    String[] parts = message.split("\s+", 3); // split on whitespace, max 3 parts
                    if (parts.length == 3) {
                        String recipient = parts[1].trim();
                        String privateMessage = parts[2].trim();
                        System.out.println("Recipient: '" + recipient + "', Message: " + privateMessage);
                        server.sendPrivateMessage(this, recipient, privateMessage);
                    } else {
                        System.out.println("Invalid /privateMessage format: " + message);
                    }
                } else if (message.startsWith("/privateRequest")) {
                    String[] parts = message.split("\s+", 2); // split on whitespace, max 2 parts
                    if (parts.length == 2) {
                        String recipient = parts[1].trim();
                        server.requestPrivateChat(this, recipient);
                    }
                } else if (message.startsWith("/privateResponse")) {
                    String[] parts = message.split("\s+", 3); // split on whitespace, max 3 parts
                    if (parts.length == 3) {
                        String sender = parts[1].trim();
                        boolean accepted = parts[2].trim().equalsIgnoreCase("yes");
                        server.handlePrivateChatResponse(this, sender, accepted);
                    }
                } else if (message.equals("/getClients")) {
                    server.sendClientList(this);
                } else {
                    server.broadcastMessage(name + ": " + message);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            server.removeClient(this);
            server.broadcastMessage(name + " disconnected.");
            closeResources();
        }
    }



    private void closeResources() {
        try {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
            if (clientSocket != null) {
                clientSocket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void sendMessage(String message) {
        out.println(message);
    }
}



package javafiles;

import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.stage.Stage;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Optional;

public class ClientController {
    private String address;
    private int port;

    // Connection
    private Socket connectionToServer;
    private BufferedReader fromServerReader;
    private PrintWriter toServerWriter;

    private String name; // Variable to store the user's name

    @FXML
    private TextArea outputTextArea;

    @FXML
    private TextField inputTextField;

    @FXML
    private Label uhrzeitLabel;

    @FXML
    private Button todobutton;

    @FXML
    private Button privateChatButton; // Button to start private chat

    public ClientController() {
        port = 3141; // Default port
    }

    @FXML
    private void initialize() {
        // Show popup to ask for server IP address
        TextInputDialog ipDialog = new TextInputDialog();
        ipDialog.setTitle("IP-Adresse eingeben");
        ipDialog.setHeaderText("Bitte geben Sie die IP-Adresse des Servers ein:");
        ipDialog.setContentText("IP-Adresse:");

        Optional<String> ipResult = ipDialog.showAndWait();

        if (ipResult.isPresent()) {
            address = ipResult.get().trim(); // Trim to remove leading/trailing spaces
            // Check if the entered IP address is valid (add your validation logic here)
            if (isValidIPAddress(address)) {
                // Show popup to ask user preference for name usage
                Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
                alert.setTitle("Name statt IP verwenden");
                alert.setHeaderText("Möchten Sie Ihren Namen statt Ihrer IP-Adresse verwenden?");
                alert.setContentText("Wählen Sie aus:");

                ButtonType buttonTypeYes = new ButtonType("Ja");
                ButtonType buttonTypeNo = new ButtonType("Nein");

                alert.getButtonTypes().setAll(buttonTypeYes, buttonTypeNo);

                alert.showAndWait().ifPresent(buttonType -> {
                    if (buttonType == buttonTypeYes) {
                        // Ask for name
                        name = askForName(); // Store the user's name
                    }
                    initializeConnection(); // Initialize connection with user's name (if provided) or default IP address
                });
            } else {
                // Invalid IP address entered, exit the application
                Alert alert = new Alert(Alert.AlertType.ERROR);
                alert.setTitle("Ungültige IP-Adresse");
                alert.setHeaderText(null);
                alert.setContentText("Ungültige IP-Adresse eingegeben. Verbindung unterbrochen.");

                alert.showAndWait();
                Platform.exit();
            }
        } else {
            // User clicked cancel, exit the application
            Platform.exit();
            System.out.println("Abbrechen gedrückt. Verbindung unterbrochen.");
        }

        // Erstelle einen Thread, um die Uhrzeit zu aktualisieren
        Thread thread = new Thread(() -> {
            while (true) {
                SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
                String uhrzeit = dateFormat.format(new Date());
                // Aktualisiere das Label auf dem JavaFX Application Thread
                Platform.runLater(() -> uhrzeitLabel.setText(uhrzeit));
                try {
                    // Warte eine Sekunde, bevor die Uhrzeit aktualisiert wird
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // Starte den Thread
        thread.start();
    }

    private String askForName() {
        TextInputDialog dialog = new TextInputDialog();
        dialog.setTitle("Name eingeben");
        dialog.setHeaderText("Bitte geben Sie Ihren Namen ein:");
        dialog.setContentText("Name:");

        Optional<String> result = dialog.showAndWait();
        return result.orElse(null);
    }

    private void initializeConnection() {

        try {
            connectionToServer = new Socket();
            connectionToServer.connect(new InetSocketAddress(address, port), 5000); // 5 seconds timeout

            fromServerReader = new BufferedReader(new InputStreamReader(connectionToServer.getInputStream()));
            toServerWriter = new PrintWriter(new OutputStreamWriter(connectionToServer.getOutputStream()), true);

            // Send the name to the server
            if (name != null && !name.isEmpty()) { // Check if the user provided a name
                toServerWriter.println("NAME:" + name);
            } else {
                // Fallback to default behavior if name is not provided or is empty
                name = connectionToServer.getLocalAddress().getHostAddress();
                toServerWriter.println("NAME:" + name);
            }

            inputTextField.setPromptText("Nachricht eingeben");

            new Thread(() -> {
                try {
                    String message;
                    while ((message = fromServerReader.readLine()) != null) {
                        if (!message.startsWith("Privat von ")) {
                            handleServerMessage(message);
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
        } catch (IOException e) {
            // Verbindung fehlgeschlagen, zeige Fehler-Popup an
            Alert alert = new Alert(Alert.AlertType.ERROR);
            alert.setTitle("Verbindungsfehler");
            alert.setHeaderText(null);
            alert.setContentText("Sorry, kein Server mit dieser IP im LAN gefunden.");
            alert.showAndWait();
            Platform.exit(); // Beende die Anwendung
        }
    }

    @FXML
    private void sendMessage() {
        String message = inputTextField.getText();
        if (!message.isEmpty()) {
            toServerWriter.println(message);
            inputTextField.clear();
        }
    }

    private void appendMessageToOutputTextArea(String message) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
        String uhrzeit = dateFormat.format(new Date());
        outputTextArea.appendText(message + "tt(" + uhrzeit + ")n");
    }

    private boolean isValidIPAddress(String ipAddress) {
        // Regex pattern for IPv4 address validation
        String ipv4Pattern = "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
        return ipAddress.matches(ipv4Pattern);
    }

    @FXML
    private void startPrivateChat() {
        Alert confirmationAlert = new Alert(Alert.AlertType.CONFIRMATION);
        confirmationAlert.setTitle("Privatchat starten");
        confirmationAlert.setHeaderText(null);
        confirmationAlert.setContentText("Möchten Sie einen Privatchat starten?");

        ButtonType buttonTypeYes = new ButtonType("Ja", ButtonBar.ButtonData.OK_DONE);
        ButtonType buttonTypeCancel = new ButtonType("Abbrechen", ButtonBar.ButtonData.CANCEL_CLOSE);

        confirmationAlert.getButtonTypes().setAll(buttonTypeYes, buttonTypeCancel);

        Optional<ButtonType> result = confirmationAlert.showAndWait();
        if (result.isPresent() && result.get() == buttonTypeYes) {
            // Request the list of active clients from the server
            toServerWriter.println("/getClients");
        }
    }

    private void showClientListPopup(String[] clientNames) {
        Platform.runLater(() -> {
            ChoiceDialog<String> dialog = new ChoiceDialog<>(clientNames.length > 0 ? clientNames[0] : "", clientNames);
            dialog.setTitle("Privatchat starten");
            dialog.setHeaderText("Wählen Sie einen Teilnehmer für den Privatchat aus:");
            dialog.setContentText("Teilnehmer:");

            Optional<String> result = dialog.showAndWait();
            result.ifPresent(this::requestPrivateChat);
        });
    }

    private void requestPrivateChat(String recipient) {
        toServerWriter.println("/privateRequest " + recipient);
    }

    private void handlePrivateChatRequest(String sender) {
        Platform.runLater(() -> {
            Alert privateChatRequestAlert = new Alert(Alert.AlertType.CONFIRMATION);
            privateChatRequestAlert.setTitle("Privatchat Anfrage");
            privateChatRequestAlert.setHeaderText(null);
            privateChatRequestAlert.setContentText("Willst du mit " + sender + " einen Privatchat öffnen?");

            ButtonType buttonTypeYes = new ButtonType("Ja");
            ButtonType buttonTypeNo = new ButtonType("Nein");

            privateChatRequestAlert.getButtonTypes().setAll(buttonTypeYes, buttonTypeNo);

            Optional<ButtonType> result = privateChatRequestAlert.showAndWait();
            if (result.isPresent()) {
                String response = "/privateResponse " + sender + " " + (result.get() == buttonTypeYes ? "yes" : "no");
                toServerWriter.println(response);
            }
        });
    }


    @FXML
    private void handlePrivateChatResponse(String requesterName, boolean accepted) {
        if (accepted) {
            Platform.runLater(() -> {
                try {
                    appendMessageToOutputTextArea("Privatchat-Anfrage von " + requesterName + " wurde angenommen.");
                    Stage privateChatStage = new Stage();
                    FXMLLoader loader = new FXMLLoader(getClass().getResource("../fxml/private.fxml"));
                    Parent root = loader.load();
                    PrivateChatController controller = loader.getController();
`your text`
                    // Use the same connection but identify messages for private chat
                    controller.initialize(requesterName, connectionToServer);

                    privateChatStage.setTitle("Privatchat mit " + requesterName);
                    privateChatStage.setScene(new Scene(root));
                    privateChatStage.show();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        } else {
            Platform.runLater(() -> {
                appendMessageToOutputTextArea("Privatchat-Anfrage von " + requesterName + " wurde abgelehnt.");
            });
        }
    }


    private void handleServerMessage(String message) {

        if (message.startsWith("/private")) {
            // Handle private message separately
            String[] parts = message.split(":", 3);
            if (parts.length == 3) {
                appendMessageToOutputTextArea(parts[2]); // Display private message
            }
        }
        else if (message.startsWith("Willst du mit")) {
            String sender = message.substring(14, message.indexOf(" einen Privatchat öffnen?"));
            handlePrivateChatRequest(sender);
        } else if (message.startsWith("CLIENTLIST:")) {
            String[] clientNames = message.substring(11).split(",");
            showClientListPopup(clientNames);
        } else if (message.startsWith("Privatchat mit ")) {
            String requesterName = message.substring(14, message.indexOf(" geöffnet."));
            handlePrivateChatResponse(requesterName, true);
        } else if (message.contains(" hat den Privatchat abgelehnt.")) {
            String requesterName = message.substring(0, message.indexOf(" hat den Privatchat abgelehnt."));
            handlePrivateChatResponse(requesterName, false);
        } else {
            appendMessageToOutputTextArea(message);
        }
    }

}




package javafiles;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;



public class ClientMain extends Application {



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


        primaryStage.setScene(new Scene(root));
        primaryStage.setTitle("Chat Client");
        primaryStage.show();




        /*FXMLLoader loaderHome = new FXMLLoader(getClass().getResource("fxml/home.fxml"));
        Parent root2 = loaderHome.load();

        primaryStage.setScene(new Scene(root2));
        primaryStage.setTitle("Home");
        primaryStage.show();*/
    }

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

   
}


New contributor

lxy is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật