Java 中的多客户端套接字?

问题描述 投票:0回答:3

很快我将完成一个单客户端聊天系统作为一个娱乐个人项目,我想知道......将这样的东西转换为具有多个客户端是否简单,或者是否需要大量重写?

无论哪种情况,你会怎么做?请描述一下! :D 谢谢!

另外,如何通过套接字传输文件?

java sockets client
3个回答
2
投票

如果您正在考虑编写 IM 系统,那么我建议您使用更高级别的 协议和 API,例如 Jabber/XMPP 这是通过套接字传输文件的示例


1
投票

将单个客户端转换为多个客户端可以很简单。这取决于您如何撰写申请。 由于您正在重写一个以前已经编写过多次的应用程序,因此我认为您并不害怕重写代码,并且您将其作为一种学习练习。

最简单的改变是,现在你accept()一个客户端,你应该使用一个循环来接受多个客户端。 (并将该客户端的处理传递给另一个线程)。

要通过套接字传输文件,您需要能够像现在一样连接到服务器,读取文件并将内容发送到服务器。您无需将文本聊天消息传递给接收者,而是传递包含文件内容的文件消息。


0
投票
Group chat sever/clients communication

        public class ChatClient {
            // Instance level fields.
            private Socket client;
        
            // I/O streams for communicating with client.
            private ObjectInputStream in;
            private ObjectOutputStream out;
        
            // Using a thread-safe queue to handle multiple threads adding to the same
            // queue (potentially) and a single thread de-queueing and sending messages
            // across the network/internet.
            private BlockingQueue<Message> outgoingMessages = new LinkedBlockingDeque<>();
        
            // Reads messages from this specific client.
            private ReadThread readThread;
        
            // Writes messages to this specific client.
            private WriteThread writeThread;
        
            // Details about the current connection's client.
            public int clientNum;
            public String handle = "";
            public String groupName = "";
        
            public ChatClient(Socket client, int clientNum) {
                this.client = client;
                this.clientNum = clientNum;
        
                // Start read loop thread.
                readThread = new ReadThread();
                readThread.start();
        
                // Add client to lobby.
                Groups.join("lobby", ChatClient.this);
        
                // Queue group list to be sent to client.
                send(new GroupsListed());
            }
        
            /**
             *  Sending a message involves adding it to a queue of messages to be
             *  sent. The WriteThread will send ACTUALLY send the message on the connection
             *  to the client when it gets a turn to run.
             *
             *  Queueing messages prevents wierd things happening if multiple clients happen
             *  to be wanting to send to the SAME client at the SAME time and communication is
             *  not completed before the other starts sending. This can lead to garbled data
             *  being sent, due to race conditions.
             *
             *  Additionally, if there WERE a GUI, none of the writing on the output stream
             *  would happen on the UI thread. All that would be done on the UI thread is adding
             *  a message to a queue object.
             * @param message The message to be sent to this chat client.
             */
            public void send(Message message) {
                try {
                    outgoingMessages.put(message);
                } catch (InterruptedException e) {
                    System.out.println(clientNum + ": Read.Exception: " + e.getMessage());
                    e.printStackTrace();
                }
            }
        
            /**
             * This thread is responsible for setting up the I/O streams, then goes into
             * a read loop in which messages are read from the client (a blocking operation),
             * then processed. When shutting down the read thread, the write thread is
             * interrupted to stop it as well.
             */
            private class ReadThread extends Thread {
                @Override
                public void run() {
                    try {
                        System.out.println(clientNum + ": Read thread started.");
        
                        // Obtain I/O streams. Gotcha for object streams is to make sure
                        // that both sides do not set up the input stream first. One must
                        // set up the output stream and flush it, else the other side will
                        // wait for its input stream to be initialised (which it never will).
                        // Remember this side's output is other side's input.
                        out = new ObjectOutputStream(client.getOutputStream());
                        out.flush();
                        in = new ObjectInputStream(client.getInputStream());
                        System.out.println(clientNum + ": Obtained I/O streams.");
        
                        // Start write loop thread. Start write thread here to ensure
                        // that the I/O streams have been initialised correctly BEFORE
                        // starting to read and write messages.
                        writeThread = new WriteThread();
                        writeThread.start();
        
                        // Read messages from client.
                        System.out.println(clientNum + ": Started Read Loop...");
                        Message msg;
                        do {
                            // Read next message (blocking operation).
                            msg = (Message) in.readObject();
                            System.out.println(clientNum + " --> " + msg);
        
                            // Process the message.
                            msg.apply(ChatClient.this);
        
                        } while (msg.getClass() != Quit.class);
        
                        // Close the connection.
                        client.close();
        
                    } catch (Exception e) {
                        System.out.println(clientNum + ": Read.Exception: " + e.getMessage());
                        e.printStackTrace();
        
                    } finally {
                        System.out.println(clientNum + ": Leaving groups...");
                        Groups.leave(ChatClient.this);
        
                        System.out.println(clientNum + ": Stopping Write thread...");
                        writeThread.interrupt();
        
                        System.out.println(clientNum + ": Read thread finished.");
                    }
                }
            }
        
            /**
             * This thread is responsible for dequeueing messages (blocking operation) and
             * then sending them to the client.
             */
            private class WriteThread extends Thread {
                @Override
                public void run() {
                    System.out.println(clientNum + ": Started Write Loop thread...");
        
                    // Remember this thread.
                    writeThread = this;
        
                    try {
                        // Check outgoing messages and send.
                        while (!isInterrupted()) {
                            Message msg = outgoingMessages.take();
        
                            out.writeObject(msg);
                            out.flush();
        
                            System.out.println(msg + " --> " + clientNum);
                        }
        
                    } catch (Exception e) {
                        System.out.println(clientNum + ": Write.Exception = " + e.getMessage());
                        e.printStackTrace();
        
                    } finally {
                        writeThread = null;
                        System.out.println(clientNum + ": Write thread finished.");
                    }
                }
            }
    
    public class ChatServer {
        public static void main(String[] args) throws Exception {
            new ChatServer();
        }
    
        // The server socket that listens to port 5050 for connection requests.
        private ServerSocket server;
    
        private int clientNum = 0;
    
        public ChatServer() throws Exception {
            // Start new server socket on port 5050.
            server = new ServerSocket(5050);
            System.out.printf("Chat server started on: %s:5050\n",
                    InetAddress.getLocalHost().getHostAddress());
    
            // Create the initial group to which all clients belong.
            Groups.addGroup("lobby");
    
            while(true) {
                // Accept connection requests.
                Socket client = server.accept();
                System.out.printf("Connection request received: %s\n", client.getInetAddress().getHostAddress());
    
                // Increment number of clients encountered.
                clientNum++;
    
                // Create new client connection object to manage.
                ChatClient chatClient = new ChatClient(client, clientNum);
            }
        }
    }
    
    /**
     * This class is responsible for managing chat groups and sending messages to
     * clients in specific groups.
     */
    public class Groups {
        // Lock to prevent multiple threads manipulating the groups data
        // structure while busy with an operation.
        private static final ReentrantLock lock = new ReentrantLock();
    
        // [Group Name] -> {clients}
        public static final Map<String, Set<ChatClient>> groups = new HashMap<>();
    
        /**
         * Join a new group. Before joining a new group, the client will leave
         * any groups it is currently in. All other clients in the group are
         * notified of the client joining.
         * @param groupName The new group's name.
         * @param client The client joining.
         */
        public static void join(String groupName, ChatClient client) {
            // If already in a group, leave it.
            leave(client);
    
            // If no such group, create it.
            if(!groups.containsKey(groupName))
                addGroup(groupName);
    
            // Now join the new group.
            lock.lock();
                // Add client to group.
                groups.get(groupName).add(client);
                client.groupName = groupName;
    
                // Tell all clients that client joined group.
                groups.get(groupName)
                        .forEach(chatClient -> chatClient.send(new Joined(groupName, client.handle)));
            lock.unlock();
        }
    
        /**
         * The client leaves all groups currently a member of. All other clients
         * are sent a notification of this fact.
         * @param client The client leaving.
         */
        public static void leave(ChatClient client) {
            lock.lock();
                // Get groups to which client belongs.
                List<String> groupsIn = groups.entrySet()
                        .stream()
                        .filter(entry -> entry.getValue().contains(client))
                        .map(entry -> entry.getKey())
                        .collect(Collectors.toList());
    
                // Remove client from these groups and notify other clients of this.
                groupsIn.forEach(groupName -> {
                    // Get group.
                    Set<ChatClient> group = groups.get(groupName);
    
                    // Remove client from group.
                    group.remove(client);
                    client.groupName = "";
    
                    // Send message to other clients in group.
                    Left msg = new Left(groupName, client.handle);
                    group.forEach(chatClient -> chatClient.send(msg));
                });
            lock.unlock();
        }
    
        /**
         * A new (empty) group is added.
         * @param groupName The new group's name.
         */
        public static void addGroup(String groupName) {
            lock.lock();
                groups.put(groupName, new HashSet<>());
            lock.unlock();
        }
    
        /**
         * A message is sent to all clients in the named group.
         * @param groupName The group's name.
         * @param message The message to be sent.
         */
        public static void send(String groupName, Message message) {
            lock.lock();
                // Is there a group with the given name? If not, exit.
                if(!groups.containsKey(groupName)) return;
    
                // Get clients in group.
                Set<ChatClient> clients = groups.get(groupName);
    
                // Send message to each client.
                for(ChatClient client : clients)
                    client.send(message);
            lock.unlock();
        }
    
        /**
         * Send a message to ALL clients, regardless of the group
         * they're in.
         * @param message The message being sent.
         */
        public static void sendAll(Message message) {
            lock.lock();
                // groups.values().stream() returns a collection of SETS of
                // chat clients, i.e. a stream of (sets of chat clients).
                // The flatmap method flattens this into a stream of chat clients.
                groups.values()
                        .stream()
                        .flatMap(Collection::stream)
                        .distinct()
                        .forEach(chatClient -> {
                            chatClient.send(message);
                            System.out.println("sendAll: " + chatClient.handle + ", " + message);
                        });
            lock.unlock();
        }
    }
    /**
     * The message received when a client wishes to create a new
     * chat group.
     */
    public class AddGroup extends Message<ChatClient> {
        private static final long serialVersionUID = 7L;
    
        // The name of the group to create.
        public String groupName;
    
        public AddGroup(String groupName) {
            this.groupName = groupName;
        }
    
        @Override
        public String toString() {
            return String.format("AddGroup('%s')", groupName);
        }
    
        @Override
        public void apply(ChatClient chatClient) {
            // Add new chat group.
            Groups.addGroup(groupName);
    
            // Return the list of group names to all clients.
            Groups.sendAll(new GroupsListed());
        }
    }
    /**
     * The message received when a client wishes to join a different
     * chat group.
     */
    public class Join extends Message<ChatClient> {
        private static final long serialVersionUID = 1L;
        public String groupName;
    
        public Join(String groupName) {
            this.groupName = groupName;
        }
    
        @Override
        public String toString() {
            return String.format("Join('%s')", groupName);
        }
    
        @Override
        public void apply(ChatClient chatClient) {
            Groups.join(groupName, chatClient);
        }
    }
    /**
     * The message received when a client wishes to leave the
     * current chat group that they are in.
     */
    public class Leave extends Message<ChatClient> {
        private static final long serialVersionUID = 2L;
    
        @Override
        public String toString() {
            return "Leave()";
        }
    
        @Override
        public void apply(ChatClient chatClient) {
            // Client leaves the group they are currently in.
            Groups.leave(chatClient);
        }
    }
    **
     * The message received from a client when they wish to send a text
     * message to all the clients in the same group as them.
     */
    public class SendChatMessage extends Message<ChatClient> {
        private static final long serialVersionUID = 5L;
    
        // The text message to be sent to all clients in the same group.
        public String chatMessage;
    
        public SendChatMessage(String chatMessage) {
            this.chatMessage = chatMessage;
        }
    
        @Override
        public String toString() {
            return String.format("SendChatMessage('%s')", chatMessage);
        }
    
        @Override
        public void apply(ChatClient chatClient) {
            // Get group name of client.
            String groupName = chatClient.groupName;
            // If not in a group, don't proceed.
            if(groupName.length() == 0) return;
    
            // Send message to all clients in same group as client.
            Groups.send(groupName,
                    new ChatMessageReceived(groupName, chatClient.handle, chatMessage));
        }
    }
    /**
     * The message received from a client, typically once logged on initially,
     * to set the handle that will be used when communicating.
     */
    public class SetHandle extends Message<ChatClient> {
        private static final long serialVersionUID = 6L;
    
        public String handle;
    
        public SetHandle(String handle) {
            this.handle = handle;
        }
    
        @Override
        public String toString() {
            return String.format("SetHandle('%s')", handle);
        }
    
        @Override
        public void apply(ChatClient chatClient) {
            // Check if the handle is already being used. If is, append client number.
            long count = Groups.groups.values()
                    .stream()
                    .flatMap(Collection::stream)
                    .distinct()
                    .filter(client -> client.handle.equalsIgnoreCase(handle))
                    .count();
            if(count > 0) handle = String.format("%s#%d", handle, chatClient.clientNum);
    
            // Set the handle.
            chatClient.handle = handle;
    
            // Tell the client about the handle that was decided upon.
            chatClient.send(new HandleSet(handle));
        }
    }
    /**
     * Message sent from server to clients in a particular group in response
     * to a client in the group sending a SendChatMessage.
     */
    public class ChatMessageReceived extends Message {
        private static final long serialVersionUID = 100L;
    
        public Date  timeStamp;
        public String groupName;
        public String handle;
        public String chatMessage;
    
        public ChatMessageReceived(String groupName, String handle, String chatMessage) {
            this.groupName = groupName;
            this.handle = handle;
            this.chatMessage = chatMessage;
            timeStamp = new Date();
        }
    
        @Override
        public String toString() {
            return String.format("ChatMessageReceived(%s, '%s', '%s', '%s')",
                    timeStamp, groupName, handle, chatMessage);
        }
    }
    /**
     * Message sent to all clients in a group when a new client joins
     * a group.
     */
    public class Joined extends Message {
        private static final long serialVersionUID = 102L;
    
        public String groupName;
        public String handle;
    
        public Joined(String groupName, String handle) {
            this.groupName = groupName;
            this.handle = handle;
        }
    
        @Override
        public String toString() {
            return String.format("Joined(%s, %s)", groupName, handle);
        }
    }
    /**
     * A message that will be sent OR received on Object streams to and
     * from clients. Because want to use Object streams, needs to implement
     * the Serializable interface.
     *
     * The apply method is provided as a way for messages from a client to
     * be processed (applied to some context). The type of the context is
     * a generic and in these examples will be a ChatClient.
     *
     * Note: the exact definition of the classes in the Java server and the
     * Android client will differ slightly (in terms of methods, not fields).
     * In order for the slightly different classes to match when reading/writing
     * each class will have a <code>private static final long serialVersionUID</code>
     * that will be used to perform the matching.
     * @param <C> The type of the context.
     */
    public abstract class Message<C> implements Serializable {
        private static final long serialVersionUID = 999L;
    
        /**
         * Apply this message's logic to a specific context. This will only
         * be used for messages received from a client.
         * @param context The context to apply the message logic too.
         */
        public void apply(C context) {}
    }
© www.soinside.com 2019 - 2024. All rights reserved.