Java学习总结-局域网通信项目实战

前置知识:

获取时间:

JDK8之前:

Date date = new Date();//获取当前时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置时间格式
System.out.println(sdf.format(date));

JDK8之后:

LocalDateTime now = LocalDateTime.now();
System.out.println(now + String.valueOf(now.getYear()));
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String nowStr = dtf.format(now);
System.out.println(nowStr);

StringBuilder高效拼接字符串

直接用“+”拼接,因为字符串变量在内存就一个,拼接就是声明一个新的String对象,开销很大。

BigDecimal解决字符串失真。

具体实施

思路分析:

一共两个页面登陆页面。

首先是登陆页面:

点击登陆按钮,就会触发点击事件。

然后调用login方法:socket与服务端建立连接,然后就是用特殊数据流DataOutputStream发送数据。首先发送“1”,告诉服务器有用户登陆进来了。再发送用户名给服务器。

让我很有收获的是,我在想怎么跳转界面,没想到是直接new一个群聊窗口对象,在new的时候就调用构造器方法,就完成界面初始化了。这里this起到大作用,因为新页面需要登陆界面的管道socket和用户名,本来的构造器接受这两个数据,并且调用兄弟构造器完成界面初始化。

这个就很有收获了,界面在互相跳转的时候,就会带着数据一起数据一起跳。

对了,我们把一些常量,专门用了一个类来存储,接下来很方便变动。

然后就是群聊页面:

在new客户端界面初始化,而且会唤醒一个进程,用来接受客户端的消息,根据消息判断要干什么。

点击发送按钮,就会触发点击事件。

然后调用sendMsgToServer方法,然后DataOutputStream数据流,借用上个页面的管道socket,发送“2”,这样服务器就会知道,发送的群聊消息。

为什么要用特殊数据流呢?因为很方便,发送的数据能直接拿来用。

群聊线程:

会不断接受来自服务器的请求,接受到1,就是更新用户列表。接收到2,就是追加消息。

最后服务器:

服务器会一直死循环,不断的尝试与客户端进行管道socket的连接。一旦有连接,这里多线程好处就体现出来了,直接将socket交给另一个线程处理ServerReaderThread(socket).start()。

接下来就是线程处理了,线程使用管道接受数据,先判断是1、还是2,这涉及到不同的功能。1更新用户列表,2发送消息给所有用户。

那么先说功能1:首先将用户名存储在集合中。然后将集合送入

Server.onLineSockets.put(socket,nickname);由这个方法进行处理,首先会发送1,让客户端进程知道要干嘛,在发送用户的个数,然后遍历发送。

功能2:首先读取发送的消息内容,然后调用sendMsgtoAll(msg),在这个方法中我们要将消息,用户名,时间进行拼接,使用StringBuilder来拼接,为什么要用这个,还有时间的获取,请看上一篇博客。然后发送就行了。

不错,很有收获!

接下来就是源代码

文件结构

public class Server {
    //定义一个集合容器存储所有的登录进来的客户端管道,以便未来群发消息给他们
    public static final Map<Socket, String> onLineSockets = new HashMap<>();
    public static void main(String[] args)
    {
        System.out.println("-------服务器启动-------");
        try {
            //注册端口
            ServerSocket serverSocket = new ServerSocket(Constant.PORT);
            //主线程负责接受客户端请求
            while (true) {
                System.out.println("等待客户端请求-----");
                Socket socket = serverSocket.accept();
                new ServerReaderThread(socket).start();
                System.out.println("一个客户端请求成功-----");
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
public class ServerReaderThread extends Thread {
    private Socket socket;

    public ServerReaderThread(Socket socket) {
        this.socket = socket;
    }
    public void run() {
        try {
            DataInputStream dis = new DataInputStream(socket.getInputStream());
            while (true) {
                int type = dis.readInt();
                switch (type) {
                    case 1:
                        String nickname = dis.readUTF();
                        Server.onLineSockets.put(socket,nickname);
                        //更新全部客户端的在线人数列表
                        updateClientOnlineList();
                        break;
                    case 2:
                        String msg = dis.readUTF();
                        sendMsgtoAll(msg);
                        break;
                    default:
                        System.out.println();
                }
            }

        } catch (IOException e) {
            System.out.println("客户端退出"+socket.getInetAddress().getAddress());
            Server.onLineSockets.remove(socket);//移除该socket
            updateClientOnlineList();//更新全部客户端的在线人数列表
        }
    }

    private void sendMsgtoAll(String msg) {
        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();
        for (Socket socket : Server.onLineSockets.keySet()) {
            try {
                DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
                dos.writeInt(2);
                dos.writeUTF(msgResult);
                dos.flush();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private void updateClientOnlineList() {
        Collection<String> onLineUsers = Server.onLineSockets.values();
        for (Socket socket : Server.onLineSockets.keySet()) {
            try {
                DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
                dos.writeInt(1);//1
                dos.writeInt(Server.onLineSockets.size());
                for (String nickname : Server.onLineSockets.values()) {
                    dos.writeUTF(nickname);
                }
                dos.flush();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
       }
    }
}
public class Constant {
    public static final int PORT = 6666;
}
public class ClientReaderThread extends Thread {
    private Socket socket;
    private DataInputStream dis;
    private LanGroupChatUI win;
    public ClientReaderThread(Socket socket, LanGroupChatUI win) {
        this.win = win;
        this.socket = socket;
    }
    public void run() {
        try {
            //接受消息的种类,1更新在线人数2群聊消息
            dis = new DataInputStream(socket.getInputStream());
            while (true) {
                int type = dis.readInt();
                switch (type) {
                    case 1:
                        updateClientOnlineUserList();
                        break;
                    case 2:
                        getMsgToWin();
                        break;
                    default:
                        System.out.println();
                }
            }

        } catch (IOException e) {

        }
    }

    private void getMsgToWin() throws IOException {
        String msg = dis.readUTF();
        win.setMsgToWin(msg);
    }

    private void updateClientOnlineUserList() throws IOException {
        int count = dis.readInt();
        String[] onlinenames = new String[count];
        for (int i = 0; i < count; i++) {
            String nickname = dis.readUTF();
            onlinenames[i] = nickname;
        }
        win.updateClientOnlineUserList(onlinenames);
    }


}

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

public class LanChatEntryUI extends JFrame {
    private JTextField nicknameField;
    private JButton enterButton;
    private JButton cancelButton;
    private Socket socket;

    public LanChatEntryUI() {
        try {
            // 设置界面风格为系统默认风格,使界面更美观
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 设置窗口标题
        setTitle("局域网聊天 - 进入界面");
        // 设置窗口大小
        setSize(300, 200);
        // 设置窗口关闭操作
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // 设置窗口布局
        setLayout(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets = new Insets(10, 10, 10, 10);

        // 昵称标签
        JLabel nicknameLabel = new JLabel("昵称:");
        nicknameLabel.setFont(new Font("楷体", Font.PLAIN, 16));
        gbc.gridx = 0;
        gbc.gridy = 0;
        add(nicknameLabel, gbc);

        // 昵称输入框
        nicknameField = new JTextField(20);
        nicknameField.setFont(new Font("楷体", Font.PLAIN, 16));
        gbc.gridx = 1;
        gbc.gridy = 0;
        add(nicknameField, gbc);

        // 创建一个面板用于放置按钮
        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 10)); // 水平居中对齐

        // 进入按钮
        enterButton = new JButton("进入");
        enterButton.setFont(new Font("楷体", Font.PLAIN, 16));
        enterButton.setOpaque(true); // 确保按钮是不透明的
        enterButton.setBackground(Color.GREEN); // 设置背景颜色
        enterButton.setForeground(Color.BLACK); // 设置字体颜色
        enterButton.setFocusPainted(false); // 去掉焦点边框
        enterButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String nickname = nicknameField.getText();
                nicknameField.setText("");
                if (!nickname.isEmpty()) {
                    try {
                        login(nickname);
                        dispose();
                        new LanGroupChatUI(nickname, socket);
                    } catch (IOException ex) {
                        throw new RuntimeException(ex);
                    }
                } else {
                    JOptionPane.showMessageDialog(LanChatEntryUI.this, "请输入昵称", "提示", JOptionPane.WARNING_MESSAGE);
                }
            }
        });
        buttonPanel.add(enterButton);

        // 取消按钮
        cancelButton = new JButton("取消");
        cancelButton.setFont(new Font("楷体", Font.PLAIN, 16));
        cancelButton.setOpaque(true); // 确保按钮是不透明的
        cancelButton.setBackground(Color.RED); // 设置背景颜色
        cancelButton.setForeground(Color.BLACK); // 设置字体颜色
        cancelButton.setFocusPainted(false); // 去掉焦点边框
        cancelButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.exit(0); // 点击取消时退出程序
            }
        });
        buttonPanel.add(cancelButton);

        // 将按钮面板添加到主界面
        gbc.gridx = 0;
        gbc.gridy = 1;
        gbc.gridwidth = 2; // 占据两列
        gbc.anchor = GridBagConstraints.CENTER; // 居中显示
        add(buttonPanel, gbc);

        // 居中显示窗口
        setLocationRelativeTo(null);
        // 显示窗口
        setVisible(true);
    }
    public void login(String nickname) throws IOException {
        socket = new Socket(Constant.SERVER_IP, Constant.SERVER_PORT);
        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
        dos.writeInt(1);
        dos.writeUTF(nickname);
        dos.flush();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new LanChatEntryUI();
            }
        });
    }
}

public class LanGroupChatUI extends JFrame {
    private JTextArea messageDisplayArea;
    private JTextField messageInputField;
    private JButton sendButton;
    private JList<String> onlineNicknameList;
    private DefaultListModel<String> onlineNicknameModel;
    private String nickname;
    private Socket socket;

    public LanGroupChatUI(String nickname, Socket socket){
        this();
        //立马展示昵称到窗口
        this.setTitle(nickname);
        this.socket = socket;
        new ClientReaderThread(socket,this).start();

    }
    public void sendMsgToServer(String message) throws IOException {
        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
        dos.writeInt(2);
        dos.writeUTF(message);
    }

    public LanGroupChatUI() {
        // 设置窗口标题
        setTitle("局域网群聊界面");
        // 设置窗口大小
        setSize(800, 600);
        // 设置窗口关闭操作
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // 设置布局为边界布局
        setLayout(new BorderLayout());

        // 设置字体为楷体
        Font kaiTiFont = new Font("楷体", Font.PLAIN, 14);

        // 群聊消息展示框
        messageDisplayArea = new JTextArea();
        messageDisplayArea.setFont(kaiTiFont);
        messageDisplayArea.setEditable(false);
        JScrollPane messageScrollPane = new JScrollPane(messageDisplayArea);
        add(messageScrollPane, BorderLayout.CENTER);

        // 消息发送面板
        JPanel inputPanel = new JPanel(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets = new Insets(5, 5, 5, 5);

        messageInputField = new JTextField();
        messageInputField.setFont(kaiTiFont);

        // 动态计算输入框高度
        int windowHeight = getHeight();
        int inputFieldHeight = (int) (windowHeight * 0.2);

        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.weightx = 1.0;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.ipady = inputFieldHeight - messageInputField.getPreferredSize().height;
        inputPanel.add(messageInputField, gbc);

        sendButton = new JButton("发送");
        sendButton.setFont(kaiTiFont);
        //调整按钮的大小,稍微大一点
        sendButton.setPreferredSize(new Dimension(100, inputFieldHeight));

        gbc.gridx = 1;
        gbc.gridy = 0;
        gbc.weightx = 0;//
        gbc.fill = GridBagConstraints.NONE;
        gbc.ipady = 0;
        inputPanel.add(sendButton, gbc);

        add(inputPanel, BorderLayout.SOUTH);

        // 在线昵称展示
        onlineNicknameModel = new DefaultListModel<>();


        onlineNicknameList = new JList<>(onlineNicknameModel);
        onlineNicknameList.setFont(kaiTiFont);
        JScrollPane nicknameScrollPane = new JScrollPane(onlineNicknameList);
        //调整宽度
        nicknameScrollPane.setPreferredSize(new Dimension(150, getHeight()));
        add(nicknameScrollPane, BorderLayout.EAST);

        // 发送按钮点击事件
        sendButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String message = messageInputField.getText();
                //sendButton.setText("");
                try {
                    sendMsgToServer(message);
                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }

            }


        });

        // 居中显示窗口
        setLocationRelativeTo(null);
        // 显示窗口
        setVisible(true);
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值