Java基础-完成局域网内沟通软件的开发

目录

案例要求:

实现思路:

itheima-chat-server包

src

com.itheima

Constant类:

Server类:

ServerReaderThread类:

itheima-chat-system包

src

com.itheima.ui

ChatEntryFrame类:

ClientChatFrame类:

ClientReaderThread类:

Constant类:

APP启动类:

总结:


案例要求:

实现思路:

itheima-chat-server包

src

com.itheima
Constant类:
package com.itheima;

public class Constant {
    public static final int PORT = 6666;
}
Server类:
package com.itheima;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

public class Server {
    // 定义一个集合容器存储所有登录进来的客户端管道,以便将来群发消息给他们.
    // 定义一个Map集合,键是存储客户端的管道,值是这个管道的用户名称。
    public static final Map<Socket, String> onLineSockets = new HashMap<>();
    public static void main(String[] args) {
        System.out.println("启动服务端系统.....");
        try {
            // 1、注册端口。
            ServerSocket serverSocket = new ServerSocket(Constant.PORT);
            // 2、主线程负责接受客户端的连接请求
            while (true) {
                // 3、调用accept方法,获取到客户端的Socket对象
                System.out.println("等待客户端的连接.....");

                Socket socket = serverSocket.accept();
                // 把这个管道交给一个独立的线程来处理:以便支持很多客户端可以同时进来通信。
                new ServerReaderThread(socket).start();

                System.out.println("一个客户端连接成功.....");
            }
        } catch (Exception e) {
           e.printStackTrace();
        }
    }
}
ServerReaderThread类:
package com.itheima;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;

public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 接收的消息可能有很多种类型:1、登录消息(包含昵称) 2、群聊消息 3、私聊消息
            // 所以客户端必须声明协议发送消息
            // 比如客户端先发1,代表接下来是登录消息。
            // 比如客户端先发2,代表接下来是群聊消息。
            // 先从socket管道中接收客户端发送来的消息类型编号
            DataInputStream dis = new DataInputStream(socket.getInputStream());
            while (true) {
                int type = dis.readInt(); // 1、2、3
                switch (type){
                    case 1:
                        // 客户端发来了登录消息,接下来要接收昵称数据,再更新全部在线客户端的在线人数列表。
                        String nickname = dis.readUTF();
                        // 把这个登录成功的客户端socket存入到在线集合。
                        Server.onLineSockets.put(socket, nickname);
                        // 更新全部客户端的在线人数列表
                        updateClientOnLineUserList();
                        break;
                    case 2:
                        // 客户端发来了群聊消息,接下来要接收群聊消息内容,再把群聊消息转发给全部在线客户端。
                        String msg = dis.readUTF();
                        sendMsgToAll(msg);
                        break;
                    case 3:
                        // 客户端发来了私聊消息,接下来要接收私聊消息内容,再把私聊消息转发给指定客户端。
                        break;
                }
            }
        } catch (Exception e) {
            System.out.println("客户端下线了:"+ socket.getInetAddress().getHostAddress());
            Server.onLineSockets.remove(socket); // 把下线的客户端socket从在线集合中移除
            updateClientOnLineUserList(); // 下线了用户也需要更新全部客户端的在线人数列表。
        }
    }

    // 给全部在线socket推送当前客户端发来的消息
    private void sendMsgToAll(String msg) {
        // 一定要拼装好这个消息,再发给全部在线socket.
        StringBuilder sb = new StringBuilder();
        String name = Server.onLineSockets.get(socket);

        // 获取当前时间
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss EEE a");
        String nowStr = dtf.format(now);

        String msgResult = sb.append(name).append(" ").append(nowStr).append("\r\n")
                .append(msg).append("\r\n").toString();
        // 推送给全部客户端socket
        for (Socket socket : Server.onLineSockets.keySet()) {
            try {
                // 3、把集合中的所有用户名称,通过socket管道发送给客户端
                DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
                dos.writeInt(2); // 1代表告诉客户端接下来是在线人数列表信息  2 代表发的是群聊消息
                dos.writeUTF(msgResult);
                dos.flush(); // 刷新数据!
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void updateClientOnLineUserList() {
        // 更新全部客户端的在线人数列表
        // 拿到全部在线客户端的用户名称,把这些名称转发给全部在线socket管道。
        // 1、拿到当前全部在线用户昵称
        Collection<String> onLineUsers = Server.onLineSockets.values();
        // 2、把这个集合中的所有用户都推送给全部客户端socket管道。
        for (Socket socket : Server.onLineSockets.keySet()) {
            try {
                // 3、把集合中的所有用户名称,通过socket管道发送给客户端
                DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
                dos.writeInt(1); // 1代表告诉客户端接下来是在线人数列表信息  2 代表发的是群聊消息
                dos.writeInt(onLineUsers.size()); // 告诉客户端,接下来要发多少个用户名称
                for (String onLineUser : onLineUsers) {
                    dos.writeUTF(onLineUser);
                }
                dos.flush();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

itheima-chat-system包

src

com.itheima.ui
ChatEntryFrame类:
package com.itheima.ui;

import javax.swing.*;
import java.awt.*;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

public class ChatEntryFrame extends JFrame {

    private JTextField nicknameField;
    private JButton enterButton;
    private JButton cancelButton;
    private Socket socket; // 记住当前客户端系统的通信管道

    public ChatEntryFrame() {
        setTitle("局域网聊天室");
        setSize(350, 150);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setResizable(false); // 禁止调整大小

        // 设置背景颜色
        getContentPane().setBackground(Color.decode("#F0F0F0"));

        // 创建主面板并设置布局
        JPanel mainPanel = new JPanel(new BorderLayout());
        mainPanel.setBackground(Color.decode("#F0F0F0"));
        add(mainPanel);

        // 创建顶部面板
        JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
        topPanel.setBackground(Color.decode("#F0F0F0"));

        // 标签和文本框
        JLabel nicknameLabel = new JLabel("昵称:");
        nicknameLabel.setFont(new Font("楷体", Font.BOLD, 16));
        nicknameField = new JTextField(10);
        nicknameField.setFont(new Font("楷体", Font.PLAIN, 16));
        nicknameField.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createMatteBorder(1, 1, 1, 1, Color.GRAY),
                BorderFactory.createEmptyBorder(5, 5, 5, 5)
        ));

        topPanel.add(nicknameLabel);
        topPanel.add(nicknameField);
        mainPanel.add(topPanel, BorderLayout.NORTH);

        // 按钮面板
        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
        buttonPanel.setBackground(Color.decode("#F0F0F0"));

        enterButton = new JButton("登录");
        enterButton.setFont(new Font("楷体", Font.BOLD, 16));
        enterButton.setBackground(Color.decode("#007BFF"));
        enterButton.setForeground(Color.WHITE);
        enterButton.setBorderPainted(false);
        enterButton.setFocusPainted(false);

        cancelButton = new JButton("取消");
        cancelButton.setFont(new Font("楷体", Font.BOLD, 16));
        cancelButton.setBackground(Color.decode("#DC3545"));
        cancelButton.setForeground(Color.WHITE);
        cancelButton.setBorderPainted(false);
        cancelButton.setFocusPainted(false);

        buttonPanel.add(enterButton);
        buttonPanel.add(cancelButton);
        mainPanel.add(buttonPanel, BorderLayout.SOUTH);

        // 添加监听器
        enterButton.addActionListener(e -> {
            String nickname = nicknameField.getText(); // 获取昵称
            nicknameField.setText("");
            if (!nickname.isEmpty()) {
                try {
                    login(nickname);
                    // 进入聊天室逻辑: 启动聊天界面。把昵称传给聊天界面。
                    new ClientChatFrame(nickname, socket);
                    this.dispose(); // 关闭登录窗口
                } catch (Exception ex) {
                    ex.printStackTrace();
                }

            } else {
                JOptionPane.showMessageDialog(this, "请输入昵称!");
            }
        });

        cancelButton.addActionListener(e -> System.exit(0));

        this.setVisible(true); // 显示窗口
    }

    public void login(String nickname) throws Exception {
        // 立即发送登录消息给服务端程序。
        // 1、创建Socket管道请求与服务端的socket链接
        socket = new Socket(Constant.SERVER_IP, Constant.SERVER_PORT );
        // 2、立即发送消息类型1 和自己的昵称给服务端
        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
        dos.writeInt(1); // 消息类型 登录
        dos.writeUTF(nickname);
        dos.flush();
    }

    public static void main(String[] args) {
        new ChatEntryFrame();
    }
}
ClientChatFrame类:
package com.itheima.ui;

import javax.swing.*;
import java.awt.*;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

public class ChatEntryFrame extends JFrame {

    private JTextField nicknameField;
    private JButton enterButton;
    private JButton cancelButton;
    private Socket socket; // 记住当前客户端系统的通信管道

    public ChatEntryFrame() {
        setTitle("局域网聊天室");
        setSize(350, 150);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setResizable(false); // 禁止调整大小

        // 设置背景颜色
        getContentPane().setBackground(Color.decode("#F0F0F0"));

        // 创建主面板并设置布局
        JPanel mainPanel = new JPanel(new BorderLayout());
        mainPanel.setBackground(Color.decode("#F0F0F0"));
        add(mainPanel);

        // 创建顶部面板
        JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
        topPanel.setBackground(Color.decode("#F0F0F0"));

        // 标签和文本框
        JLabel nicknameLabel = new JLabel("昵称:");
        nicknameLabel.setFont(new Font("楷体", Font.BOLD, 16));
        nicknameField = new JTextField(10);
        nicknameField.setFont(new Font("楷体", Font.PLAIN, 16));
        nicknameField.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createMatteBorder(1, 1, 1, 1, Color.GRAY),
                BorderFactory.createEmptyBorder(5, 5, 5, 5)
        ));

        topPanel.add(nicknameLabel);
        topPanel.add(nicknameField);
        mainPanel.add(topPanel, BorderLayout.NORTH);

        // 按钮面板
        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
        buttonPanel.setBackground(Color.decode("#F0F0F0"));

        enterButton = new JButton("登录");
        enterButton.setFont(new Font("楷体", Font.BOLD, 16));
        enterButton.setBackground(Color.decode("#007BFF"));
        enterButton.setForeground(Color.WHITE);
        enterButton.setBorderPainted(false);
        enterButton.setFocusPainted(false);

        cancelButton = new JButton("取消");
        cancelButton.setFont(new Font("楷体", Font.BOLD, 16));
        cancelButton.setBackground(Color.decode("#DC3545"));
        cancelButton.setForeground(Color.WHITE);
        cancelButton.setBorderPainted(false);
        cancelButton.setFocusPainted(false);

        buttonPanel.add(enterButton);
        buttonPanel.add(cancelButton);
        mainPanel.add(buttonPanel, BorderLayout.SOUTH);

        // 添加监听器
        enterButton.addActionListener(e -> {
            String nickname = nicknameField.getText(); // 获取昵称
            nicknameField.setText("");
            if (!nickname.isEmpty()) {
                try {
                    login(nickname);
                    // 进入聊天室逻辑: 启动聊天界面。把昵称传给聊天界面。
                    new ClientChatFrame(nickname, socket);
                    this.dispose(); // 关闭登录窗口
                } catch (Exception ex) {
                    ex.printStackTrace();
                }

            } else {
                JOptionPane.showMessageDialog(this, "请输入昵称!");
            }
        });

        cancelButton.addActionListener(e -> System.exit(0));

        this.setVisible(true); // 显示窗口
    }

    public void login(String nickname) throws Exception {
        // 立即发送登录消息给服务端程序。
        // 1、创建Socket管道请求与服务端的socket链接
        socket = new Socket(Constant.SERVER_IP, Constant.SERVER_PORT );
        // 2、立即发送消息类型1 和自己的昵称给服务端
        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
        dos.writeInt(1); // 消息类型 登录
        dos.writeUTF(nickname);
        dos.flush();
    }

    public static void main(String[] args) {
        new ChatEntryFrame();
    }
}
ClientReaderThread类:
package com.itheima.ui;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class ClientReaderThread extends Thread{
    private Socket socket;
    private DataInputStream dis;
    private ClientChatFrame win;
    public ClientReaderThread( Socket socket, ClientChatFrame win) {
        this.win = win;
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 接收的消息可能有很多种类型:1、在线人数更新的数据 2、群聊消息
            dis = new DataInputStream(socket.getInputStream());
            while (true) {
                int type = dis.readInt(); // 1、2、3
                switch (type){
                    case 1:
                        // 服务端发来的在线人数更新消息
                        updateClientOnLineUserList();
                        break;
                    case 2:
                        // 服务端发送来的群聊消息
                        getMsgToWin();
                        break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void getMsgToWin() throws Exception {
        // 获取群聊消息
        String msg = dis.readUTF();
        win.setMsgToWin(msg);
    }

    // 更新客户端的在线用户列表
    private void updateClientOnLineUserList() throws Exception {
        // 1、读取有多少个在线用户
        int count = dis.readInt();

        // 2、循环控制读取多少个用户信息。
        String[] names = new String[count];
        for (int i = 0; i < count; i++) {
            // 3、读取每个用户的信息
            String nickname = dis.readUTF();
            // 4、将每个用户的信息添加到数组
            names[i] = nickname;
        }

        // 5、将集合中的数据展示到窗口上
        win.updateOnlineUsers(names);
    }


}

Constant类:

package com.itheima.ui;

public class Constant {
    public static final String SERVER_IP = "127.0.0.1";
    public static final int SERVER_PORT = 6666;
}

APP启动类:

import com.itheima.ui.ChatEntryFrame;

public class App {
    public static void main(String[] args) {
        new ChatEntryFrame(); // 启动登录界面
    }
}


总结:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杰克尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值